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, 22:15 UTC
caddytls: Improve ECH error logging (close #7152)
Matthew Holt committed
Aug 4, 2025 at 16:21 UTC
731e6c24821630f7ac117ff74188e79a06a75d1e
1 file changed
+81
-25
modules/caddytls/ech.go
+81
-25
@@ -291,18 +291,54 @@ func (t *TLS) publishECHConfigs() error {
291
}
292
293
logger.Debug("publishing ECH config list",
294
+ zap.String("publisher", publisherKey),
295
zap.Strings("domains", dnsNamesToPublish),
296
zap.Uint8s("config_ids", configIDs))
297
298
// publish this ECH config list with this publisher
299
pubTime := time.Now()
300
err := publisher.PublishECHConfigList(t.ctx, dnsNamesToPublish, echCfgListBin)
300
- if err == nil {
301
- t.logger.Info("published ECH configuration list",
301
+
302
+ var publishErrs PublishECHConfigListErrors
303
+ if errors.As(err, &publishErrs) {
304
+ // at least a partial failure, maybe a complete failure, but we can
305
+ // log each error by domain
306
+ for innerName, domainErr := range publishErrs {
307
+ t.logger.Error("failed to publish ECH configuration list",
308
+ zap.String("publisher", publisherKey),
309
+ zap.String("domain", innerName),
310
+ zap.Uint8s("config_ids", configIDs),
311
+ zap.Error(domainErr))
312
+ }
313
+ } else if err != nil {
314
+ // generic error; assume the entire thing failed, I guess
315
+ t.logger.Error("failed publishing ECH configuration list",
316
+ zap.String("publisher", publisherKey),
317
zap.Strings("domains", dnsNamesToPublish),
318
zap.Uint8s("config_ids", configIDs),
319
zap.Error(err))
305
- // update publication history, so that we don't unnecessarily republish every time
320
+ }
321
+
322
+ if err == nil || (len(publishErrs) > 0 && len(publishErrs) < len(dnsNamesToPublish)) {
323
+ // if publication for at least some domains succeeded, we should update our publication
324
+ // state for those domains to avoid unnecessarily republishing every time
325
+ someAll := "all"
326
+ if len(publishErrs) > 0 {
327
+ someAll = "some"
328
+ }
329
+ // make a list of names that published successfully with this publisher
330
+ // so that we update only their state in storage, not the failed ones
331
+ var successNames []string
332
+ for _, name := range dnsNamesToPublish {
333
+ if _, ok := publishErrs[name]; !ok {
334
+ successNames = append(successNames, name)
335
+ }
336
+ }
337
+ t.logger.Info("successfully published ECH configuration list for "+someAll+" domains",
338
+ zap.String("publisher", publisherKey),
339
+ zap.Strings("domains", successNames),
340
+ zap.Uint8s("config_ids", configIDs))
341
+
342
for _, cfg := range echCfgList {
343
if cfg.meta.Publications == nil {
344
cfg.meta.Publications = make(publicationHistory)
@@ -310,7 +346,7 @@ func (t *TLS) publishECHConfigs() error {
346
if _, ok := cfg.meta.Publications[publisherKey]; !ok {
347
cfg.meta.Publications[publisherKey] = make(map[string]time.Time)
348
}
313
- for _, name := range dnsNamesToPublish {
349
+ for _, name := range successNames {
350
cfg.meta.Publications[publisherKey][name] = pubTime
351
}
352
metaBytes, err := json.Marshal(cfg.meta)
@@ -323,10 +359,10 @@ func (t *TLS) publishECHConfigs() error {
359
}
360
}
361
} else {
326
- t.logger.Error("publishing ECH configuration list",
327
- zap.Strings("domains", publication.Domains),
328
- zap.Uint8s("config_ids", configIDs),
329
- zap.Error(err))
362
+ t.logger.Error("all domains failed to publish ECH configuration list (see earlier errors)",
363
+ zap.String("publisher", publisherKey),
364
+ zap.Strings("domains", dnsNamesToPublish),
365
+ zap.Uint8s("config_ids", configIDs))
366
}
367
}
368
}
@@ -631,17 +667,19 @@ func (dnsPub ECHDNSPublisher) PublisherKey() string {
667
return string(dnsPub.provider.(caddy.Module).CaddyModule().ID)
668
}
669
634
-// PublishECHConfigList publishes the given ECH config list to the given DNS names.
670
+// PublishECHConfigList publishes the given ECH config list (as binary) to the given DNS names.
671
+// If there is an error, it may be of type PublishECHConfigListErrors, detailing
672
+// potentially multiple errors keyed by associated innerName.
673
func (dnsPub *ECHDNSPublisher) PublishECHConfigList(ctx context.Context, innerNames []string, configListBin []byte) error {
674
nameservers := certmagic.RecursiveNameservers(nil) // TODO: we could make resolvers configurable
675
676
+ errs := make(PublishECHConfigListErrors)
677
+
678
nextName:
679
for _, domain := range innerNames {
680
zone, err := certmagic.FindZoneByFQDN(ctx, dnsPub.logger, domain, nameservers)
681
if err != nil {
642
- dnsPub.logger.Error("could not determine zone for domain",
643
- zap.String("domain", domain),
644
- zap.Error(err))
682
+ errs[domain] = fmt.Errorf("could not determine zone for domain: %w (domain=%s nameservers=%v)", err, domain, nameservers)
683
continue
684
}
685
@@ -653,9 +691,7 @@ nextName:
691
// we can augment the ech SvcParamKey with any other existing SvcParams
692
recs, err := dnsPub.provider.GetRecords(ctx, zone)
693
if err != nil {
656
- dnsPub.logger.Error("unable to get existing DNS records to publish ECH data to HTTPS DNS record",
657
- zap.String("domain", domain),
658
- zap.Error(err))
694
+ errs[domain] = fmt.Errorf("unable to get existing DNS records to publish ECH data to HTTPS DNS record: %w", err)
695
continue
696
}
697
var httpsRec libdns.ServiceBinding
@@ -713,16 +749,14 @@ nextName:
749
},
750
})
751
if err != nil {
716
- // TODO: Maybe this should just stop and return the error...
717
- dnsPub.logger.Error("unable to publish ECH data to HTTPS DNS record",
718
- zap.String("domain", domain),
719
- zap.String("zone", zone),
720
- zap.String("dns_record_name", relName),
721
- zap.Error(err))
752
+ errs[domain] = fmt.Errorf("unable to publish ECH data to HTTPS DNS record: %w (zone=%s dns_record_name=%s)", err, zone, relName)
753
continue
754
}
755
}
756
757
+ if len(errs) > 0 {
758
+ return errs
759
+ }
760
return nil
761
}
762
@@ -956,13 +990,35 @@ type ECHPublisher interface {
990
// It is used to prevent duplicating publications.
991
PublisherKey() string
992
959
- // Publishes the ECH config list for the given innerNames. Some publishers
960
- // may not need a list of inner/protected names, and can ignore the argument;
961
- // most, however, will want to use it to know which inner names are to be
962
- // associated with the given ECH config list.
993
+ // Publishes the ECH config list (as binary) for the given innerNames. Some
994
+ // publishers may not need a list of inner/protected names, and can ignore the
995
+ // argument; most, however, will want to use it to know which inner names are
996
+ // to be associated with the given ECH config list.
997
+ //
998
+ // Implementations should return an error of type PublishECHConfigListErrors
999
+ // when relevant to key errors to their associated innerName, but should never
1000
+ // return a non-nil PublishECHConfigListErrors when its length is 0.
1001
PublishECHConfigList(ctx context.Context, innerNames []string, echConfigList []byte) error
1002
}
1003
1004
+// PublishECHConfigListErrors is returned by ECHPublishers to describe one or more
1005
+// errors publishing an ECH config list from PublishECHConfigList. A non-nil, empty
1006
+// value of this type should never be returned.
1007
+type PublishECHConfigListErrors map[string]error
1008
+
1009
+func (p PublishECHConfigListErrors) Error() string {
1010
+ var sb strings.Builder
1011
+ for innerName, err := range p {
1012
+ if sb.Len() > 0 {
1013
+ sb.WriteString("; ")
1014
+ }
1015
+ sb.WriteString(innerName)
1016
+ sb.WriteString(": ")
1017
+ sb.WriteString(err.Error())
1018
+ }
1019
+ return sb.String()
1020
+}
1021
+
1022
type echConfigMeta struct {
1023
Created time.Time `json:"created"`
1024
Publications publicationHistory `json:"publications"`