Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 11, 11:00 UTC
caddyfile: preserve implicit TLS issuer semantics (#7743)
Zen Dodd committed
May 21, 2026 at 02:48 UTC
6a210e96ee481a07abde725fd4eae8c76cccfe82
2 files changed
+71
-1
caddyconfig/httpcaddyfile/tlsapp.go
+53
-1
@@ -1036,7 +1036,7 @@ outer:
1036
// otherwise the one without any subjects (a catch-all) would be
1037
// eaten up by the one with subjects; and if both have subjects, we
1038
// need to combine their lists
1039
- if reflect.DeepEqual(aps[i].IssuersRaw, aps[j].IssuersRaw) &&
1039
+ if automationPoliciesHaveSameIssuers(aps[i], aps[j]) &&
1040
reflect.DeepEqual(aps[i].ManagersRaw, aps[j].ManagersRaw) &&
1041
bytes.Equal(aps[i].StorageRaw, aps[j].StorageRaw) &&
1042
aps[i].MustStaple == aps[j].MustStaple &&
@@ -1128,6 +1128,58 @@ func subjectQualifiesForPublicCert(ap *caddytls.AutomationPolicy, subj string) b
1128
(strings.Count(subj, "*.") < 2 || ap.OnDemand)
1129
}
1130
1131
+func automationPoliciesHaveSameIssuers(a, b *caddytls.AutomationPolicy) bool {
1132
+ if reflect.DeepEqual(a.IssuersRaw, b.IssuersRaw) {
1133
+ return automationPoliciesHaveCompatibleImplicitIssuers(a, b)
1134
+ }
1135
+ return automationPolicyUsesDefaultInternalIssuer(a) && automationPolicyUsesDefaultInternalIssuer(b)
1136
+}
1137
+
1138
+func automationPolicyUsesDefaultInternalIssuer(ap *caddytls.AutomationPolicy) bool {
1139
+ if len(ap.IssuersRaw) == 0 && len(ap.Issuers) == 0 {
1140
+ return automationPolicyImplicitIssuerClass(ap) == "internal"
1141
+ }
1142
+ return len(ap.IssuersRaw) == 1 &&
1143
+ len(ap.Issuers) == 0 &&
1144
+ string(bytes.TrimSpace(ap.IssuersRaw[0])) == `{"module":"internal"}`
1145
+}
1146
+
1147
+// automationPoliciesHaveCompatibleImplicitIssuers returns whether two policies
1148
+// without explicit issuers can be consolidated without changing default issuer
1149
+// selection for their subjects.
1150
+func automationPoliciesHaveCompatibleImplicitIssuers(a, b *caddytls.AutomationPolicy) bool {
1151
+ if len(a.IssuersRaw) > 0 || len(a.Issuers) > 0 ||
1152
+ len(b.IssuersRaw) > 0 || len(b.Issuers) > 0 {
1153
+ return true
1154
+ }
1155
+
1156
+ aClass := automationPolicyImplicitIssuerClass(a)
1157
+ bClass := automationPolicyImplicitIssuerClass(b)
1158
+ return aClass == "catch-all" || bClass == "catch-all" || aClass == bClass
1159
+}
1160
+
1161
+func automationPolicyImplicitIssuerClass(ap *caddytls.AutomationPolicy) string {
1162
+ if len(ap.SubjectsRaw) == 0 {
1163
+ return "catch-all"
1164
+ }
1165
+
1166
+ hasPublic := slices.ContainsFunc(ap.SubjectsRaw, func(subj string) bool {
1167
+ return subjectQualifiesForPublicCert(ap, subj)
1168
+ })
1169
+ hasInternal := slices.ContainsFunc(ap.SubjectsRaw, func(subj string) bool {
1170
+ return !subjectQualifiesForPublicCert(ap, subj)
1171
+ })
1172
+
1173
+ switch {
1174
+ case hasPublic && hasInternal:
1175
+ return "mixed"
1176
+ case hasPublic:
1177
+ return "public"
1178
+ default:
1179
+ return "internal"
1180
+ }
1181
+}
1182
+
1183
// automationPolicyHasAllPublicNames returns true if all the names on the policy
1184
// do NOT qualify for public certs OR are tailscale domains.
1185
func automationPolicyHasAllPublicNames(ap *caddytls.AutomationPolicy) bool {
caddyconfig/httpcaddyfile/tlsapp_test.go
+18
@@ -3,6 +3,7 @@ package httpcaddyfile
3
import (
4
"testing"
5
6
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
7
"github.com/caddyserver/caddy/v2/modules/caddytls"
8
)
9
@@ -54,3 +55,20 @@ func TestAutomationPolicyIsSubset(t *testing.T) {
55
}
56
}
57
}
58
+
59
+func TestAutomationPoliciesAllowSameHostOnDifferentPorts(t *testing.T) {
60
+ input := `https://example.com:5000 localhost:5000 {
61
+ respond "one"
62
+}
63
+
64
+https://example.net localhost:8080 {
65
+ respond "two"
66
+}
67
+`
68
+
69
+ adapter := caddyfile.Adapter{ServerType: ServerType{}}
70
+ _, _, err := adapter.Adapt([]byte(input), nil)
71
+ if err != nil {
72
+ t.Fatalf("adapting Caddyfile: %v", err)
73
+ }
74
+}