caddyfile: preserve implicit TLS issuer semantics (#7743)

This commit is contained in:
Zen Dodd 2026-05-21 02:48:37 +10:00 committed by GitHub
parent 6628c4a9de
commit 6a210e96ee
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 71 additions and 1 deletions

View file

@ -1036,7 +1036,7 @@ outer:
// otherwise the one without any subjects (a catch-all) would be
// eaten up by the one with subjects; and if both have subjects, we
// need to combine their lists
if reflect.DeepEqual(aps[i].IssuersRaw, aps[j].IssuersRaw) &&
if automationPoliciesHaveSameIssuers(aps[i], aps[j]) &&
reflect.DeepEqual(aps[i].ManagersRaw, aps[j].ManagersRaw) &&
bytes.Equal(aps[i].StorageRaw, aps[j].StorageRaw) &&
aps[i].MustStaple == aps[j].MustStaple &&
@ -1128,6 +1128,58 @@ func subjectQualifiesForPublicCert(ap *caddytls.AutomationPolicy, subj string) b
(strings.Count(subj, "*.") < 2 || ap.OnDemand)
}
func automationPoliciesHaveSameIssuers(a, b *caddytls.AutomationPolicy) bool {
if reflect.DeepEqual(a.IssuersRaw, b.IssuersRaw) {
return automationPoliciesHaveCompatibleImplicitIssuers(a, b)
}
return automationPolicyUsesDefaultInternalIssuer(a) && automationPolicyUsesDefaultInternalIssuer(b)
}
func automationPolicyUsesDefaultInternalIssuer(ap *caddytls.AutomationPolicy) bool {
if len(ap.IssuersRaw) == 0 && len(ap.Issuers) == 0 {
return automationPolicyImplicitIssuerClass(ap) == "internal"
}
return len(ap.IssuersRaw) == 1 &&
len(ap.Issuers) == 0 &&
string(bytes.TrimSpace(ap.IssuersRaw[0])) == `{"module":"internal"}`
}
// automationPoliciesHaveCompatibleImplicitIssuers returns whether two policies
// without explicit issuers can be consolidated without changing default issuer
// selection for their subjects.
func automationPoliciesHaveCompatibleImplicitIssuers(a, b *caddytls.AutomationPolicy) bool {
if len(a.IssuersRaw) > 0 || len(a.Issuers) > 0 ||
len(b.IssuersRaw) > 0 || len(b.Issuers) > 0 {
return true
}
aClass := automationPolicyImplicitIssuerClass(a)
bClass := automationPolicyImplicitIssuerClass(b)
return aClass == "catch-all" || bClass == "catch-all" || aClass == bClass
}
func automationPolicyImplicitIssuerClass(ap *caddytls.AutomationPolicy) string {
if len(ap.SubjectsRaw) == 0 {
return "catch-all"
}
hasPublic := slices.ContainsFunc(ap.SubjectsRaw, func(subj string) bool {
return subjectQualifiesForPublicCert(ap, subj)
})
hasInternal := slices.ContainsFunc(ap.SubjectsRaw, func(subj string) bool {
return !subjectQualifiesForPublicCert(ap, subj)
})
switch {
case hasPublic && hasInternal:
return "mixed"
case hasPublic:
return "public"
default:
return "internal"
}
}
// automationPolicyHasAllPublicNames returns true if all the names on the policy
// do NOT qualify for public certs OR are tailscale domains.
func automationPolicyHasAllPublicNames(ap *caddytls.AutomationPolicy) bool {

View file

@ -3,6 +3,7 @@ package httpcaddyfile
import (
"testing"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/modules/caddytls"
)
@ -54,3 +55,20 @@ func TestAutomationPolicyIsSubset(t *testing.T) {
}
}
}
func TestAutomationPoliciesAllowSameHostOnDifferentPorts(t *testing.T) {
input := `https://example.com:5000 localhost:5000 {
respond "one"
}
https://example.net localhost:8080 {
respond "two"
}
`
adapter := caddyfile.Adapter{ServerType: ServerType{}}
_, _, err := adapter.Adapt([]byte(input), nil)
if err != nil {
t.Fatalf("adapting Caddyfile: %v", err)
}
}