summaryrefslogtreecommitdiff
path: root/modules/caddyhttp
diff options
context:
space:
mode:
Diffstat (limited to 'modules/caddyhttp')
-rw-r--r--modules/caddyhttp/autohttps.go214
1 files changed, 145 insertions, 69 deletions
diff --git a/modules/caddyhttp/autohttps.go b/modules/caddyhttp/autohttps.go
index 1239abb..d8e5c26 100644
--- a/modules/caddyhttp/autohttps.go
+++ b/modules/caddyhttp/autohttps.go
@@ -407,98 +407,129 @@ redirServersLoop:
// base for the new ones (this is important for preserving behavior the
// user intends to be "defaults").
func (app *App) createAutomationPolicies(ctx caddy.Context, publicNames, internalNames []string) error {
- // nothing to do if no names to manage certs for
- if len(publicNames) == 0 && len(internalNames) == 0 {
- return nil
- }
-
- // start by finding a base policy that the user may have defined
- // which should, in theory, apply to any policies derived from it;
- // typically this would be a "catch-all" policy with no host filter
+ // before we begin, loop through the existing automation policies
+ // and, for any ACMEIssuers we find, make sure they're filled in
+ // with default values that might be specified in our HTTP app; also
+ // look for a base (or "catch-all" / default) automation policy,
+ // which we're going to essentially require, to make sure it has
+ // those defaults, too
var basePolicy *caddytls.AutomationPolicy
- if app.tlsApp.Automation != nil {
- for _, ap := range app.tlsApp.Automation.Policies {
- // if an existing policy matches (specifically, a catch-all policy),
- // we should inherit from it, because that is what the user expects;
- // this is very common for user setting a default issuer, with a
- // custom CA endpoint, for example - whichever one we choose must
- // have a host list that is a superset of the policy we make...
- // the policy with no host filter is guaranteed to qualify
- if len(ap.Subjects) == 0 {
- basePolicy = ap
- break
+ var foundBasePolicy bool
+ if app.tlsApp.Automation == nil {
+ // we will expect this to not be nil from now on
+ app.tlsApp.Automation = new(caddytls.AutomationConfig)
+ }
+ for _, ap := range app.tlsApp.Automation.Policies {
+ // set up default issuer -- honestly, this is only
+ // really necessary because the HTTP app is opinionated
+ // and has settings which could be inferred as new
+ // defaults for the ACMEIssuer in the TLS app
+ if ap.Issuer == nil {
+ ap.Issuer = new(caddytls.ACMEIssuer)
+ }
+ if acmeIssuer, ok := ap.Issuer.(*caddytls.ACMEIssuer); ok {
+ err := app.fillInACMEIssuer(acmeIssuer)
+ if err != nil {
+ return err
}
}
+
+ // while we're here, is this the catch-all/base policy?
+ if !foundBasePolicy && len(ap.Subjects) == 0 {
+ basePolicy = ap
+ foundBasePolicy = true
+ }
}
+
if basePolicy == nil {
+ // no base policy found, we will make one!
basePolicy = new(caddytls.AutomationPolicy)
}
- // addPolicy adds an automation policy that uses issuer for hosts.
- addPolicy := func(issuer certmagic.Issuer, hosts []string) error {
- // shallow-copy the matching policy; we want to inherit
- // from it, not replace it... this takes two lines to
- // overrule compiler optimizations
- policyCopy := *basePolicy
- newPolicy := &policyCopy
+ // if the basePolicy has an existing ACMEIssuer, let's
+ // use it, otherwise we'll make one
+ baseACMEIssuer, _ := basePolicy.Issuer.(*caddytls.ACMEIssuer)
+ if baseACMEIssuer == nil {
+ // note that this happens if basePolicy.Issuer is nil
+ // OR if it is not nil but is not an ACMEIssuer
+ baseACMEIssuer = new(caddytls.ACMEIssuer)
+ }
- // very important to provision it, since we are
- // bypassing the JSON-unmarshaling step
- if prov, ok := issuer.(caddy.Provisioner); ok {
- err := prov.Provision(ctx)
- if err != nil {
- return err
- }
+ // if there was a base policy to begin with, we already
+ // filled in its issuer's defaults; if there wasn't, we
+ // stil need to do that
+ if !foundBasePolicy {
+ err := app.fillInACMEIssuer(baseACMEIssuer)
+ if err != nil {
+ return err
}
- newPolicy.Issuer = issuer
- newPolicy.Subjects = hosts
+ }
- return app.tlsApp.AddAutomationPolicy(newPolicy)
+ // never overwrite any other issuer that might already be configured
+ if basePolicy.Issuer == nil {
+ basePolicy.Issuer = baseACMEIssuer
}
- if len(publicNames) > 0 {
- var acmeIssuer *caddytls.ACMEIssuer
- // if it has an ACME issuer, maybe we can just use that
- // TODO: we might need a deep copy here, like a Clone() method on ACMEIssuer...
- acmeIssuer, _ = basePolicy.Issuer.(*caddytls.ACMEIssuer)
- if acmeIssuer == nil {
- acmeIssuer = new(caddytls.ACMEIssuer)
- }
- if app.HTTPPort > 0 || app.HTTPSPort > 0 {
- if acmeIssuer.Challenges == nil {
- acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
- }
- }
- if app.HTTPPort > 0 {
- if acmeIssuer.Challenges.HTTP == nil {
- acmeIssuer.Challenges.HTTP = new(caddytls.HTTPChallengeConfig)
- }
- // don't overwrite existing explicit config
- if acmeIssuer.Challenges.HTTP.AlternatePort == 0 {
- acmeIssuer.Challenges.HTTP.AlternatePort = app.HTTPPort
- }
- }
- if app.HTTPSPort > 0 {
- if acmeIssuer.Challenges.TLSALPN == nil {
- acmeIssuer.Challenges.TLSALPN = new(caddytls.TLSALPNChallengeConfig)
- }
- // don't overwrite existing explicit config
- if acmeIssuer.Challenges.TLSALPN.AlternatePort == 0 {
- acmeIssuer.Challenges.TLSALPN.AlternatePort = app.HTTPSPort
- }
- }
- if err := addPolicy(acmeIssuer, publicNames); err != nil {
+ if !foundBasePolicy {
+ // there was no base policy to begin with, so add
+ // our base/catch-all policy - this will serve the
+ // public-looking names as well as any other names
+ // that don't match any other policy
+ app.tlsApp.AddAutomationPolicy(basePolicy)
+ } else {
+ // a base policy already existed; we might have
+ // changed it, so re-provision it
+ err := basePolicy.Provision(app.tlsApp)
+ if err != nil {
return err
}
}
+ // public names will be taken care of by the base (catch-all)
+ // policy, which we've ensured exists if not already specified;
+ // internal names, however, need to be handled by an internal
+ // issuer, which we need to make a new policy for, scoped to
+ // just those names (yes, this logic is a bit asymmetric, but
+ // it works, because our assumed/natural default issuer is an
+ // ACME issuer)
if len(internalNames) > 0 {
internalIssuer := new(caddytls.InternalIssuer)
- if err := addPolicy(internalIssuer, internalNames); err != nil {
+
+ // shallow-copy the base policy; we want to inherit
+ // from it, not replace it... this takes two lines to
+ // overrule compiler optimizations
+ policyCopy := *basePolicy
+ newPolicy := &policyCopy
+
+ // very important to provision the issuer, since we
+ // are bypassing the JSON-unmarshaling step
+ if err := internalIssuer.Provision(ctx); err != nil {
+ return err
+ }
+
+ // this policy should apply only to the given names
+ // and should use our issuer -- yes, this overrides
+ // any issuer that may have been set in the base
+ // policy, but we do this because these names do not
+ // already have a policy associated with them, which
+ // is easy to do; consider the case of a Caddyfile
+ // that has only "localhost" as a name, but sets the
+ // default/global ACME CA to the Let's Encrypt staging
+ // endpoint... they probably don't intend to change the
+ // fundamental set of names that setting applies to,
+ // rather they just want to change the CA for the set
+ // of names that would normally use the production API;
+ // anyway, that gets into the weeds a bit...
+ newPolicy.Subjects = internalNames
+ newPolicy.Issuer = internalIssuer
+
+ err := app.tlsApp.AddAutomationPolicy(newPolicy)
+ if err != nil {
return err
}
}
+ // we just changed a lot of stuff, so double-check that it's all good
err := app.tlsApp.Validate()
if err != nil {
return err
@@ -507,6 +538,51 @@ func (app *App) createAutomationPolicies(ctx caddy.Context, publicNames, interna
return nil
}
+// fillInACMEIssuer fills in default values into acmeIssuer that
+// are defined in app; these values at time of writing are just
+// app.HTTPPort and app.HTTPSPort, which are used by ACMEIssuer.
+// Sure, we could just use the global/CertMagic defaults, but if
+// a user has configured those ports in the HTTP app, it makes
+// sense to use them in the TLS app too, even if they forgot (or
+// were too lazy, like me) to set it in each automation policy
+// that uses it -- this just makes things a little less tedious
+// for the user, so they don't have to repeat those ports in
+// potentially many places. This function never steps on existing
+// config values. If any changes are made, acmeIssuer is
+// reprovisioned. acmeIssuer must not be nil.
+func (app *App) fillInACMEIssuer(acmeIssuer *caddytls.ACMEIssuer) error {
+ var anyChanges bool
+ if app.HTTPPort > 0 || app.HTTPSPort > 0 {
+ if acmeIssuer.Challenges == nil {
+ acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
+ }
+ }
+ if app.HTTPPort > 0 {
+ if acmeIssuer.Challenges.HTTP == nil {
+ acmeIssuer.Challenges.HTTP = new(caddytls.HTTPChallengeConfig)
+ }
+ // don't overwrite existing explicit config
+ if acmeIssuer.Challenges.HTTP.AlternatePort == 0 {
+ acmeIssuer.Challenges.HTTP.AlternatePort = app.HTTPPort
+ anyChanges = true
+ }
+ }
+ if app.HTTPSPort > 0 {
+ if acmeIssuer.Challenges.TLSALPN == nil {
+ acmeIssuer.Challenges.TLSALPN = new(caddytls.TLSALPNChallengeConfig)
+ }
+ // don't overwrite existing explicit config
+ if acmeIssuer.Challenges.TLSALPN.AlternatePort == 0 {
+ acmeIssuer.Challenges.TLSALPN.AlternatePort = app.HTTPSPort
+ anyChanges = true
+ }
+ }
+ if anyChanges {
+ return acmeIssuer.Provision(app.ctx)
+ }
+ return nil
+}
+
// automaticHTTPSPhase2 begins certificate management for
// all names in the qualifying domain set for each server.
// This phase must occur after provisioning and at the end