Skip to content

Commit b129ed6

Browse files
httpcaddyfile: Fixes for prefer_wildcard mode (#6636)
* httpcaddyfile: Fixes for prefer_wildcard mode The wildcard hosts need to be collected first, then considered after, because there's no guarantee that all non-wildcards will appear after all wildcards when looping. Also we should not add a domain to Skip if it doesn't qualify for TLS anyway. * Alternate solution by avoiding adding APs altogether if covered by wildcard
1 parent d398898 commit b129ed6

File tree

4 files changed

+353
-51
lines changed

4 files changed

+353
-51
lines changed

caddyconfig/httpcaddyfile/httptype.go

+23-19
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,16 @@ func (st *ServerType) serversFromPairings(
706706
return specificity(iLongestHost) > specificity(jLongestHost)
707707
})
708708

709+
// collect all hosts that have a wildcard in them
710+
wildcardHosts := []string{}
711+
for _, sblock := range p.serverBlocks {
712+
for _, addr := range sblock.parsedKeys {
713+
if strings.HasPrefix(addr.Host, "*.") {
714+
wildcardHosts = append(wildcardHosts, addr.Host[2:])
715+
}
716+
}
717+
}
718+
709719
var hasCatchAllTLSConnPolicy, addressQualifiesForTLS bool
710720
autoHTTPSWillAddConnPolicy := srv.AutoHTTPS == nil || !srv.AutoHTTPS.Disabled
711721

@@ -791,13 +801,6 @@ func (st *ServerType) serversFromPairings(
791801
}
792802
}
793803

