From 36c9c4478a85abdc20d6dcdb4326e4ea960c7942 Mon Sep 17 00:00:00 2001 From: Vatsal Goel <144617902+VatsalGoel3@users.noreply.github.com> Date: Thu, 9 Jul 2026 09:58:38 +0530 Subject: [PATCH 1/2] caddyfile: reset wildcard client auth for exact hosts --- BOT_PR_REQUEST_NOTICE.txt | 1 + caddyconfig/httpcaddyfile/httptype.go | 81 +++++++++++++++ ...xact_host_resets_client_auth.caddyfiletest | 99 +++++++++++++++++++ 3 files changed, 181 insertions(+) create mode 100644 BOT_PR_REQUEST_NOTICE.txt create mode 100644 caddytest/integration/caddyfile_adapt/tls_wildcard_exact_host_resets_client_auth.caddyfiletest diff --git a/BOT_PR_REQUEST_NOTICE.txt b/BOT_PR_REQUEST_NOTICE.txt new file mode 100644 index 00000000000..6ffd4e61a56 --- /dev/null +++ b/BOT_PR_REQUEST_NOTICE.txt @@ -0,0 +1 @@ +I am just a bot. You are interacting with a bot. diff --git a/caddyconfig/httpcaddyfile/httptype.go b/caddyconfig/httpcaddyfile/httptype.go index 74cca4a4f9f..eaafa987301 100644 --- a/caddyconfig/httpcaddyfile/httptype.go +++ b/caddyconfig/httpcaddyfile/httptype.go @@ -16,6 +16,7 @@ package httpcaddyfile import ( "cmp" + "crypto/tls" "encoding/json" "fmt" "net" @@ -967,6 +968,8 @@ func (st *ServerType) serversFromPairings( return nil, err } + srv.TLSConnPolicies = addExactHostTLSConnPolicies(srv.TLSConnPolicies, p.serverBlocks, httpPort, warnings) + // a catch-all TLS conn policy is necessary to ensure TLS can // be offered to all hostnames of the server; even though only // one policy is needed to enable TLS for the server, that @@ -1063,6 +1066,84 @@ func detectConflictingSchemes(srv *caddyhttp.Server, serverBlocks []serverBlock, return nil } +func addExactHostTLSConnPolicies(cps caddytls.ConnectionPolicies, serverBlocks []serverBlock, httpPort string, warnings *[]caddyconfig.Warning) caddytls.ConnectionPolicies { + exactHosts := make(map[string]struct{}) + for _, sblock := range serverBlocks { + for _, addr := range sblock.parsedKeys { + if addr.Host == "" || strings.Contains(addr.Host, "*") { + continue + } + if addr.Scheme == "http" || addr.Port == httpPort { + continue + } + exactHosts[addr.Host] = struct{}{} + } + } + + if len(exactHosts) == 0 { + return cps + } + + for _, cp := range cps { + for _, name := range sniConnPolicyMatcherNames(cp) { + if !strings.Contains(name, "*") { + delete(exactHosts, name) + } + } + } + + sortedExactHosts := make([]string, 0, len(exactHosts)) + for host := range exactHosts { + sortedExactHosts = append(sortedExactHosts, host) + } + slices.Sort(sortedExactHosts) + + for _, host := range sortedExactHosts { + insertAt := -1 + for i, cp := range cps { + if cp.ClientAuthentication == nil { + continue + } + for _, name := range sniConnPolicyMatcherNames(cp) { + if strings.Contains(name, "*") && (caddytls.MatchServerName{name}).Match(&tls.ClientHelloInfo{ServerName: host}) { + insertAt = i + break + } + } + if insertAt >= 0 { + break + } + } + if insertAt < 0 { + continue + } + + exactHostCP := &caddytls.ConnectionPolicy{ + MatchersRaw: caddy.ModuleMap{ + "sni": caddyconfig.JSON([]string{host}, warnings), + }, + } + cps = append(cps[:insertAt], append(caddytls.ConnectionPolicies{exactHostCP}, cps[insertAt:]...)...) + } + + return cps +} + +func sniConnPolicyMatcherNames(cp *caddytls.ConnectionPolicy) caddytls.MatchServerName { + if cp == nil || len(cp.MatchersRaw) != 1 { + return nil + } + sniMatcherJSON, ok := cp.MatchersRaw["sni"] + if !ok { + return nil + } + var sniMatcher caddytls.MatchServerName + if err := json.Unmarshal(sniMatcherJSON, &sniMatcher); err != nil { + return nil + } + return sniMatcher +} + // consolidateConnPolicies sorts any catch-all policy to the end, removes empty TLS connection // policies, and combines equivalent ones for a cleaner overall output. func consolidateConnPolicies(cps caddytls.ConnectionPolicies) (caddytls.ConnectionPolicies, error) { diff --git a/caddytest/integration/caddyfile_adapt/tls_wildcard_exact_host_resets_client_auth.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_wildcard_exact_host_resets_client_auth.caddyfiletest new file mode 100644 index 00000000000..cf968e81752 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/tls_wildcard_exact_host_resets_client_auth.caddyfiletest @@ -0,0 +1,99 @@ +*.example.com { + tls { + client_auth { + mode require + } + } + + respond "wildcard" +} + +public.example.com { + respond "public" +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "public.example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "public", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + }, + { + "match": [ + { + "host": [ + "*.example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "wildcard", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + } + ], + "tls_connection_policies": [ + { + "match": { + "sni": [ + "public.example.com" + ] + } + }, + { + "match": { + "sni": [ + "*.example.com" + ] + }, + "client_authentication": { + "mode": "require" + } + }, + {} + ] + } + } + } + } +} From 8237978776dfbcaf6ea72832ffdba157ad6a921e Mon Sep 17 00:00:00 2001 From: Vatsal Goel <144617902+VatsalGoel3@users.noreply.github.com> Date: Fri, 10 Jul 2026 11:23:05 +0530 Subject: [PATCH 2/2] remove accidental bot notice --- BOT_PR_REQUEST_NOTICE.txt | 1 - 1 file changed, 1 deletion(-) delete mode 100644 BOT_PR_REQUEST_NOTICE.txt diff --git a/BOT_PR_REQUEST_NOTICE.txt b/BOT_PR_REQUEST_NOTICE.txt deleted file mode 100644 index 6ffd4e61a56..00000000000 --- a/BOT_PR_REQUEST_NOTICE.txt +++ /dev/null @@ -1 +0,0 @@ -I am just a bot. You are interacting with a bot.