@@ -407,98 +407,129 @@ redirServersLoop:
407
407
// base for the new ones (this is important for preserving behavior the
408
408
// user intends to be "defaults").
409
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
418
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
430
434
}
431
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
+ }
432
442
}
443
+
433
444
if basePolicy == nil {
445
+ // no base policy found, we will make one!
434
446
basePolicy = new (caddytls.AutomationPolicy )
435
447
}
436
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
+ }
444
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
452
465
}
453
- newPolicy .Issuer = issuer
454
- newPolicy .Subjects = hosts
466
+ }
455
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
457
471
}
458
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 {
491
484
return err
492
485
}
493
486
}
494
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
495
if len (internalNames ) > 0 {
496
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 {
498
528
return err
499
529
}
500
530
}
501
531
532
+ // we just changed a lot of stuff, so double-check that it's all good
502
533
err := app .tlsApp .Validate ()
503
534
if err != nil {
504
535
return err
@@ -507,6 +538,51 @@ func (app *App) createAutomationPolicies(ctx caddy.Context, publicNames, interna
507
538
return nil
508
539
}
509
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
+
510
586
// automaticHTTPSPhase2 begins certificate management for
511
587
// all names in the qualifying domain set for each server.
512
588
// This phase must occur after provisioning and at the end
0 commit comments