tls: centralise HTTPS RR ALPN defaults and registration

Reuse shared protocol defaults instead of repeating the default HTTP protocol list, unify server name registration to carry ALPN in one experimental API and reuse the TLS default ALPN ordering for HTTPS RR publication
This commit is contained in:
Zen Dodd 2026-04-17 12:25:29 +10:00
parent aacb430d87
commit 904f9fddcc
No known key found for this signature in database
GPG Key ID: 6909546B2C52EC2D
6 changed files with 55 additions and 50 deletions

View File

@ -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{}{}

View File

@ -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

View File

@ -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())

View File

@ -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) {

View File

@ -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{

View File

@ -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