Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow specific issuers for specific on-demand domains #298

Open
mbardelmeijer opened this issue Jul 1, 2024 · 7 comments
Open

Allow specific issuers for specific on-demand domains #298

mbardelmeijer opened this issue Jul 1, 2024 · 7 comments
Labels
question Further information is requested

Comments

@mbardelmeijer
Copy link
Contributor

What is your question?

We use LetsEncrypt & ZeroSSL's API issuers. We prefer to configure to allow domains to use LetsEncrypt, and IP addresses the ZeroSSL API only. We can only specify a singular DecisionFunc and define the Issuers globally. Is it possible to define that some domains may use 1 issuer and other domains another?

What have you already tried?

No attempts have been made.

@mbardelmeijer mbardelmeijer added the question Further information is requested label Jul 1, 2024
@mohammed90
Copy link
Member

It's actually doable using the JSON config. See here, you can enable on_demand on a specific automation policy, which includes specifying the issuer.

@mholt
Copy link
Member

mholt commented Jul 1, 2024

It is possible as Mohammed said, but note that this is the CertMagic repo not the Caddy repo. I'm mobile but I'll reply with details soonish

@mholt
Copy link
Member

mholt commented Jul 1, 2024

@mbardelmeijer So, the answer to your question: "Is it possible to define that some domains may use 1 issuer and other domains another?" -- is yes.

In your DecisionFunc, just return true for the domains or IPs that are allowed to get an on-demand cert, and false for the others.

We can only specify a singular DecisionFunc and define the Issuers globally

I don't see how this is the case, since these are specified only per-Config, e.g.: https://pkg.go.dev/github.com/caddyserver/certmagic#Config.OnDemand

Use the relevant Config for your domain(s); how you do this depends on your code/program, but without actual code I can't provide any more specifics that are helpful or relevant. Caddy does it, you could look at its source code. You may find tls.Config.GetConfigForClient() helpful.

@mbardelmeijer
Copy link
Contributor Author

@mholt thanks for those details -- great to hear it's possible by using separate configs! Our implementation (cleaned up) looks like below.

In this format, I'm unsure how to best implement different configs per Issuer. Do we need to return a new config instance in the GetConfigForCert there, or is there another (recommend) way?

var cache *certmagic.Cache
cache = certmagic.NewCache(certmagic.CacheOptions{
	GetConfigForCert: func(certmagic.Certificate) (*certmagic.Config, error) {
		// Here we use New to get a valid Config associated with the same cache.
		// The provided Config is used as a template and will be completed with
		// any defaults that are set in the Default config.
		return certmagic.New(cache, certmagic.Config{
			OnEvent: onEvent,
		}), nil
	},
	Logger: logger.With(zap.String("component", "acme_cache")),
})

magic := certmagic.New(cache, certmagic.Config{
	OnEvent: onEvent,
})

magic.Logger = logger.With(zap.String("component", "acme"))
magic.Storage = NewCertStoreFromCertificatesConfig(&config.Certificates)
magic.OnDemand = &certmagic.OnDemandConfig{
	DecisionFunc: func(ctx context.Context, name string) error {
		// Decision logic. We're unsure about the `Issuer` here, as it shares the same config instance.
		log.Debugf("Accepted to request certificate for: %s", name)
		return nil
	},
}

if config.Certificates.LetsEncryptEnabled {
	magic.Issuers = append(magic.Issuers, certmagic.NewACMEIssuer(magic, certmagic.ACMEIssuer{
		CA:                   config.Certificates.LetsEncryptCa,
		Logger:               logger.With(zap.String("component", "letsencrypt_acme_issuer")),
		Email:                config.Certificates.AcmeEmail,
		Agreed:               true,
	}))
}

// @TODO: we wish only to allow this ZeroSSL API for IP address decisions.
if config.Certificates.ZeroSSLAPIEnabled {
	// https://github.com/caddyserver/certmagic#zerossl
	magic.Issuers = append(magic.Issuers, &certmagic.ZeroSSLIssuer{
		APIKey: config.Certificates.ZeroSSLAPIKey,
		Storage: magic.Storage, // https://github.com/caddyserver/certmagic/issues/291
		Logger: logger.With(zap.String("component", "zerossl_api_issuer")),
	})
}

@mholt
Copy link
Member

mholt commented Jul 5, 2024

That looks pretty good. Yeah, so if each domain has a different certmagic.Config (an issuer is just part of a Config), then you'll want to make a new Config for each one, with the right issuer(s) for each domain.

@mbardelmeijer
Copy link
Contributor Author

Is that possible when using the OnDemand method as well? We don't know beforehand which domains "we have", as the DecisionFunc will determine if a domain is valid or not.

I'm unsure how to have a singular certmagic instance with multiple configs. Is there some documentation or pseudo code available for such a setup?

@mholt
Copy link
Member

mholt commented Jul 10, 2024

Yep. OnDemand is just a boolean flag that decides whether handshakes can trigger cert automation (and an associated DecisionFunc to allow it). When you call Manage*() (Sync/Async) on a certmagic.Config, it will manage those names according to that config.

No need to draft psuedocode, Caddy does this very thing: https://github.com/caddyserver/caddy/blob/4943a4fc527f41e32c1770164b00980b4226f7c7/modules/caddytls/connpolicy.go#L107

Notice how it uses GetConfigForClient() so you can use the desired config for the domain.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

3 participants