From ca53b82a06079162f6f71c5581aefe911cb9fd73 Mon Sep 17 00:00:00 2001 From: Pascal Date: Mon, 7 Apr 2025 16:37:09 +0200 Subject: [PATCH] caddytls: Regularly reload static certificates --- modules/caddytls/tls.go | 79 ++++++++++++++++++++++++++++++++++------- 1 file changed, 66 insertions(+), 13 deletions(-) diff --git a/modules/caddytls/tls.go b/modules/caddytls/tls.go index 34ffbf62d..366277fd0 100644 --- a/modules/caddytls/tls.go +++ b/modules/caddytls/tls.go @@ -139,6 +139,8 @@ type TLS struct { storageCleanStop chan struct{} logger *zap.Logger events *caddyevents.App + magic *certmagic.Config + unmanagedCertsTicker *time.Ticker serverNames map[string]struct{} serverNamesMu *sync.Mutex @@ -249,7 +251,7 @@ func (t *TLS) Provision(ctx caddy.Context) error { // certificates have been manually loaded, and also so that // commands like validate can be a better test certCacheMu.RLock() - magic := certmagic.New(certCache, certmagic.Config{ + t.magic = certmagic.New(certCache, certmagic.Config{ Storage: ctx.Storage(), Logger: t.logger, OnEvent: t.onEvent, @@ -259,18 +261,14 @@ func (t *TLS) Provision(ctx caddy.Context) error { DisableStorageCheck: t.DisableStorageCheck, }) certCacheMu.RUnlock() - for _, loader := range t.certificateLoaders { - certs, err := loader.LoadCertificates() - if err != nil { - return fmt.Errorf("loading certificates: %v", err) - } - for _, cert := range certs { - hash, err := magic.CacheUnmanagedTLSCertificate(ctx, cert.Certificate, cert.Tags) - if err != nil { - return fmt.Errorf("caching unmanaged certificate: %v", err) - } - t.loaded[hash] = "" - } + + unmanaged, err := t.loadUnmanagedCertificates(ctx) + if err != nil { + return err + } + + if unmanaged > 0 { + t.regularlyReloadUnmanagedCertificates() } // on-demand permission module @@ -813,6 +811,61 @@ func (t *TLS) HasCertificateForSubject(subject string) bool { return false } +func (t *TLS) loadUnmanagedCertificates(ctx caddy.Context) (int, error) { + cached := 0 + for _, loader := range t.certificateLoaders { + certs, err := loader.LoadCertificates() + if err != nil { + return 0, fmt.Errorf("loading certificates: %v", err) + } + for _, cert := range certs { + hash, err := t.magic.CacheUnmanagedTLSCertificate(ctx, cert.Certificate, cert.Tags) + if err != nil { + return 0, fmt.Errorf("caching unmanaged certificate: %v", err) + } + t.loaded[hash] = "" + } + } + return cached, nil +} + +func (t *TLS) regularlyReloadUnmanagedCertificates() { + t.unmanagedCertsTicker = time.NewTicker(2 * time.Hour) + go func() { + defer func() { + if err := recover(); err != nil { + log.Printf("[PANIC] unmanaged certificates reloader: %v\n%s", err, debug.Stack()) + } + }() + t.reloadUnmanagedCertificates() + for { + select { + case <-t.storageCleanStop: + return + case <-t.storageCleanTicker.C: + t.cleanStorageUnits() + } + } + }() +} + +func (t *TLS) reloadUnmanagedCertificates() error { + for _, loader := range t.certificateLoaders { + certs, err := loader.LoadCertificates() + if err != nil { + return fmt.Errorf("loading certificates: %v", err) + } + for _, cert := range certs { + hash, err := t.magic.CacheUnmanagedTLSCertificate(t.ctx, cert.Certificate, cert.Tags) + if err != nil { + return fmt.Errorf("caching unmanaged certificate: %v", err) + } + t.loaded[hash] = "" + } + } + return nil +} + // keepStorageClean starts a goroutine that immediately cleans up all // known storage units if it was not recently done, and then runs the // operation at every tick from t.storageCleanTicker.