25
"strconv"
26
"strings"
27
28
+ "github.com/caddyserver/certmagic"
29
"go.uber.org/zap"
30
31
"github.com/caddyserver/caddy/v2"
293
if filesystems, ok := options["filesystem"].(caddy.Module); ok {
294
cfg.AppsRaw["caddy.filesystems"] = caddyconfig.JSON(
295
filesystems,
295
- &warnings)
296
+ &warnings,
297
+ )
298
}
299
300
if storageCvtr, ok := options["storage"].(caddy.StorageConverter); ok {
721
})
722
723
var hasCatchAllTLSConnPolicy, addressQualifiesForTLS bool
724
+ var emptyConnPolicies caddytls.ConnectionPolicies
725
autoHTTPSWillAddConnPolicy := srv.AutoHTTPS == nil || !srv.AutoHTTPS.Disabled
726
727
// if needed, the ServerLogConfig is initialized beforehand so
827
if !cp.SettingsEmpty() || mapContains(forceAutomatedNames, hosts) {
828
srv.TLSConnPolicies = append(srv.TLSConnPolicies, cp)
829
hasCatchAllTLSConnPolicy = len(hosts) == 0
830
+ } else if len(hosts) > 0 {
831
+ emptyConnPolicies = append(emptyConnPolicies, cp)
832
}
833
}
834
+ } else if specificHosts := slices.DeleteFunc(
835
+ slices.Clone(hosts),
836
+ func(h string) bool { return h == "" || strings.Contains(h, "*") },
837
+ ); len(specificHosts) > 0 {
838
+ // site blocks with no TLS connection policy of their own may
839
+ // still need an empty policy hoisted above a wildcard policy
840
+ // that requires client authentication, or that requirement
841
+ // would apply to their more specific hostnames too - see the
842
+ // hoisting loop below and issue #7860
843
+ slices.Sort(specificHosts)
844
+ emptyConnPolicies = append(emptyConnPolicies, &caddytls.ConnectionPolicy{
845
+ MatchersRaw: caddy.ModuleMap{
846
+ "sni": caddyconfig.JSON(specificHosts, warnings),
847
+ },
848
+ })
849
}
850
851
for _, addr := range sblock.parsedKeys {
896
listenerWrapper,
897
"wrapper",
898
listenerWrapper.(caddy.Module).CaddyModule().ID.Name(),
879
- warnings)
899
+ warnings,
900
+ )
901
srv.ListenerWrappersRaw = append(srv.ListenerWrappersRaw, jsonListenerWrapper)
902
}
903
911
packetConnWrapper,
912
"wrapper",
913
packetConnWrapper.(caddy.Module).CaddyModule().ID.Name(),
893
- warnings)
914
+ warnings,
915
+ )
916
srv.PacketConnWrappersRaw = append(srv.PacketConnWrappersRaw, jsonPacketConnWrapper)
917
}
918
1012
return nil, err
1013
}
1014
1015
+ // hostnames whose site blocks configure no TLS settings of their own
1016
+ // still need shielding if a policy matching a wildcard hostname would
1017
+ // otherwise impose CLIENT AUTHENTICATION on them. Because connection
1018
+ // policies are first-match, the shield hoisted above the wildcard
1019
+ // policy must be a COPY of that policy with only client_authentication
1020
+ // removed: an empty policy would not just lift the client-auth
1021
+ // requirement but suppress every other setting the wildcard policy
1022
+ // carries (certificate selection, protocol bounds, ALPN, ...), which
1023
+ // the covered hostname does want to inherit - see issue #7860
1024
+ // the wildcard names each client-auth policy matches, decoded once and
1025
+ // index-aligned with srv.TLSConnPolicies; nil for a policy that needs
1026
+ // no client auth or matches no wildcard
1027
+ clientAuthWildcards := make([][]string, len(srv.TLSConnPolicies))
1028
+ for i, cp := range srv.TLSConnPolicies {
1029
+ if cp.ClientAuthentication == nil {
1030
+ continue
1031
+ }
1032
+ names, ok := sniNames(cp, "of existing connection policy ", warnings)
1033
+ if !ok {
1034
+ continue
1035
+ }
1036
+ clientAuthWildcards[i] = slices.DeleteFunc(names, func(name string) bool {
1037
+ return !strings.Contains(name, "*")
1038
+ })
1039
+ }
1040
+
1041
+ // each covering policy needs its OWN shield matching only the
1042
+ // hostnames it covers: the hostnames of a single site block may be
1043
+ // covered by DIFFERENT wildcards, and a shield hoisted above one
1044
+ // policy must not match the hostnames belonging to another, or it
1045
+ // would lift their client-auth requirement too
1046
+ shieldedHosts := make([][]string, len(srv.TLSConnPolicies))
1047
+ for _, ecp := range emptyConnPolicies {
1048
+ hosts, ok := sniNames(ecp, "", warnings)
1049
+ if !ok {
1050
+ continue // no SNI matcher to reason about
1051
+ }
1052
+ for _, host := range hosts {
1053
+ // connection policies are first-match, so only the first
1054
+ // covering policy is ever reached for this hostname
1055
+ for i, wildcards := range clientAuthWildcards {
1056
+ if slices.ContainsFunc(wildcards, func(name string) bool {
1057
+ return certmagic.MatchWildcard(host, name)
1058
+ }) {
1059
+ shieldedHosts[i] = append(shieldedHosts[i], host)
1060
+ break
1061
+ }
1062
+ }
1063
+ }
1064
+ }
1065
+
1066
+ // hoist the shields last to last, so that inserting one does not
1067
+ // shift the index of a covering policy still to be shielded
1068
+ for i, hosts := range slices.Backward(shieldedHosts) {
1069
+ if len(hosts) == 0 {
1070
+ continue
1071
+ }
1072
+ slices.Sort(hosts)
1073
+ shield := *srv.TLSConnPolicies[i]
1074
+ shield.ClientAuthentication = nil
1075
+ shield.MatchersRaw = caddy.ModuleMap{
1076
+ "sni": caddyconfig.JSON(slices.Compact(hosts), warnings),
1077
+ }
1078
+ srv.TLSConnPolicies = slices.Insert(srv.TLSConnPolicies, i, &shield)
1079
+ }
1080
+
1081
// a catch-all TLS conn policy is necessary to ensure TLS can
1082
// be offered to all hostnames of the server; even though only
1083
// one policy is needed to enable TLS for the server, that
1116
return servers, nil
1117
}
1118
1119
+// sniNames returns the server names a connection policy's sni matcher matches.
1120
+// The bool is false when the policy has no sni matcher, or when it does not
1121
+// decode - the latter is unexpected enough to warn about rather than silently
1122
+// skip, since callers use it to decide whether a hostname needs shielding.
1123
+func sniNames(cp *caddytls.ConnectionPolicy, what string, warnings *[]caddyconfig.Warning) ([]string, bool) {
1124
+ raw, ok := cp.MatchersRaw["sni"]
1125
+ if !ok {
1126
+ return nil, false
1127
+ }
1128
+ var sni caddytls.MatchServerName
1129
+ if err := json.Unmarshal(raw, &sni); err != nil {
1130
+ *warnings = append(*warnings, caddyconfig.Warning{
1131
+ Message: fmt.Sprintf("decoding sni matcher %swhile checking wildcard coverage: %v", what, err),
1132
+ })
1133
+ return nil, false
1134
+ }
1135
+ return sni, true
1136
+}
1137
+
1138
func detectConflictingSchemes(srv *caddyhttp.Server, serverBlocks []serverBlock, options map[string]any) error {
1139
httpPort := strconv.Itoa(caddyhttp.DefaultHTTPPort)
1140
if hp, ok := options["http_port"].(int); ok {