diff --git a/modules/caddyhttp/app.go b/modules/caddyhttp/app.go index 74f1466be..7b7b02e5d 100644 --- a/modules/caddyhttp/app.go +++ b/modules/caddyhttp/app.go @@ -23,6 +23,7 @@ import ( "maps" "net" "net/http" + "slices" "strconv" "sync" "time" @@ -235,7 +236,7 @@ func (app *App) Provision(ctx caddy.Context) error { // if no protocols configured explicitly, enable all except h2c if len(srv.Protocols) == 0 { - srv.Protocols = []string{"h1", "h2", "h3"} + srv.Protocols = slices.Clone(srv.protocolsWithDefaults()) } srvProtocolsUnique := map[string]struct{}{} diff --git a/modules/caddyhttp/autohttps.go b/modules/caddyhttp/autohttps.go index 05ef08e1a..64240d9f4 100644 --- a/modules/caddyhttp/autohttps.go +++ b/modules/caddyhttp/autohttps.go @@ -173,7 +173,7 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er for d := range serverDomainSet { echDomains = append(echDomains, d) } - app.tlsApp.RegisterServerNamesWithALPN(echDomains, httpsRRALPNs(srv)) + app.tlsApp.RegisterServerNames(echDomains, httpsRRALPNs(srv)) // nothing more to do here if there are no domains that qualify for // automatic HTTPS and there are no explicit TLS connection policies: @@ -553,10 +553,7 @@ func (app *App) makeRedirRoute(redirToPort uint, matcherSet MatcherSet) Route { func httpsRRALPNs(srv *Server) []string { // Automatic HTTPS runs before server provisioning fills in the default // protocols, so derive the effective set directly from the raw config here. - serverProtocols := srv.Protocols - if len(serverProtocols) == 0 { - serverProtocols = []string{"h1", "h2", "h3"} - } + serverProtocols := srv.protocolsWithDefaults() protocols := make(map[string]struct{}, len(serverProtocols)) if srv.ListenProtocols == nil { @@ -583,17 +580,17 @@ func httpsRRALPNs(srv *Server) []string { } } - alpn := make([]string, 0, 3) + alpn := make(map[string]struct{}, 3) if _, ok := protocols["h3"]; ok { - alpn = append(alpn, "h3") + alpn["h3"] = struct{}{} } if _, ok := protocols["h2"]; ok { - alpn = append(alpn, "h2") + alpn["h2"] = struct{}{} } if _, ok := protocols["h1"]; ok { - alpn = append(alpn, "http/1.1") + alpn["http/1.1"] = struct{}{} } - return alpn + return caddytls.OrderedHTTPSRRALPN(alpn) } // createAutomationPolicies ensures that automated certificates for this diff --git a/modules/caddyhttp/server.go b/modules/caddyhttp/server.go index 41a8e55b0..ec9fe18aa 100644 --- a/modules/caddyhttp/server.go +++ b/modules/caddyhttp/server.go @@ -301,6 +301,8 @@ type Server struct { onStopFuncs []func(context.Context) error // TODO: Experimental (Nov. 2023) } +var defaultProtocols = []string{"h1", "h2", "h3"} + var ( ServerHeader = "Caddy" serverHeader = []string{ServerHeader} @@ -900,13 +902,14 @@ func (s *Server) logRequest( // protocol returns true if the protocol proto is configured/enabled. func (s *Server) protocol(proto string) bool { if s.ListenProtocols == nil { - if slices.Contains(s.Protocols, proto) { + if slices.Contains(s.protocolsWithDefaults(), proto) { return true } } else { + serverProtocols := s.protocolsWithDefaults() for _, lnProtocols := range s.ListenProtocols { for _, lnProtocol := range lnProtocols { - if lnProtocol == "" && slices.Contains(s.Protocols, proto) || lnProtocol == proto { + if lnProtocol == "" && slices.Contains(serverProtocols, proto) || lnProtocol == proto { return true } } @@ -916,6 +919,13 @@ func (s *Server) protocol(proto string) bool { return false } +func (s *Server) protocolsWithDefaults() []string { + if len(s.Protocols) == 0 { + return defaultProtocols + } + return s.Protocols +} + // Listeners returns the server's listeners. These are active listeners, // so calling Accept() or Close() on them will probably break things. // They are made available here for read-only purposes (e.g. Addr()) diff --git a/modules/caddytls/connpolicy.go b/modules/caddytls/connpolicy.go index c9258da48..9597af359 100644 --- a/modules/caddytls/connpolicy.go +++ b/modules/caddytls/connpolicy.go @@ -153,9 +153,9 @@ func (cp ConnectionPolicies) TLSConfig(ctx caddy.Context) *tls.Config { // in its config (remember, TLS connection policies are used by *other* apps to // run TLS servers) -- we skip names with placeholders if tlsApp.EncryptedClientHello.Publication == nil { - var echNames []string repl := caddy.NewReplacer() for _, p := range cp { + var echNames []string for _, m := range p.matchers { if sni, ok := m.(MatchServerName); ok { for _, name := range sni { @@ -164,8 +164,8 @@ func (cp ConnectionPolicies) TLSConfig(ctx caddy.Context) *tls.Config { } } } + tlsApp.RegisterServerNames(echNames, p.ALPN) } - tlsApp.RegisterServerNames(echNames) } tlsCfg.GetEncryptedClientHelloKeys = func(chi *tls.ClientHelloInfo) ([]tls.EncryptedClientHelloKey, error) { diff --git a/modules/caddytls/ech_dns_test.go b/modules/caddytls/ech_dns_test.go index 6f555acc9..7c337366e 100644 --- a/modules/caddytls/ech_dns_test.go +++ b/modules/caddytls/ech_dns_test.go @@ -11,17 +11,16 @@ import ( func TestRegisterServerNamesWithALPN(t *testing.T) { tlsApp := &TLS{ - serverNames: make(map[string]struct{}), - serverNameALPN: make(map[string]map[string]struct{}), - serverNamesMu: new(sync.Mutex), + serverNames: make(map[string]serverNameRegistration), + serverNamesMu: new(sync.Mutex), } - tlsApp.RegisterServerNamesWithALPN([]string{ + tlsApp.RegisterServerNames([]string{ "Example.com:443", "example.com", "127.0.0.1:443", }, []string{"h2", "http/1.1"}) - tlsApp.RegisterServerNamesWithALPN([]string{"EXAMPLE.COM"}, []string{"h3"}) + tlsApp.RegisterServerNames([]string{"EXAMPLE.COM"}, []string{"h3"}) got := tlsApp.alpnValuesForServerNames([]string{"example.com:443", "127.0.0.1:443"}) want := map[string][]string{ diff --git a/modules/caddytls/tls.go b/modules/caddytls/tls.go index 6a2f8f587..e5f6e6fc0 100644 --- a/modules/caddytls/tls.go +++ b/modules/caddytls/tls.go @@ -141,9 +141,8 @@ type TLS struct { logger *zap.Logger events *caddyevents.App - serverNames map[string]struct{} - serverNameALPN map[string]map[string]struct{} - serverNamesMu *sync.Mutex + serverNames map[string]serverNameRegistration + serverNamesMu *sync.Mutex // set of subjects with managed certificates, // and hashes of manually-loaded certificates @@ -170,8 +169,7 @@ func (t *TLS) Provision(ctx caddy.Context) error { t.logger = ctx.Logger() repl := caddy.NewReplacer() t.managing, t.loaded = make(map[string]string), make(map[string]string) - t.serverNames = make(map[string]struct{}) - t.serverNameALPN = make(map[string]map[string]struct{}) + t.serverNames = make(map[string]serverNameRegistration) t.serverNamesMu = new(sync.Mutex) // set up default DNS module, if any, and make sure it implements all the @@ -651,24 +649,16 @@ func (t *TLS) managingWildcardFor(subj string, otherSubjsToManage map[string]str return false } -// RegisterServerNames registers the provided DNS names with the TLS app. -// This is currently used to auto-publish Encrypted ClientHello (ECH) -// configurations, if enabled. Use of this function by apps using the TLS -// app removes the need for the user to redundantly specify domain names -// in their configuration. This function separates hostname and port -// (keeping only the hotsname) and filters IP addresses, which can't be -// used with ECH. +// RegisterServerNames registers the provided DNS names with the TLS app and +// associates them with the given HTTPS RR ALPN values, if any. This is +// currently used to auto-publish Encrypted ClientHello (ECH) configurations, +// if enabled. Use of this function by apps using the TLS app removes the need +// for the user to redundantly specify domain names in their configuration. +// This function separates hostname and port, keeping only the hostname, and +// filters IP addresses which can't be used with ECH. // // EXPERIMENTAL: This function and its semantics/behavior are subject to change. -func (t *TLS) RegisterServerNames(dnsNames []string) { - t.RegisterServerNamesWithALPN(dnsNames, nil) -} - -// RegisterServerNamesWithALPN registers the provided DNS names with the TLS app -// and associates them with the given HTTPS RR ALPN values, if any. -// -// EXPERIMENTAL: This function and its semantics/behavior are subject to change. -func (t *TLS) RegisterServerNamesWithALPN(dnsNames []string, alpnValues []string) { +func (t *TLS) RegisterServerNames(dnsNames, alpnValues []string) { t.serverNamesMu.Lock() defer t.serverNamesMu.Unlock() @@ -681,21 +671,24 @@ func (t *TLS) RegisterServerNamesWithALPN(dnsNames []string, alpnValues []string if host == "" || certmagic.SubjectIsIP(host) { continue } - t.serverNames[host] = struct{}{} + + registration := t.serverNames[host] if len(alpnValues) == 0 { + t.serverNames[host] = registration continue } - if t.serverNameALPN[host] == nil { - t.serverNameALPN[host] = make(map[string]struct{}, len(alpnValues)) + if registration.alpnValues == nil { + registration.alpnValues = make(map[string]struct{}, len(alpnValues)) } for _, alpn := range alpnValues { if alpn == "" { continue } - t.serverNameALPN[host][alpn] = struct{}{} + registration.alpnValues[alpn] = struct{}{} } + t.serverNames[host] = registration } } @@ -714,22 +707,23 @@ func (t *TLS) alpnValuesForServerNames(dnsNames []string) map[string][]string { continue } - alpnSet := t.serverNameALPN[host] - if len(alpnSet) == 0 { + registration, ok := t.serverNames[host] + if !ok || len(registration.alpnValues) == 0 { continue } - result[host] = orderedHTTPSRRALPN(alpnSet) + result[host] = OrderedHTTPSRRALPN(registration.alpnValues) } return result } -func orderedHTTPSRRALPN(alpnSet map[string]struct{}) []string { +// OrderedHTTPSRRALPN returns the HTTPS RR ALPN values in preferred order. +func OrderedHTTPSRRALPN(alpnSet map[string]struct{}) []string { if len(alpnSet) == 0 { return nil } - knownOrder := []string{"h3", "h2", "http/1.1"} + knownOrder := append([]string{"h3"}, defaultALPN...) ordered := make([]string, 0, len(alpnSet)) seen := make(map[string]struct{}, len(alpnSet)) @@ -756,6 +750,10 @@ func orderedHTTPSRRALPN(alpnSet map[string]struct{}) []string { return append(ordered, remaining...) } +type serverNameRegistration struct { + alpnValues map[string]struct{} +} + // HandleHTTPChallenge ensures that the ACME HTTP challenge or ZeroSSL HTTP // validation request is handled for the certificate named by r.Host, if it // is an HTTP challenge request. It requires that the automation policy for