Skip to content

Commit d692d50

Browse files
committed
tls/http: Fix auto-HTTPS logic w/rt default issuers (fixes #3164)
The comments in the code should explain the new logic thoroughly. The basic problem for the issue was that we were overriding a catch-all automation policy's explicitly-configured issuer with our own, for names that we thought looked like public names. In other words, one could configure an internal issuer for all names, but then our auto HTTPS would create a new policy for public-looking names that uses the default ACME issuer, because we assume public<==>ACME and nonpublic<==>Internal, but that is not always the case. The new logic still assumes nonpublic<==>Internal (on catch-all policies only), but no longer assumes that public-looking names always use an ACME issuer. Also fix a bug where HTTPPort and HTTPSPort from the HTTP app weren't being carried through to ACME issuers properly. It required a bit of refactoring.
1 parent 3c1def2 commit d692d50

File tree

4 files changed

+161
-80
lines changed

4 files changed

+161
-80
lines changed

context.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -377,9 +377,14 @@ func (ctx Context) loadModuleInline(moduleNameKey, moduleScope string, raw json.
377377
return val, nil
378378
}
379379

380-
// App returns the configured app named name. If no app with
381-
// that name is currently configured, a new empty one will be
382-
// instantiated. (The app module must still be registered.)
380+
// App returns the configured app named name. If that app has
381+
// not yet been loaded and provisioned, it will be immediately
382+
// loaded and provisioned. If no app with that name is
383+
// configured, a new empty one will be instantiated instead.
384+
// (The app module must still be registered.) This must not be
385+
// called during the Provision/Validate phase to reference a
386+
// module's own host app (since the parent app module is still
387+
// in the process of being provisioned, it is not yet ready).
383388
func (ctx Context) App(name string) (interface{}, error) {
384389
if app, ok := ctx.cfg.apps[name]; ok {
385390
return app, nil

modules/caddyhttp/autohttps.go

Lines changed: 145 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -407,98 +407,129 @@ redirServersLoop:
407407
// base for the new ones (this is important for preserving behavior the
408408
// user intends to be "defaults").
409409
func (app *App) createAutomationPolicies(ctx caddy.Context, publicNames, internalNames []string) error {
410-
// nothing to do if no names to manage certs for
411-
if len(publicNames) == 0 && len(internalNames) == 0 {
412-
return nil
413-
}
414-
415-
// start by finding a base policy that the user may have defined
416-
// which should, in theory, apply to any policies derived from it;
417-
// typically this would be a "catch-all" policy with no host filter
410+
// before we begin, loop through the existing automation policies
411+
// and, for any ACMEIssuers we find, make sure they're filled in
412+
// with default values that might be specified in our HTTP app; also
413+
// look for a base (or "catch-all" / default) automation policy,
414+
// which we're going to essentially require, to make sure it has
415+
// those defaults, too
418416
var basePolicy *caddytls.AutomationPolicy
419-
if app.tlsApp.Automation != nil {
420-
for _, ap := range app.tlsApp.Automation.Policies {
421-
// if an existing policy matches (specifically, a catch-all policy),
422-
// we should inherit from it, because that is what the user expects;
423-
// this is very common for user setting a default issuer, with a
424-
// custom CA endpoint, for example - whichever one we choose must
425-
// have a host list that is a superset of the policy we make...
426-
// the policy with no host filter is guaranteed to qualify
427-
if len(ap.Subjects) == 0 {
428-
basePolicy = ap
429-
break
417+
var foundBasePolicy bool
418+
if app.tlsApp.Automation == nil {
419+
// we will expect this to not be nil from now on
420+
app.tlsApp.Automation = new(caddytls.AutomationConfig)
421+
}
422+
for _, ap := range app.tlsApp.Automation.Policies {
423+
// set up default issuer -- honestly, this is only
424+
// really necessary because the HTTP app is opinionated
425+
// and has settings which could be inferred as new
426+
// defaults for the ACMEIssuer in the TLS app
427+
if ap.Issuer == nil {
428+
ap.Issuer = new(caddytls.ACMEIssuer)
429+
}
430+
if acmeIssuer, ok := ap.Issuer.(*caddytls.ACMEIssuer); ok {
431+
err := app.fillInACMEIssuer(acmeIssuer)
432+
if err != nil {
433+
return err
430434
}
431435
}
436+
437+
// while we're here, is this the catch-all/base policy?
438+
if !foundBasePolicy && len(ap.Subjects) == 0 {
439+
basePolicy = ap
440+
foundBasePolicy = true
441+
}
432442
}
443+
433444
if basePolicy == nil {
445+
// no base policy found, we will make one!
434446
basePolicy = new(caddytls.AutomationPolicy)
435447
}
436448

437-
// addPolicy adds an automation policy that uses issuer for hosts.
438-
addPolicy := func(issuer certmagic.Issuer, hosts []string) error {
439-
// shallow-copy the matching policy; we want to inherit
440-
// from it, not replace it... this takes two lines to
441-
// overrule compiler optimizations
442-
policyCopy := *basePolicy
443-
newPolicy := &policyCopy
449+
// if the basePolicy has an existing ACMEIssuer, let's
450+
// use it, otherwise we'll make one
451+
baseACMEIssuer, _ := basePolicy.Issuer.(*caddytls.ACMEIssuer)
452+
if baseACMEIssuer == nil {
453+
// note that this happens if basePolicy.Issuer is nil
454+
// OR if it is not nil but is not an ACMEIssuer
455+
baseACMEIssuer = new(caddytls.ACMEIssuer)
456+
}
444457

445-
// very important to provision it, since we are
446-
// bypassing the JSON-unmarshaling step
447-
if prov, ok := issuer.(caddy.Provisioner); ok {
448-
err := prov.Provision(ctx)
449-
if err != nil {
450-
return err
451-
}
458+
// if there was a base policy to begin with, we already
459+
// filled in its issuer's defaults; if there wasn't, we
460+
// stil need to do that
461+
if !foundBasePolicy {
462+
err := app.fillInACMEIssuer(baseACMEIssuer)
463+
if err != nil {
464+
return err
452465
}
453-
newPolicy.Issuer = issuer
454-
newPolicy.Subjects = hosts
466+
}
455467

456-
return app.tlsApp.AddAutomationPolicy(newPolicy)
468+
// never overwrite any other issuer that might already be configured
469+
if basePolicy.Issuer == nil {
470+
basePolicy.Issuer = baseACMEIssuer
457471
}
458472

459-
if len(publicNames) > 0 {
460-
var acmeIssuer *caddytls.ACMEIssuer
461-
// if it has an ACME issuer, maybe we can just use that
462-
// TODO: we might need a deep copy here, like a Clone() method on ACMEIssuer...
463-
acmeIssuer, _ = basePolicy.Issuer.(*caddytls.ACMEIssuer)
464-
if acmeIssuer == nil {
465-
acmeIssuer = new(caddytls.ACMEIssuer)
466-
}
467-
if app.HTTPPort > 0 || app.HTTPSPort > 0 {
468-
if acmeIssuer.Challenges == nil {
469-
acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
470-
}
471-
}
472-
if app.HTTPPort > 0 {
473-
if acmeIssuer.Challenges.HTTP == nil {
474-
acmeIssuer.Challenges.HTTP = new(caddytls.HTTPChallengeConfig)
475-
}
476-
// don't overwrite existing explicit config
477-
if acmeIssuer.Challenges.HTTP.AlternatePort == 0 {
478-
acmeIssuer.Challenges.HTTP.AlternatePort = app.HTTPPort
479-
}
480-
}
481-
if app.HTTPSPort > 0 {
482-
if acmeIssuer.Challenges.TLSALPN == nil {
483-
acmeIssuer.Challenges.TLSALPN = new(caddytls.TLSALPNChallengeConfig)
484-
}
485-
// don't overwrite existing explicit config
486-
if acmeIssuer.Challenges.TLSALPN.AlternatePort == 0 {
487-
acmeIssuer.Challenges.TLSALPN.AlternatePort = app.HTTPSPort
488-
}
489-
}
490-
if err := addPolicy(acmeIssuer, publicNames); err != nil {
473+
if !foundBasePolicy {
474+
// there was no base policy to begin with, so add
475+
// our base/catch-all policy - this will serve the
476+
// public-looking names as well as any other names
477+
// that don't match any other policy
478+
app.tlsApp.AddAutomationPolicy(basePolicy)
479+
} else {
480+
// a base policy already existed; we might have
481+
// changed it, so re-provision it
482+
err := basePolicy.Provision(app.tlsApp)
483+
if err != nil {
491484
return err
492485
}
493486
}
494487

488+
// public names will be taken care of by the base (catch-all)
489+
// policy, which we've ensured exists if not already specified;
490+
// internal names, however, need to be handled by an internal
491+
// issuer, which we need to make a new policy for, scoped to
492+
// just those names (yes, this logic is a bit asymmetric, but
493+
// it works, because our assumed/natural default issuer is an
494+
// ACME issuer)
495495
if len(internalNames) > 0 {
496496
internalIssuer := new(caddytls.InternalIssuer)
497-
if err := addPolicy(internalIssuer, internalNames); err != nil {
497+
498+
// shallow-copy the base policy; we want to inherit
499+
// from it, not replace it... this takes two lines to
500+
// overrule compiler optimizations
501+
policyCopy := *basePolicy
502+
newPolicy := &policyCopy
503+
504+
// very important to provision the issuer, since we
505+
// are bypassing the JSON-unmarshaling step
506+
if err := internalIssuer.Provision(ctx); err != nil {
507+
return err
508+
}
509+
510+
// this policy should apply only to the given names
511+
// and should use our issuer -- yes, this overrides
512+
// any issuer that may have been set in the base
513+
// policy, but we do this because these names do not
514+
// already have a policy associated with them, which
515+
// is easy to do; consider the case of a Caddyfile
516+
// that has only "localhost" as a name, but sets the
517+
// default/global ACME CA to the Let's Encrypt staging
518+
// endpoint... they probably don't intend to change the
519+
// fundamental set of names that setting applies to,
520+
// rather they just want to change the CA for the set
521+
// of names that would normally use the production API;
522+
// anyway, that gets into the weeds a bit...
523+
newPolicy.Subjects = internalNames
524+
newPolicy.Issuer = internalIssuer
525+
526+
err := app.tlsApp.AddAutomationPolicy(newPolicy)
527+
if err != nil {
498528
return err
499529
}
500530
}
501531

532+
// we just changed a lot of stuff, so double-check that it's all good
502533
err := app.tlsApp.Validate()
503534
if err != nil {
504535
return err
@@ -507,6 +538,51 @@ func (app *App) createAutomationPolicies(ctx caddy.Context, publicNames, interna
507538
return nil
508539
}
509540

541+
// fillInACMEIssuer fills in default values into acmeIssuer that
542+
// are defined in app; these values at time of writing are just
543+
// app.HTTPPort and app.HTTPSPort, which are used by ACMEIssuer.
544+
// Sure, we could just use the global/CertMagic defaults, but if
545+
// a user has configured those ports in the HTTP app, it makes
546+
// sense to use them in the TLS app too, even if they forgot (or
547+
// were too lazy, like me) to set it in each automation policy
548+
// that uses it -- this just makes things a little less tedious
549+
// for the user, so they don't have to repeat those ports in
550+
// potentially many places. This function never steps on existing
551+
// config values. If any changes are made, acmeIssuer is
552+
// reprovisioned. acmeIssuer must not be nil.
553+
func (app *App) fillInACMEIssuer(acmeIssuer *caddytls.ACMEIssuer) error {
554+
var anyChanges bool
555+
if app.HTTPPort > 0 || app.HTTPSPort > 0 {
556+
if acmeIssuer.Challenges == nil {
557+
acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
558+
}
559+
}
560+
if app.HTTPPort > 0 {
561+
if acmeIssuer.Challenges.HTTP == nil {
562+
acmeIssuer.Challenges.HTTP = new(caddytls.HTTPChallengeConfig)
563+
}
564+
// don't overwrite existing explicit config
565+
if acmeIssuer.Challenges.HTTP.AlternatePort == 0 {
566+
acmeIssuer.Challenges.HTTP.AlternatePort = app.HTTPPort
567+
anyChanges = true
568+
}
569+
}
570+
if app.HTTPSPort > 0 {
571+
if acmeIssuer.Challenges.TLSALPN == nil {
572+
acmeIssuer.Challenges.TLSALPN = new(caddytls.TLSALPNChallengeConfig)
573+
}
574+
// don't overwrite existing explicit config
575+
if acmeIssuer.Challenges.TLSALPN.AlternatePort == 0 {
576+
acmeIssuer.Challenges.TLSALPN.AlternatePort = app.HTTPSPort
577+
anyChanges = true
578+
}
579+
}
580+
if anyChanges {
581+
return acmeIssuer.Provision(app.ctx)
582+
}
583+
return nil
584+
}
585+
510586
// automaticHTTPSPhase2 begins certificate management for
511587
// all names in the qualifying domain set for each server.
512588
// This phase must occur after provisioning and at the end

modules/caddytls/automation.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,8 @@ type AutomationPolicy struct {
115115
storage certmagic.Storage
116116
}
117117

118-
// provision converts ap into a CertMagic config.
119-
func (ap *AutomationPolicy) provision(tlsApp *TLS) error {
118+
// Provision sets up ap and builds its underlying CertMagic config.
119+
func (ap *AutomationPolicy) Provision(tlsApp *TLS) error {
120120
// policy-specific storage implementation
121121
if ap.StorageRaw != nil {
122122
val, err := tlsApp.ctx.LoadModule(ap, "StorageRaw")
@@ -157,8 +157,8 @@ func (ap *AutomationPolicy) provision(tlsApp *TLS) error {
157157
// none the subjects do not qualify for a public certificate,
158158
// set the issuer to internal so that these names can all
159159
// get certificates; critically, we can only do this if an
160-
// issuer is not explictly configured AND if the list of
161-
// subjects is non-empty
160+
// issuer is not explictly configured (IssuerRaw, vs. just
161+
// Issuer) AND if the list of subjects is non-empty
162162
if ap.IssuerRaw == nil && len(ap.Subjects) > 0 {
163163
var anyPublic bool
164164
for _, s := range ap.Subjects {
@@ -174,7 +174,7 @@ func (ap *AutomationPolicy) provision(tlsApp *TLS) error {
174174
}
175175
}
176176

177-
// load and provision the issuer module
177+
// load and provision any explicitly-configured issuer module
178178
if ap.IssuerRaw != nil {
179179
val, err := tlsApp.ctx.LoadModule(ap, "IssuerRaw")
180180
if err != nil {

modules/caddytls/tls.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,12 @@ func (t *TLS) Provision(ctx caddy.Context) error {
9494
t.Automation = new(AutomationConfig)
9595
}
9696
t.Automation.defaultAutomationPolicy = new(AutomationPolicy)
97-
err := t.Automation.defaultAutomationPolicy.provision(t)
97+
err := t.Automation.defaultAutomationPolicy.Provision(t)
9898
if err != nil {
9999
return fmt.Errorf("provisioning default automation policy: %v", err)
100100
}
101101
for i, ap := range t.Automation.Policies {
102-
err := ap.provision(t)
102+
err := ap.Provision(t)
103103
if err != nil {
104104
return fmt.Errorf("provisioning automation policy %d: %v", i, err)
105105
}
@@ -300,7 +300,7 @@ func (t *TLS) AddAutomationPolicy(ap *AutomationPolicy) error {
300300
if t.Automation == nil {
301301
t.Automation = new(AutomationConfig)
302302
}
303-
err := ap.provision(t)
303+
err := ap.Provision(t)
304304
if err != nil {
305305
return err
306306
}

0 commit comments

Comments
 (0)