@marcel91200 / caddy Mirror · read-only / commits / 731e6c24
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, 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"`