From 4701857d7f1b471bf05cd8515f3696423d0a682d Mon Sep 17 00:00:00 2001 From: Mechiel Lukkien Date: Fri, 22 Dec 2023 13:41:00 +0100 Subject: [PATCH] at startup, request missing acme tls certificates more quickly/silently --- autotls/autotls.go | 34 ++++++++++++++++++++++++++++++++++ http/web.go | 8 ++++++++ 2 files changed, 42 insertions(+) diff --git a/autotls/autotls.go b/autotls/autotls.go index 3022c3b..13b2f53 100644 --- a/autotls/autotls.go +++ b/autotls/autotls.go @@ -200,6 +200,40 @@ func Load(name, acmeDir, contactEmail, directoryURL string, eabKeyID string, eab return a, nil } +// CertAvailable checks whether a non-expired ECDSA certificate is available in the +// cache for host. No other checks than expiration are done. +func (m *Manager) CertAvailable(ctx context.Context, log mlog.Log, host dns.Domain) (bool, error) { + ck := host.ASCII // Would be "+rsa" for rsa keys. + data, err := m.Manager.Cache.Get(ctx, ck) + if err != nil && errors.Is(err, autocert.ErrCacheMiss) { + return false, nil + } else if err != nil { + return false, fmt.Errorf("attempt to get certificate from cache: %v", err) + } + + // The cached keycert is of the form: private key, leaf certificate, intermediate certificates... + privb, rem := pem.Decode(data) + if privb == nil { + return false, fmt.Errorf("missing private key in cached keycert file") + } + pubb, _ := pem.Decode(rem) + if pubb == nil { + return false, fmt.Errorf("missing certificate in cached keycert file") + } else if pubb.Type != "CERTIFICATE" { + return false, fmt.Errorf("second pem block is %q, expected CERTIFICATE", pubb.Type) + } + cert, err := x509.ParseCertificate(pubb.Bytes) + if err != nil { + return false, fmt.Errorf("parsing certificate from cached keycert file: %v", err) + } + // We assume the certificate has a matching hostname, and is properly CA-signed. We + // only check the expiration time. + if time.Until(cert.NotBefore) > 0 || time.Since(cert.NotAfter) > 0 { + return false, nil + } + return true, nil +} + // SetAllowedHostnames sets a new list of allowed hostnames for automatic TLS. // After setting the host names, a goroutine is start to check that new host names // are fully served by publicIPs (only if non-empty and there is no unspecified diff --git a/http/web.go b/http/web.go index a69c11b..4361aa6 100644 --- a/http/web.go +++ b/http/web.go @@ -801,6 +801,14 @@ func Serve() { i := 0 for m, hosts := range ensureManagerHosts { for host := range hosts { + // Check if certificate is already available. If so, we don't print as much after a + // restart, and finish more quickly if only a few certificates are missing/old. + if avail, err := m.CertAvailable(mox.Shutdown, pkglog, host); err != nil { + pkglog.Errorx("checking acme certificate availability", err, slog.Any("host", host)) + } else if avail { + continue + } + if i >= 10 { // Just in case someone adds quite some domains to their config. We don't want to // hit any ACME rate limits.