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
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