Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 12, 02:00 UTC
caddytls: Don't publish ECH configs if other records don't exist
Publishing a DNS record for a name that doesn't have any could make wildcards ineffective, which would be surprising for site owners and could lead to downtime.
Matthew Holt committed
Mar 12, 2025 at 16:33 UTC
1f8dab572ca9681464fdadc65bfb5f250fc496c3
3 files changed
+42
-15
modules/caddyhttp/autohttps.go
+12
-5
@@ -163,6 +163,7 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
163
}
164
}
165
166
+ // trim the list of domains covered by wildcards, if configured
167
if srv.AutoHTTPS.PreferWildcard {
168
wildcards := make(map[string]struct{})
169
for d := range serverDomainSet {
@@ -184,6 +185,17 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
185
}
186
}
187
188
+ // build the list of domains that could be used with ECH (if enabled)
189
+ // so the TLS app can know to publish ECH configs for them; we do this
190
+ // after trimming domains covered by wildcards because, presumably,
191
+ // if the user wants to use wildcard certs, they also want to use the
192
+ // wildcard for ECH, rather than individual subdomains
193
+ echDomains := make([]string, 0, len(serverDomainSet))
194
+ for d := range serverDomainSet {
195
+ echDomains = append(echDomains, d)
196
+ }
197
+ app.tlsApp.RegisterServerNames(echDomains)
198
+
199
// nothing more to do here if there are no domains that qualify for
200
// automatic HTTPS and there are no explicit TLS connection policies:
201
// if there is at least one domain but no TLS conn policy (F&&T), we'll
@@ -205,7 +217,6 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
217
// for all the hostnames we found, filter them so we have
218
// a deduplicated list of names for which to obtain certs
219
// (only if cert management not disabled for this server)
208
- var echDomains []string
220
if srv.AutoHTTPS.DisableCerts {
221
logger.Warn("skipping automated certificate management for server because it is disabled", zap.String("server_name", srvName))
222
} else {
@@ -232,14 +243,10 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
243
}
244
245
uniqueDomainsForCerts[d] = struct{}{}
235
- echDomains = append(echDomains, d)
246
}
247
}
248
}
249
240
- // let the TLS server know we have some hostnames that could be protected behind ECH
241
- app.tlsApp.RegisterServerNames(echDomains)
242
-
250
// tell the server to use TLS if it is not already doing so
251
if srv.TLSConnPolicies == nil {
252
srv.TLSConnPolicies = caddytls.ConnectionPolicies{new(caddytls.ConnectionPolicy)}
modules/caddytls/ech.go
+29
-9
@@ -639,8 +639,16 @@ func (dnsPub *ECHDNSPublisher) PublishECHConfigList(ctx context.Context, innerNa
639
continue
640
}
641
642
- // get any existing HTTPS record for this domain, and augment
643
- // our ech SvcParamKey with any other existing SvcParams
642
+ relName := libdns.RelativeName(domain+".", zone)
643
+ // TODO: libdns.RelativeName should probably return "@" instead of "". (The latest commits of libdns do this, so remove this logic once upgraded.)
644
+ if relName == "" {
645
+ relName = "@"
646
+ }
647
+
648
+ // get existing records for this domain; we need to make sure another
649
+ // record exists for it so we don't accidentally trample a wildcard; we
650
+ // also want to get any HTTPS record that may already exist for it so
651
+ // we can augment the ech SvcParamKey with any other existing SvcParams
652
recs, err := dnsPub.provider.GetRecords(ctx, zone)
653
if err != nil {
654
dnsPub.logger.Error("unable to get existing DNS records to publish ECH data to HTTPS DNS record",
@@ -648,17 +656,29 @@ func (dnsPub *ECHDNSPublisher) PublishECHConfigList(ctx context.Context, innerNa
656
zap.Error(err))
657
continue
658
}
651
- relName := libdns.RelativeName(domain+".", zone)
652
- // TODO: libdns.RelativeName should probably return "@" instead of "".
653
- if relName == "" {
654
- relName = "@"
655
- }
659
var httpsRec libdns.Record
660
+ var nameHasExistingRecord bool
661
for _, rec := range recs {
658
- if rec.Name == relName && rec.Type == "HTTPS" && (rec.Target == "" || rec.Target == ".") {
659
- httpsRec = rec
662
+ if rec.Name == relName {
663
+ nameHasExistingRecord = true
664
+ if rec.Type == "HTTPS" && (rec.Target == "" || rec.Target == ".") {
665
+ httpsRec = rec
666
+ break
667
+ }
668
}
669
}
670
+ if !nameHasExistingRecord {
671
+ // Turns out if you publish a DNS record for a name that doesn't have any DNS record yet,
672
+ // any wildcard records won't apply for the name anymore, meaning if a wildcard A/AAAA record
673
+ // is used to resolve the domain to a server, publishing an HTTPS record could break resolution!
674
+ // In theory, this should be a non-issue, at least for A/AAAA records, if the HTTPS record
675
+ // includes ipv[4|6]hint SvcParamKeys,
676
+ dnsPub.logger.Warn("domain does not have any existing records, so skipping publication of HTTPS record",
677
+ zap.String("domain", domain),
678
+ zap.String("relative_name", relName),
679
+ zap.String("zone", zone))
680
+ continue
681
+ }
682
params := make(svcParams)
683
if httpsRec.Value != "" {
684
params, err = parseSvcParams(httpsRec.Value)
modules/caddytls/tls.go
+1
-1
@@ -563,7 +563,7 @@ func (t *TLS) Manage(names []string) error {
563
// (keeping only the hotsname) and filters IP addresses, which can't be
564
// used with ECH.
565
//
566
-// EXPERIMENTAL: This function and its behavior are subject to change.
566
+// EXPERIMENTAL: This function and its semantics/behavior are subject to change.
567
func (t *TLS) RegisterServerNames(dnsNames []string) {
568
t.serverNamesMu.Lock()
569
for _, name := range dnsNames {