diff --git a/config/envoyconfig/listeners.go b/config/envoyconfig/listeners.go index 96f1f2b7e..a71c87019 100644 --- a/config/envoyconfig/listeners.go +++ b/config/envoyconfig/listeners.go @@ -150,6 +150,8 @@ func (b *Builder) buildMainListener( li.Address = buildAddress(cfg.Options.Addr, 443) li.ListenerFilters = append(li.ListenerFilters, TLSInspectorFilter()) + li.FilterChains = append(li.FilterChains, b.buildACMETLSALPNFilterChain()) + allCertificates, err := getAllCertificates(cfg) if err != nil { return nil, err diff --git a/internal/autocert/manager.go b/internal/autocert/manager.go index 60029d217..4c1a9c319 100644 --- a/internal/autocert/manager.go +++ b/internal/autocert/manager.go @@ -46,12 +46,16 @@ type Manager struct { src config.Source acmeTemplate certmagic.ACMEIssuer - mu sync.RWMutex - config *config.Config - certmagic *certmagic.Config - acmeMgr atomic.Pointer[certmagic.ACMEIssuer] - srv *http.Server + mu sync.RWMutex + config *config.Config + certmagic *certmagic.Config + acmeMgr atomic.Pointer[certmagic.ACMEIssuer] + srv *http.Server + + acmeTLSALPNLock sync.Mutex + acmeTLSALPNPort string acmeTLSALPNListener net.Listener + acmeTLSALPNConfig *tls.Config *ocspCache @@ -155,6 +159,7 @@ func (mgr *Manager) getCertMagicConfig(ctx context.Context, cfg *config.Config) } } acmeMgr := certmagic.NewACMEIssuer(mgr.certmagic, mgr.acmeTemplate) + acmeMgr.DisableHTTPChallenge = !shouldEnableHTTPChallenge(cfg) err = configureCertificateAuthority(acmeMgr, cfg.Options.AutocertOptions) if err != nil { return nil, err @@ -342,20 +347,34 @@ func (mgr *Manager) updateServer(ctx context.Context, cfg *config.Config) { } func (mgr *Manager) updateACMETLSALPNServer(ctx context.Context, cfg *config.Config) { - addr := net.JoinHostPort("127.0.0.1", cfg.ACMETLSALPNPort) + mgr.acmeTLSALPNLock.Lock() + defer mgr.acmeTLSALPNLock.Unlock() + + // store the updated TLS config + mgr.acmeTLSALPNConfig = mgr.certmagic.TLSConfig().Clone() + // if the port hasn't changed, we're done + if mgr.acmeTLSALPNPort == cfg.ACMETLSALPNPort { + return + } + + // store the updated port + mgr.acmeTLSALPNPort = cfg.ACMETLSALPNPort + if mgr.acmeTLSALPNListener != nil { _ = mgr.acmeTLSALPNListener.Close() mgr.acmeTLSALPNListener = nil } - tlsConfig := mgr.certmagic.TLSConfig() - ln, err := tls.Listen("tcp", addr, tlsConfig) + // start the listener + addr := net.JoinHostPort("127.0.0.1", cfg.ACMETLSALPNPort) + ln, err := net.Listen("tcp", addr) if err != nil { log.Error(ctx).Err(err).Msg("failed to run acme tls alpn server") return } mgr.acmeTLSALPNListener = ln + // accept connections go func() { for { conn, err := ln.Accept() @@ -364,6 +383,20 @@ func (mgr *Manager) updateACMETLSALPNServer(ctx context.Context, cfg *config.Con } else if err != nil { continue } + + // initiate the TLS handshake + mgr.acmeTLSALPNLock.Lock() + tlsConfig := mgr.acmeTLSALPNConfig.Clone() + mgr.acmeTLSALPNLock.Unlock() + + orig := tlsConfig.GetCertificate + tlsConfig.GetCertificate = func(chi *tls.ClientHelloInfo) (*tls.Certificate, error) { + log.Info(ctx).Str("server-name", chi.ServerName). + Msg("received request for ACME TLS ALPN certificate") + return orig(chi) + } + + _ = tls.Server(conn, tlsConfig).HandshakeContext(ctx) _ = conn.Close() } }() @@ -469,3 +502,16 @@ func sourceHostnames(cfg *config.Config) []string { return h } + +func shouldEnableHTTPChallenge(cfg *config.Config) bool { + if cfg == nil || cfg.Options == nil { + return false + } + + _, p, err := net.SplitHostPort(cfg.Options.HTTPRedirectAddr) + if err != nil { + return false + } + + return p == "80" +} diff --git a/internal/autocert/manager_test.go b/internal/autocert/manager_test.go index b3b0cd47a..1b94e9e22 100644 --- a/internal/autocert/manager_test.go +++ b/internal/autocert/manager_test.go @@ -630,3 +630,23 @@ func Test_configureTrustedRoots(t *testing.T) { }) } } + +func TestShouldEnableHTTPChallenge(t *testing.T) { + t.Parallel() + + assert.False(t, shouldEnableHTTPChallenge(nil)) + assert.False(t, shouldEnableHTTPChallenge(&config.Config{})) + assert.False(t, shouldEnableHTTPChallenge(&config.Config{Options: &config.Options{}})) + assert.False(t, shouldEnableHTTPChallenge(&config.Config{Options: &config.Options{ + HTTPRedirectAddr: ":8080", + }})) + assert.False(t, shouldEnableHTTPChallenge(&config.Config{Options: &config.Options{ + HTTPRedirectAddr: "127.0.0.1:8080", + }})) + assert.True(t, shouldEnableHTTPChallenge(&config.Config{Options: &config.Options{ + HTTPRedirectAddr: ":80", + }})) + assert.True(t, shouldEnableHTTPChallenge(&config.Config{Options: &config.Options{ + HTTPRedirectAddr: "127.0.0.1:80", + }})) +}