794-
wildcardHosts := []string{}
795-
for _, addr := range sblock.parsedKeys {
796-
if strings.HasPrefix(addr.Host, "*.") {
797-
wildcardHosts = append(wildcardHosts, addr.Host[2:])
798-
}
799-
}
800-
801804
for _, addr := range sblock.parsedKeys {
802805
// if server only uses HTTP port, auto-HTTPS will not apply
803806
if listenersUseAnyPortOtherThan(srv.Listen, httpPort) {
@@ -813,18 +816,6 @@ func (st *ServerType) serversFromPairings(
813816
}
814817
}
815818

816-
// If prefer wildcard is enabled, then we add hosts that are
817-
// already covered by the wildcard to the skip list
818-
if srv.AutoHTTPS != nil && srv.AutoHTTPS.PreferWildcard && addr.Scheme == "https" {
819-
baseDomain := addr.Host
820-
if idx := strings.Index(baseDomain, "."); idx != -1 {
821-
baseDomain = baseDomain[idx+1:]
822-
}
823-
if !strings.HasPrefix(addr.Host, "*.") && slices.Contains(wildcardHosts, baseDomain) {
824-
srv.AutoHTTPS.Skip = append(srv.AutoHTTPS.Skip, addr.Host)
825-
}
826-
}
827-
828819
// If TLS is specified as directive, it will also result in 1 or more connection policy being created
829820
// Thus, catch-all address with non-standard port, e.g. :8443, can have TLS enabled without
830821
// specifying prefix "https://"
@@ -841,6 +832,19 @@ func (st *ServerType) serversFromPairings(
841832
(addr.Scheme != "http" && addr.Port != httpPort && hasTLSEnabled) {
842833
addressQualifiesForTLS = true
843834
}
835+
836+
// If prefer wildcard is enabled, then we add hosts that are
837+
// already covered by the wildcard to the skip list
838+
if addressQualifiesForTLS && srv.AutoHTTPS != nil && srv.AutoHTTPS.PreferWildcard {
839+
baseDomain := addr.Host
840+
if idx := strings.Index(baseDomain, "."); idx != -1 {
841+
baseDomain = baseDomain[idx+1:]
842+
}
843+
if !strings.HasPrefix(addr.Host, "*.") && slices.Contains(wildcardHosts, baseDomain) {
844+
srv.AutoHTTPS.SkipCerts = append(srv.AutoHTTPS.SkipCerts, addr.Host)
845+
}
846+
}
847+
844848
// predict whether auto-HTTPS will add the conn policy for us; if so, we
845849
// may not need to add one for this server
846850
autoHTTPSWillAddConnPolicy = autoHTTPSWillAddConnPolicy &&

caddyconfig/httpcaddyfile/tlsapp.go

+59-32
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,25 @@ func (st ServerType) buildTLSApp(
9292
tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, catchAllAP)
9393
}
9494

95+
// collect all hosts that have a wildcard in them, and arent HTTP
96+
wildcardHosts := []string{}
97+
for _, p := range pairings {
98+
var addresses []string
99+
for _, addressWithProtocols := range p.addressesWithProtocols {
100+
addresses = append(addresses, addressWithProtocols.address)
101+
}
102+
if !listenersUseAnyPortOtherThan(addresses, httpPort) {
103+
continue
104+
}
105+
for _, sblock := range p.serverBlocks {
106+
for _, addr := range sblock.parsedKeys {
107+
if strings.HasPrefix(addr.Host, "*.") {
108+
wildcardHosts = append(wildcardHosts, addr.Host[2:])
109+
}
110+
}
111+
}
112+
}
113+
95114
for _, p := range pairings {
96115
// avoid setting up TLS automation policies for a server that is HTTP-only
97116
var addresses []string
@@ -115,6 +134,12 @@ func (st ServerType) buildTLSApp(
115134
return nil, warnings, err
116135
}
117136

137+
// make a plain copy so we can compare whether we made any changes
138+
apCopy, err := newBaseAutomationPolicy(options, warnings, true)
139+
if err != nil {
140+
return nil, warnings, err
141+
}
142+
118143
sblockHosts := sblock.hostsFromKeys(false)
119144
if len(sblockHosts) == 0 && catchAllAP != nil {
120145
ap = catchAllAP
@@ -217,9 +242,21 @@ func (st ServerType) buildTLSApp(
217242
catchAllAP = ap
218243
}
219244

245+
hostsNotHTTP := sblock.hostsFromKeysNotHTTP(httpPort)
246+
sort.Strings(hostsNotHTTP) // solely for deterministic test results
247+
248+
// if the we prefer wildcards and the AP is unchanged,
249+
// then we can skip this AP because it should be covered
250+
// by an AP with a wildcard
251+
if slices.Contains(autoHTTPS, "prefer_wildcard") {
252+
if hostsCoveredByWildcard(hostsNotHTTP, wildcardHosts) &&
253+
reflect.DeepEqual(ap, apCopy) {
254+
continue
255+
}
256+
}
257+
220258
// associate our new automation policy with this server block's hosts
221-
ap.SubjectsRaw = sblock.hostsFromKeysNotHTTP(httpPort)
222-
sort.Strings(ap.SubjectsRaw) // solely for deterministic test results
259+
ap.SubjectsRaw = hostsNotHTTP
223260

224261
// if a combination of public and internal names were given
225262
// for this same server block and no issuer was specified, we
@@ -258,6 +295,7 @@ func (st ServerType) buildTLSApp(
258295
ap2.IssuersRaw = []json.RawMessage{caddyconfig.JSONModuleObject(caddytls.InternalIssuer{}, "module", "internal", &warnings)}
259296
}
260297
}
298+
261299
if tlsApp.Automation == nil {
262300
tlsApp.Automation = new(caddytls.AutomationConfig)
263301
}
@@ -418,10 +456,7 @@ func (st ServerType) buildTLSApp(
418456
}
419457

420458
// consolidate automation policies that are the exact same
421-
tlsApp.Automation.Policies = consolidateAutomationPolicies(
422-
tlsApp.Automation.Policies,
423-
slices.Contains(autoHTTPS, "prefer_wildcard"),
424-
)
459+
tlsApp.Automation.Policies = consolidateAutomationPolicies(tlsApp.Automation.Policies)
425460

426461
// ensure automation policies don't overlap subjects (this should be
427462
// an error at provision-time as well, but catch it in the adapt phase
@@ -567,7 +602,7 @@ func newBaseAutomationPolicy(
567602

568603
// consolidateAutomationPolicies combines automation policies that are the same,
569604
// for a cleaner overall output.
570-
func consolidateAutomationPolicies(aps []*caddytls.AutomationPolicy, preferWildcard bool) []*caddytls.AutomationPolicy {
605+
func consolidateAutomationPolicies(aps []*caddytls.AutomationPolicy) []*caddytls.AutomationPolicy {
571606
// sort from most specific to least specific; we depend on this ordering
572607
sort.SliceStable(aps, func(i, j int) bool {
573608
if automationPolicyIsSubset(aps[i], aps[j]) {
@@ -652,31 +687,6 @@ outer:
652687
j--
653688
}
654689
}
655-
656-
if preferWildcard {
657-
// remove subjects from i if they're covered by a wildcard in j
658-
iSubjs := aps[i].SubjectsRaw
659-
for iSubj := 0; iSubj < len(iSubjs); iSubj++ {
660-
for jSubj := range aps[j].SubjectsRaw {
661-
if !strings.HasPrefix(aps[j].SubjectsRaw[jSubj], "*.") {
662-
continue
663-
}
664-
if certmagic.MatchWildcard(aps[i].SubjectsRaw[iSubj], aps[j].SubjectsRaw[jSubj]) {
665-
iSubjs = slices.Delete(iSubjs, iSubj, iSubj+1)
666-
iSubj--
667-
break
668-
}
669-
}
670-
}
671-
aps[i].SubjectsRaw = iSubjs
672-
673-
// remove i if it has no subjects left
674-
if len(aps[i].SubjectsRaw) == 0 {
675-
aps = slices.Delete(aps, i, i+1)
676-
i--
677-
continue outer
678-
}
679-
}
680690
}
681691
}
682692

@@ -748,3 +758,20 @@ func automationPolicyHasAllPublicNames(ap *caddytls.AutomationPolicy) bool {
748758
func isTailscaleDomain(name string) bool {
749759
return strings.HasSuffix(strings.ToLower(name), ".ts.net")
750760
}
761+
762+
func hostsCoveredByWildcard(hosts []string, wildcards []string) bool {
763+
if len(hosts) == 0 || len(wildcards) == 0 {
764+
return false
765+
}
766+
for _, host := range hosts {
767+
for _, wildcard := range wildcards {
768+
if strings.HasPrefix(host, "*.") {
769+
continue
770+
}
771+
if certmagic.MatchWildcard(host, "*."+wildcard) {
772+
return true
773+
}
774+
}
775+
}
776+
return false
777+
}

caddytest/integration/caddyfile_adapt/auto_https_prefer_wildcard.caddyfiletest

+3
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ foo.example.com {
7474
}
7575
],
7676
"automatic_https": {
77+
"skip_certificates": [
78+
"foo.example.com"
79+
],
7780
"prefer_wildcard": true
7881
}
7982
}

0 commit comments

Comments
 (0)