autohttps: Ensure CertMagic config is recreated after autohttps runs (#7510)

This commit is contained in:
Francis Lavoie
2026-03-03 16:44:06 -05:00
committed by GitHub
parent 2dd3852416
commit d935a6956c
2 changed files with 77 additions and 6 deletions

View File

@@ -614,6 +614,27 @@ func (app *App) createAutomationPolicies(ctx caddy.Context, internalNames, tails
}
}
// Ensure automation policies' CertMagic configs are rebuilt when
// ACME issuer templates may have been modified above (for example,
// alternate ports filled in by the HTTP app). If a policy is already
// provisioned, perform a lightweight rebuild of the CertMagic config
// so issuers receive SetConfig with the updated templates; otherwise
// run a normal Provision to initialize the policy.
for i, ap := range app.tlsApp.Automation.Policies {
// If the policy is already provisioned, rebuild only the CertMagic
// config so issuers get SetConfig with updated templates. Otherwise
// provision the policy normally (which may load modules).
if ap.IsProvisioned() {
if err := ap.RebuildCertMagic(app.tlsApp); err != nil {
return fmt.Errorf("rebuilding certmagic config for automation policy %d: %v", i, err)
}
} else {
if err := ap.Provision(app.tlsApp); err != nil {
return fmt.Errorf("provisioning automation policy %d after auto-HTTPS defaults: %v", i, err)
}
}
}
if basePolicy == nil {
// no base policy found; we will make one
basePolicy = new(caddytls.AutomationPolicy)

View File

@@ -243,22 +243,49 @@ func (ap *AutomationPolicy) Provision(tlsApp *TLS) error {
}
}
// build certmagic.Config and attach it to the policy
storage := ap.storage
if storage == nil {
storage = tlsApp.ctx.Storage()
}
cfg, err := ap.makeCertMagicConfig(tlsApp, issuers, storage)
if err != nil {
return err
}
certCacheMu.RLock()
ap.magic = certmagic.New(certCache, cfg)
certCacheMu.RUnlock()
// give issuers a chance to see the config pointer
for _, issuer := range ap.magic.Issuers {
if annoying, ok := issuer.(ConfigSetter); ok {
annoying.SetConfig(ap.magic)
}
}
return nil
}
// makeCertMagicConfig constructs a certmagic.Config for this policy using the
// provided issuers and storage. It encapsulates common logic shared between
// Provision and RebuildCertMagic so we don't duplicate code.
func (ap *AutomationPolicy) makeCertMagicConfig(tlsApp *TLS, issuers []certmagic.Issuer, storage certmagic.Storage) (certmagic.Config, error) {
// key source
keyType := ap.KeyType
if keyType != "" {
var err error
keyType, err = caddy.NewReplacer().ReplaceOrErr(ap.KeyType, true, true)
if err != nil {
return fmt.Errorf("invalid key type %s: %s", ap.KeyType, err)
return certmagic.Config{}, fmt.Errorf("invalid key type %s: %s", ap.KeyType, err)
}
if _, ok := supportedCertKeyTypes[keyType]; !ok {
return fmt.Errorf("unrecognized key type: %s", keyType)
return certmagic.Config{}, fmt.Errorf("unrecognized key type: %s", keyType)
}
}
keySource := certmagic.StandardKeyGenerator{
KeyType: supportedCertKeyTypes[keyType],
}
storage := ap.storage
if storage == nil {
storage = tlsApp.ctx.Storage()
}
@@ -277,7 +304,7 @@ func (ap *AutomationPolicy) Provision(tlsApp *TLS) error {
if noProtections {
if !ap.hadExplicitManagers {
// no managers, no explicitly-configured permission module, this is a config error
return fmt.Errorf("on-demand TLS cannot be enabled without a permission module to prevent abuse; please refer to documentation for details")
return certmagic.Config{}, fmt.Errorf("on-demand TLS cannot be enabled without a permission module to prevent abuse; please refer to documentation for details")
}
// allow on-demand to be enabled but only for the purpose of the Managers; issuance won't be allowed from Issuers
tlsApp.logger.Warn("on-demand TLS can only get certificates from the configured external manager(s) because no ask endpoint / permission module is specified")
@@ -334,7 +361,7 @@ func (ap *AutomationPolicy) Provision(tlsApp *TLS) error {
}
}
template := certmagic.Config{
cfg := certmagic.Config{
MustStaple: ap.MustStaple,
RenewalWindowRatio: ap.RenewalWindowRatio,
KeySource: keySource,
@@ -349,8 +376,31 @@ func (ap *AutomationPolicy) Provision(tlsApp *TLS) error {
Issuers: issuers,
Logger: tlsApp.logger,
}
return cfg, nil
}
// IsProvisioned reports whether the automation policy has been
// provisioned. A provisioned policy has an initialized CertMagic
// instance (i.e. ap.magic != nil).
func (ap *AutomationPolicy) IsProvisioned() bool { return ap.magic != nil }
// RebuildCertMagic rebuilds the policy's CertMagic configuration from the
// policy's already-populated fields (Issuers, Managers, storage, etc.) and
// replaces the internal CertMagic instance. This is a lightweight
// alternative to calling Provision because it does not re-provision
// modules or re-run module Provision; instead, it constructs a new
// certmagic.Config and calls SetConfig on issuers so they receive updated
// templates (for example, alternate HTTP/TLS ports supplied by the HTTP
// app). RebuildCertMagic should only be called when the policy's required
// fields are already populated.
func (ap *AutomationPolicy) RebuildCertMagic(tlsApp *TLS) error {
cfg, err := ap.makeCertMagicConfig(tlsApp, ap.Issuers, ap.storage)
if err != nil {
return err
}
certCacheMu.RLock()
ap.magic = certmagic.New(certCache, template)
ap.magic = certmagic.New(certCache, cfg)
certCacheMu.RUnlock()
// sometimes issuers may need the parent certmagic.Config in