diff --git a/proxy/internal/acme/manager.go b/proxy/internal/acme/manager.go index c5ae32537..f1463af2f 100644 --- a/proxy/internal/acme/manager.go +++ b/proxy/internal/acme/manager.go @@ -2,8 +2,11 @@ package acme import ( "context" + "crypto/tls" "fmt" + "net" "sync" + "time" log "github.com/sirupsen/logrus" "golang.org/x/crypto/acme" @@ -64,7 +67,6 @@ func (mgr *Manager) hostPolicy(ctx context.Context, domain string) error { func (mgr *Manager) AddDomain(domain, accountID, reverseProxyID string) { mgr.domainsMux.Lock() - defer mgr.domainsMux.Unlock() mgr.domains[domain] = struct { accountID string reverseProxyID string @@ -72,8 +74,44 @@ func (mgr *Manager) AddDomain(domain, accountID, reverseProxyID string) { accountID: accountID, reverseProxyID: reverseProxyID, } + mgr.domainsMux.Unlock() + + go mgr.prefetchCertificate(domain) } +// prefetchCertificate proactively triggers certificate generation for a domain. +func (mgr *Manager) prefetchCertificate(domain string) { + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) + defer cancel() + + hello := &tls.ClientHelloInfo{ + ServerName: domain, + Conn: &dummyConn{ctx: ctx}, + } + + log.Infof("prefetching certificate for domain %q", domain) + _, err := mgr.GetCertificate(hello) + if err != nil { + log.Warnf("prefetch certificate for domain %q: %v", domain, err) + return + } + log.Infof("successfully prefetched certificate for domain %q", domain) +} + +// dummyConn implements net.Conn to provide context for certificate fetching. +type dummyConn struct { + ctx context.Context +} + +func (c *dummyConn) Read(b []byte) (n int, err error) { return 0, nil } +func (c *dummyConn) Write(b []byte) (n int, err error) { return len(b), nil } +func (c *dummyConn) Close() error { return nil } +func (c *dummyConn) LocalAddr() net.Addr { return nil } +func (c *dummyConn) RemoteAddr() net.Addr { return nil } +func (c *dummyConn) SetDeadline(t time.Time) error { return nil } +func (c *dummyConn) SetReadDeadline(t time.Time) error { return nil } +func (c *dummyConn) SetWriteDeadline(t time.Time) error { return nil } + func (mgr *Manager) RemoveDomain(domain string) { mgr.domainsMux.Lock() defer mgr.domainsMux.Unlock()