@marcel91200 / caddy Mirror · read-only / commits / d692d503
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 14, 05:15 UTC

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.

Matthew Holt committed Mar 20, 2020 at 20:25 UTC d692d503a3d327d54c82bceab48bb1de07bb3c3d
4 files changed +161 -80
context.go
+8 -3
@@ -377,9 +377,14 @@ func (ctx Context) loadModuleInline(moduleNameKey, moduleScope string, raw json.
377 return val, nil
378 }
379
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).
388 func (ctx Context) App(name string) (interface{}, error) {
389 if app, ok := ctx.cfg.apps[name]; ok {
390 return app, nil
modules/caddyhttp/autohttps.go
+145 -69
@@ -407,98 +407,129 @@ redirServersLoop:
407 // base for the new ones (this is important for preserving behavior the
408 // user intends to be "defaults").
409 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
416 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
434 }
435 }
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 + }
442 }
443 +
444 if basePolicy == nil {
445 + // no base policy found, we will make one!
446 basePolicy = new(caddytls.AutomationPolicy)
447 }
448
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 + }
457
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
465 }
453 - newPolicy.Issuer = issuer
454 - newPolicy.Subjects = hosts
466 + }
467
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
471 }
472
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 {
484 return err
485 }
486 }
487
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)
495 if len(internalNames) > 0 {
496 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 {
528 return err
529 }
530 }
531
532 + // we just changed a lot of stuff, so double-check that it's all good
533 err := app.tlsApp.Validate()
534 if err != nil {
535 return err
@@ -507,6 +538,51 @@ func (app *App) createAutomationPolicies(ctx caddy.Context, publicNames, interna
538 return nil
539 }
540
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 +
586 // automaticHTTPSPhase2 begins certificate management for
587 // all names in the qualifying domain set for each server.
588 // This phase must occur after provisioning and at the end
modules/caddytls/automation.go
+5 -5
@@ -115,8 +115,8 @@ type AutomationPolicy struct {
115 storage certmagic.Storage
116 }
117
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 {
120 // policy-specific storage implementation
121 if ap.StorageRaw != nil {
122 val, err := tlsApp.ctx.LoadModule(ap, "StorageRaw")
@@ -157,8 +157,8 @@ func (ap *AutomationPolicy) provision(tlsApp *TLS) error {
157 // none the subjects do not qualify for a public certificate,
158 // set the issuer to internal so that these names can all
159 // 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
162 if ap.IssuerRaw == nil && len(ap.Subjects) > 0 {
163 var anyPublic bool
164 for _, s := range ap.Subjects {
@@ -174,7 +174,7 @@ func (ap *AutomationPolicy) provision(tlsApp *TLS) error {
174 }
175 }
176
177 - // load and provision the issuer module
177 + // load and provision any explicitly-configured issuer module
178 if ap.IssuerRaw != nil {
179 val, err := tlsApp.ctx.LoadModule(ap, "IssuerRaw")
180 if err != nil {
modules/caddytls/tls.go
+3 -3
@@ -94,12 +94,12 @@ func (t *TLS) Provision(ctx caddy.Context) error {
94 t.Automation = new(AutomationConfig)
95 }
96 t.Automation.defaultAutomationPolicy = new(AutomationPolicy)
97 - err := t.Automation.defaultAutomationPolicy.provision(t)
97 + err := t.Automation.defaultAutomationPolicy.Provision(t)
98 if err != nil {
99 return fmt.Errorf("provisioning default automation policy: %v", err)
100 }
101 for i, ap := range t.Automation.Policies {
102 - err := ap.provision(t)
102 + err := ap.Provision(t)
103 if err != nil {
104 return fmt.Errorf("provisioning automation policy %d: %v", i, err)
105 }
@@ -300,7 +300,7 @@ func (t *TLS) AddAutomationPolicy(ap *AutomationPolicy) error {
300 if t.Automation == nil {
301 t.Automation = new(AutomationConfig)
302 }
303 - err := ap.provision(t)
303 + err := ap.Provision(t)
304 if err != nil {
305 return err
306 }