@marcel91200 / caddy Mirror · read-only / commits / 0fab9f0f
Fork 0

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, 12:15 UTC

caddytls: avoid duplicate automation for wildcard-covered hosts (#7697)

* caddytls: Fix wildcard race in auto-HTTPS launch When evaluating whether to skip managing an individual subdomain due to an existing wildcard configuration, we now explicitly consult the automate loader. Because Caddy apps can start in any order, relying strictly on the TLS app's internal management state was non-deterministic if the HTTP app started first. Checking the automate loader guarantees predictable behavior since it is fully populated during the Provision phase, well before any apps are started. * respond to review comments 1. update requested comment 2. remove personal domain from test 3. add regression test * remove unnecessary mutex lock * refactor: -integration test, +explicit cases * refactor: remove redundant test, add comment * rename file and add header * update copyright year

Rijul committed May 10, 2026 at 19:38 UTC 0fab9f0f7db04fdb0c1c3113d51e7c420d3c8f06
2 files changed +104 -3
modules/caddytls/tls.go
+8 -3
@@ -614,8 +614,8 @@ func (t *TLS) Manage(subjects map[string]struct{}) error {
614
615 // managingWildcardFor returns true if the app is managing a certificate that covers that
616 // subject name (including consideration of wildcards), either from its internal list of
617 -// names that it IS managing certs for, or from the otherSubjsToManage which includes names
618 -// that WILL be managed.
617 +// names that it IS managing certs for, from the otherSubjsToManage which includes names
618 +// that WILL be managed, or from names configured in the 'automate' loader.
619 func (t *TLS) managingWildcardFor(subj string, otherSubjsToManage map[string]struct{}) bool {
620 // TODO: we could also consider manually-loaded certs using t.HasCertificateForSubject(),
621 // but that does not account for how manually-loaded certs may be restricted as to which
@@ -630,7 +630,9 @@ func (t *TLS) managingWildcardFor(subj string, otherSubjsToManage map[string]str
630 return managing
631 }
632
633 - // replace labels of the domain with wildcards until we get a match
633 + // replace labels of the domain with wildcards until we get a match from names
634 + // already being managed, those about to be managed in this batch, or those
635 + // configured for automation
636 labels := strings.Split(subj, ".")
637 for i := range labels {
638 if labels[i] == "*" {
@@ -644,6 +646,9 @@ func (t *TLS) managingWildcardFor(subj string, otherSubjsToManage map[string]str
646 if _, ok := otherSubjsToManage[candidate]; ok {
647 return true
648 }
649 + if _, ok := t.automateNames[candidate]; ok {
650 + return true
651 + }
652 }
653
654 return false
modules/caddytls/tls_wildcard_test.go new
+96
@@ -0,0 +1,96 @@
1 +// Copyright 2015 Matthew Holt and The Caddy Authors
2 +//
3 +// Licensed under the Apache License, Version 2.0 (the "License");
4 +// you may not use this file except in compliance with the License.
5 +// You may obtain a copy of the License at
6 +//
7 +// http://www.apache.org/licenses/LICENSE-2.0
8 +//
9 +// Unless required by applicable law or agreed to in writing, software
10 +// distributed under the License is distributed on an "AS IS" BASIS,
11 +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 +// See the License for the specific language governing permissions and
13 +// limitations under the License.
14 +
15 +package caddytls
16 +
17 +import (
18 + "encoding/json"
19 + "testing"
20 +
21 + "github.com/caddyserver/caddy/v2"
22 +)
23 +
24 +func TestAvoidDuplicateAutomation(t *testing.T) {
25 + tests := []struct {
26 + name string
27 + automateNames []string
28 + expectedToManage bool
29 + }{
30 + {
31 + name: "do not manage if wildcard is automated",
32 + automateNames: []string{"*.example.com"},
33 + expectedToManage: false,
34 + },
35 + {
36 + name: "manage if no automation configured",
37 + automateNames: []string{},
38 + expectedToManage: true,
39 + },
40 + {
41 + name: "manage if explicitly requested even when wildcard automated",
42 + automateNames: []string{"*.example.com", "sub.example.com"},
43 + expectedToManage: true,
44 + },
45 + }
46 +
47 + for _, tc := range tests {
48 + t.Run(tc.name, func(t *testing.T) {
49 + automateJSON, err := json.Marshal(tc.automateNames)
50 + if err != nil {
51 + t.Fatal(err)
52 + }
53 +
54 + tlsApp := &TLS{
55 + Automation: &AutomationConfig{
56 + Policies: []*AutomationPolicy{
57 + {
58 + IssuersRaw: []json.RawMessage{
59 + []byte(`{"module": "internal"}`),
60 + },
61 + },
62 + },
63 + },
64 + CertificatesRaw: map[string]json.RawMessage{
65 + "automate": automateJSON,
66 + },
67 + }
68 +
69 + var cfg caddy.Config
70 + ctx, err := caddy.ProvisionContext(&cfg)
71 + if err != nil {
72 + t.Fatal(err)
73 + }
74 +
75 + if err := tlsApp.Provision(ctx); err != nil {
76 + t.Fatal(err)
77 + }
78 +
79 + // simulate a case wherein the HTTP app starts first and
80 + // tells the TLS app about the following auto-HTTPS domains
81 + httpDomains := map[string]struct{}{"sub.example.com": {}}
82 + if err := tlsApp.Manage(httpDomains); err != nil {
83 + t.Fatal(err)
84 + }
85 +
86 + _, actuallyManaged := tlsApp.managing["sub.example.com"]
87 + if actuallyManaged != tc.expectedToManage {
88 + t.Errorf(
89 + "expected sub.example.com individually managed: %v, got: %v",
90 + tc.expectedToManage,
91 + actuallyManaged,
92 + )
93 + }
94 + })
95 + }
96 +}