@marcel91200 / caddy Mirror · read-only / commits / 1f8dab57
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 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 {