httpcaddyfile: Fix default issuers when email provided

If `tls <email>` is used, we should apply that to all applicable default issuers, not drop them. This refactoring applies implicit ACME issuer settings from the tls directive to all default ACME issuers, like ZeroSSL.

We also consolidate some annoying logic and improve config validity checks.

Ref: https://caddy.community/t/error-obtaining-certificate-after-caddy-restart/11335/8
This commit is contained in:
Matthew Holt 2021-02-02 16:17:26 -07:00
parent 2772ede43c
commit 90284e8017
No known key found for this signature in database
GPG key ID: 2A349DD577D586A5
4 changed files with 71 additions and 49 deletions

View file

@ -369,31 +369,57 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
}) })
} }
// some tls subdirectives are shortcuts that implicitly configure issuers, and the
// user can also configure issuers explicitly using the issuer subdirective; the
// logic to support both would likely be complex, or at least unintuitive
if len(issuers) > 0 && (acmeIssuer != nil || internalIssuer != nil) { if len(issuers) > 0 && (acmeIssuer != nil || internalIssuer != nil) {
// some tls subdirectives are shortcuts that implicitly configure issuers, and the
// user can also configure issuers explicitly using the issuer subdirective; the
// logic to support both would likely be complex, or at least unintuitive
return nil, h.Err("cannot mix issuer subdirective (explicit issuers) with other issuer-specific subdirectives (implicit issuers)") return nil, h.Err("cannot mix issuer subdirective (explicit issuers) with other issuer-specific subdirectives (implicit issuers)")
} }
for _, issuer := range issuers { if acmeIssuer != nil && internalIssuer != nil {
configVals = append(configVals, ConfigValue{ return nil, h.Err("cannot create both ACME and internal certificate issuers")
Class: "tls.cert_issuer",
Value: issuer,
})
} }
if acmeIssuer != nil {
configVals = append(configVals, ConfigValue{ // now we should either have: explicitly-created issuers, or an implicitly-created
Class: "tls.cert_issuer", // ACME or internal issuer, or no issuers at all
Value: disambiguateACMEIssuer(acmeIssuer), switch {
}) case len(issuers) > 0:
} for _, issuer := range issuers {
if internalIssuer != nil { configVals = append(configVals, ConfigValue{
Class: "tls.cert_issuer",
Value: issuer,
})
}
case acmeIssuer != nil:
// implicit ACME issuers (from various subdirectives) - use defaults; there might be more than one
defaultIssuers := caddytls.DefaultIssuers()
// if a CA endpoint was set, override multiple implicit issuers since it's a specific one
if acmeIssuer.CA != "" {
defaultIssuers = []certmagic.Issuer{acmeIssuer}
}
for _, issuer := range defaultIssuers {
switch iss := issuer.(type) {
case *caddytls.ACMEIssuer:
issuer = acmeIssuer
case *caddytls.ZeroSSLIssuer:
iss.ACMEIssuer = acmeIssuer
}
configVals = append(configVals, ConfigValue{
Class: "tls.cert_issuer",
Value: issuer,
})
}
case internalIssuer != nil:
configVals = append(configVals, ConfigValue{ configVals = append(configVals, ConfigValue{
Class: "tls.cert_issuer", Class: "tls.cert_issuer",
Value: internalIssuer, Value: internalIssuer,
}) })
} }
// certificate key type
if keyType != "" { if keyType != "" {
configVals = append(configVals, ConfigValue{ configVals = append(configVals, ConfigValue{
Class: "tls.key_type", Class: "tls.key_type",

View file

@ -316,13 +316,15 @@ func (st ServerType) buildTLSApp(
if hasGlobalACMEDefaults { if hasGlobalACMEDefaults {
for _, ap := range tlsApp.Automation.Policies { for _, ap := range tlsApp.Automation.Policies {
if len(ap.Issuers) == 0 { if len(ap.Issuers) == 0 {
acme, zerosslACME := new(caddytls.ACMEIssuer), new(caddytls.ACMEIssuer) ap.Issuers = caddytls.DefaultIssuers()
zerossl := &caddytls.ZeroSSLIssuer{ACMEIssuer: zerosslACME}
ap.Issuers = []certmagic.Issuer{acme, zerossl} // TODO: keep this in sync with Caddy's other issuer defaults elsewhere, like in caddytls/automation.go (DefaultIssuers).
// if a non-ZeroSSL endpoint is specified, we assume we can't use the ZeroSSL issuer successfully // if a specific endpoint is configured, can't use multiple default issuers
if globalACMECA != nil && !strings.Contains(globalACMECA.(string), "zerossl") { if globalACMECA != nil {
ap.Issuers = []certmagic.Issuer{acme} if strings.Contains(globalACMECA.(string), "zerossl") {
ap.Issuers = []certmagic.Issuer{&caddytls.ZeroSSLIssuer{ACMEIssuer: new(caddytls.ACMEIssuer)}}
} else {
ap.Issuers = []certmagic.Issuer{new(caddytls.ACMEIssuer)}
}
} }
} }
} }
@ -463,19 +465,6 @@ func newBaseAutomationPolicy(options map[string]interface{}, warnings []caddycon
return ap, nil return ap, nil
} }
// disambiguateACMEIssuer returns an issuer based on the properties of acmeIssuer.
// If acmeIssuer implicitly configures a certain kind of ACMEIssuer (for example,
// ZeroSSL), the proper wrapper over acmeIssuer will be returned instead.
func disambiguateACMEIssuer(acmeIssuer *caddytls.ACMEIssuer) certmagic.Issuer {
// as a special case, we integrate with ZeroSSL's ACME endpoint if it looks like an
// implicit ZeroSSL configuration (this requires a wrapper type over ACMEIssuer
// because of the EAB generation; if EAB is provided, we can use plain ACMEIssuer)
if strings.Contains(acmeIssuer.CA, "acme.zerossl.com") && acmeIssuer.ExternalAccount == nil {
return &caddytls.ZeroSSLIssuer{ACMEIssuer: acmeIssuer}
}
return acmeIssuer
}
// consolidateAutomationPolicies combines automation policies that are the same, // consolidateAutomationPolicies combines automation policies that are the same,
// for a cleaner overall output. // for a cleaner overall output.
func consolidateAutomationPolicies(aps []*caddytls.AutomationPolicy) []*caddytls.AutomationPolicy { func consolidateAutomationPolicies(aps []*caddytls.AutomationPolicy) []*caddytls.AutomationPolicy {

View file

@ -444,7 +444,7 @@ func (app *App) createAutomationPolicies(ctx caddy.Context, internalNames []stri
// what the HTTP and HTTPS ports are) // what the HTTP and HTTPS ports are)
if ap.Issuers == nil { if ap.Issuers == nil {
var err error var err error
ap.Issuers, err = caddytls.DefaultIssuers(ctx) ap.Issuers, err = caddytls.DefaultIssuersProvisioned(ctx)
if err != nil { if err != nil {
return err return err
} }
@ -499,7 +499,7 @@ func (app *App) createAutomationPolicies(ctx caddy.Context, internalNames []stri
// never overwrite any other issuer that might already be configured // never overwrite any other issuer that might already be configured
if basePolicy.Issuers == nil { if basePolicy.Issuers == nil {
var err error var err error
basePolicy.Issuers, err = caddytls.DefaultIssuers(ctx) basePolicy.Issuers, err = caddytls.DefaultIssuersProvisioned(ctx)
if err != nil { if err != nil {
return err return err
} }

View file

@ -187,7 +187,7 @@ func (ap *AutomationPolicy) Provision(tlsApp *TLS) error {
issuers := ap.Issuers issuers := ap.Issuers
if len(issuers) == 0 { if len(issuers) == 0 {
var err error var err error
issuers, err = DefaultIssuers(tlsApp.ctx) issuers, err = DefaultIssuersProvisioned(tlsApp.ctx)
if err != nil { if err != nil {
return err return err
} }
@ -242,21 +242,28 @@ func (ap *AutomationPolicy) Provision(tlsApp *TLS) error {
return nil return nil
} }
// DefaultIssuers returns empty but provisioned default Issuers. // DefaultIssuers returns empty Issuers (not provisioned) to be used as defaults.
// This function is experimental and has no compatibility promises. // This function is experimental and has no compatibility promises.
func DefaultIssuers(ctx caddy.Context) ([]certmagic.Issuer, error) { func DefaultIssuers() []certmagic.Issuer {
acme := new(ACMEIssuer) return []certmagic.Issuer{
err := acme.Provision(ctx) new(ACMEIssuer),
if err != nil { &ZeroSSLIssuer{ACMEIssuer: new(ACMEIssuer)},
return nil, err
} }
zerossl := new(ZeroSSLIssuer) }
err = zerossl.Provision(ctx)
if err != nil { // DefaultIssuersProvisioned returns empty but provisioned default Issuers from
return nil, err // DefaultIssuers(). This function is experimental and has no compatibility promises.
func DefaultIssuersProvisioned(ctx caddy.Context) ([]certmagic.Issuer, error) {
issuers := DefaultIssuers()
for i, iss := range issuers {
if prov, ok := iss.(caddy.Provisioner); ok {
err := prov.Provision(ctx)
if err != nil {
return nil, fmt.Errorf("provisioning default issuer %d: %T: %v", i, iss, err)
}
}
} }
// TODO: eventually, insert ZeroSSL into first position in the slice -- see also httpcaddyfile/tlsapp.go for where similar defaults are configured return issuers, nil
return []certmagic.Issuer{acme, zerossl}, nil
} }
// ChallengesConfig configures the ACME challenges. // ChallengesConfig configures the ACME challenges.