From 9411595df042ab2bc743599dc14dfc05d29cbea9 Mon Sep 17 00:00:00 2001 From: Pavel Siomachkin Date: Thu, 9 Oct 2025 19:26:38 +0200 Subject: [PATCH 1/4] Add global resolvers directive for DNS challenge configuration --- caddyconfig/httpcaddyfile/options.go | 10 ++ caddyconfig/httpcaddyfile/options_test.go | 104 ++++++++++++++++ caddyconfig/httpcaddyfile/tlsapp.go | 14 +++ .../global_options_resolvers.caddyfiletest | 77 ++++++++++++ ...ons_resolvers_http_challenge.caddyfiletest | 38 ++++++ ..._resolvers_local_dns_inherit.caddyfiletest | 72 +++++++++++ ...ons_resolvers_local_override.caddyfiletest | 98 +++++++++++++++ ...obal_options_resolvers_mixed.caddyfiletest | 112 ++++++++++++++++++ modules/caddytls/tls.go | 6 + 9 files changed, 531 insertions(+) create mode 100644 caddytest/integration/caddyfile_adapt/global_options_resolvers.caddyfiletest create mode 100644 caddytest/integration/caddyfile_adapt/global_options_resolvers_http_challenge.caddyfiletest create mode 100644 caddytest/integration/caddyfile_adapt/global_options_resolvers_local_dns_inherit.caddyfiletest create mode 100644 caddytest/integration/caddyfile_adapt/global_options_resolvers_local_override.caddyfiletest create mode 100644 caddytest/integration/caddyfile_adapt/global_options_resolvers_mixed.caddyfiletest diff --git a/caddyconfig/httpcaddyfile/options.go b/caddyconfig/httpcaddyfile/options.go index 336c6999f..a68d54dc4 100644 --- a/caddyconfig/httpcaddyfile/options.go +++ b/caddyconfig/httpcaddyfile/options.go @@ -64,6 +64,7 @@ func init() { RegisterGlobalOption("preferred_chains", parseOptPreferredChains) RegisterGlobalOption("persist_config", parseOptPersistConfig) RegisterGlobalOption("dns", parseOptDNS) + RegisterGlobalOption("resolvers", parseOptResolvers) RegisterGlobalOption("ech", parseOptECH) } @@ -305,6 +306,15 @@ func parseOptSingleString(d *caddyfile.Dispenser, _ any) (any, error) { return val, nil } +func parseOptResolvers(d *caddyfile.Dispenser, _ any) (any, error) { + d.Next() // consume option name + resolvers := d.RemainingArgs() + if len(resolvers) == 0 { + return nil, d.ArgErr() + } + return resolvers, nil +} + func parseOptDefaultBind(d *caddyfile.Dispenser, _ any) (any, error) { d.Next() // consume option name diff --git a/caddyconfig/httpcaddyfile/options_test.go b/caddyconfig/httpcaddyfile/options_test.go index bc9e88134..eb10efe4f 100644 --- a/caddyconfig/httpcaddyfile/options_test.go +++ b/caddyconfig/httpcaddyfile/options_test.go @@ -1,9 +1,11 @@ package httpcaddyfile import ( + "encoding/json" "testing" "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + "github.com/caddyserver/caddy/v2/modules/caddytls" _ "github.com/caddyserver/caddy/v2/modules/logging" ) @@ -62,3 +64,105 @@ func TestGlobalLogOptionSyntax(t *testing.T) { } } } + +func TestGlobalResolversOption(t *testing.T) { + tests := []struct { + name string + input string + expectResolvers []string + expectError bool + }{ + { + name: "single resolver", + input: `{ + resolvers 1.1.1.1 + } + example.com { + }`, + expectResolvers: []string{"1.1.1.1"}, + expectError: false, + }, + { + name: "two resolvers", + input: `{ + resolvers 1.1.1.1 8.8.8.8 + } + example.com { + }`, + expectResolvers: []string{"1.1.1.1", "8.8.8.8"}, + expectError: false, + }, + { + name: "multiple resolvers", + input: `{ + resolvers 1.1.1.1 8.8.8.8 9.9.9.9 + } + example.com { + }`, + expectResolvers: []string{"1.1.1.1", "8.8.8.8", "9.9.9.9"}, + expectError: false, + }, + { + name: "no resolvers specified", + input: `{ + } + example.com { + }`, + expectResolvers: nil, + expectError: false, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + adapter := caddyfile.Adapter{ + ServerType: ServerType{}, + } + + out, _, err := adapter.Adapt([]byte(tc.input), nil) + + if (err != nil) != tc.expectError { + t.Errorf("error expectation failed. Expected error: %v, got: %v", tc.expectError, err) + return + } + + if tc.expectError { + return + } + + // Parse the output JSON to check resolvers + var config struct { + Apps struct { + TLS *caddytls.TLS `json:"tls"` + } `json:"apps"` + } + + if err := json.Unmarshal(out, &config); err != nil { + t.Errorf("failed to unmarshal output: %v", err) + return + } + + // Check if resolvers match expected + if config.Apps.TLS == nil { + if tc.expectResolvers != nil { + t.Errorf("Expected TLS config with resolvers %v, but TLS config is nil", tc.expectResolvers) + } + return + } + + actualResolvers := config.Apps.TLS.Resolvers + if len(tc.expectResolvers) == 0 && len(actualResolvers) == 0 { + return // Both empty, ok + } + if len(actualResolvers) != len(tc.expectResolvers) { + t.Errorf("Expected %d resolvers, got %d. Expected: %v, got: %v", len(tc.expectResolvers), len(actualResolvers), tc.expectResolvers, actualResolvers) + return + } + for j, expected := range tc.expectResolvers { + if actualResolvers[j] != expected { + t.Errorf("Resolver %d mismatch. Expected: %s, got: %s", j, expected, actualResolvers[j]) + } + } + }) + } +} diff --git a/caddyconfig/httpcaddyfile/tlsapp.go b/caddyconfig/httpcaddyfile/tlsapp.go index c5ff3bb23..9b49a9819 100644 --- a/caddyconfig/httpcaddyfile/tlsapp.go +++ b/caddyconfig/httpcaddyfile/tlsapp.go @@ -362,6 +362,11 @@ func (st ServerType) buildTLSApp( tlsApp.DNSRaw = caddyconfig.JSONModuleObject(globalDNS, "name", globalDNS.(caddy.Module).CaddyModule().ID.Name(), nil) } + // set up "global" (to the TLS app) DNS resolvers config + if globalResolvers, ok := options["resolvers"]; ok && globalResolvers != nil { + tlsApp.Resolvers = globalResolvers.([]string) + } + // set up ECH from Caddyfile options if ech, ok := options["ech"].(*caddytls.ECH); ok { tlsApp.EncryptedClientHello = ech @@ -609,6 +614,15 @@ func fillInGlobalACMEDefaults(issuer certmagic.Issuer, options map[string]any) e if globalCertLifetime != nil && acmeIssuer.CertificateLifetime == 0 { acmeIssuer.CertificateLifetime = globalCertLifetime.(caddy.Duration) } + // apply global resolvers if DNS challenge is configured and resolvers are not already set + globalResolvers := options["resolvers"] + if globalResolvers != nil && acmeIssuer.Challenges != nil && acmeIssuer.Challenges.DNS != nil { + // Check if DNS challenge is actually configured + hasDNSChallenge := globalACMEDNSok || acmeIssuer.Challenges.DNS.ProviderRaw != nil + if hasDNSChallenge && len(acmeIssuer.Challenges.DNS.Resolvers) == 0 { + acmeIssuer.Challenges.DNS.Resolvers = globalResolvers.([]string) + } + } return nil } diff --git a/caddytest/integration/caddyfile_adapt/global_options_resolvers.caddyfiletest b/caddytest/integration/caddyfile_adapt/global_options_resolvers.caddyfiletest new file mode 100644 index 000000000..7ab0fb4a4 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/global_options_resolvers.caddyfiletest @@ -0,0 +1,77 @@ +{ + email test@example.com + dns mock + resolvers 1.1.1.1 8.8.8.8 + acme_dns +} + +example.com { +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "example.com" + ] + } + ], + "terminal": true + } + ] + } + } + }, + "tls": { + "automation": { + "policies": [ + { + "issuers": [ + { + "challenges": { + "dns": { + "resolvers": [ + "1.1.1.1", + "8.8.8.8" + ] + } + }, + "email": "test@example.com", + "module": "acme" + }, + { + "ca": "https://acme.zerossl.com/v2/DV90", + "challenges": { + "dns": { + "resolvers": [ + "1.1.1.1", + "8.8.8.8" + ] + } + }, + "email": "test@example.com", + "module": "acme" + } + ] + } + ] + }, + "dns": { + "name": "mock" + }, + "resolvers": [ + "1.1.1.1", + "8.8.8.8" + ] + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/global_options_resolvers_http_challenge.caddyfiletest b/caddytest/integration/caddyfile_adapt/global_options_resolvers_http_challenge.caddyfiletest new file mode 100644 index 000000000..7b6c9de42 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/global_options_resolvers_http_challenge.caddyfiletest @@ -0,0 +1,38 @@ +{ + resolvers 1.1.1.1 8.8.8.8 +} + +example.com { +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "example.com" + ] + } + ], + "terminal": true + } + ] + } + } + }, + "tls": { + "resolvers": [ + "1.1.1.1", + "8.8.8.8" + ] + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/global_options_resolvers_local_dns_inherit.caddyfiletest b/caddytest/integration/caddyfile_adapt/global_options_resolvers_local_dns_inherit.caddyfiletest new file mode 100644 index 000000000..e715a2894 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/global_options_resolvers_local_dns_inherit.caddyfiletest @@ -0,0 +1,72 @@ +{ + email test@example.com + dns mock + resolvers 1.1.1.1 8.8.8.8 +} + +example.com { + tls { + dns mock + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "example.com" + ] + } + ], + "terminal": true + } + ] + } + } + }, + "tls": { + "automation": { + "policies": [ + { + "subjects": [ + "example.com" + ], + "issuers": [ + { + "challenges": { + "dns": { + "provider": { + "name": "mock" + }, + "resolvers": [ + "1.1.1.1", + "8.8.8.8" + ] + } + }, + "email": "test@example.com", + "module": "acme" + } + ] + } + ] + }, + "dns": { + "name": "mock" + }, + "resolvers": [ + "1.1.1.1", + "8.8.8.8" + ] + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/global_options_resolvers_local_override.caddyfiletest b/caddytest/integration/caddyfile_adapt/global_options_resolvers_local_override.caddyfiletest new file mode 100644 index 000000000..f78301c4d --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/global_options_resolvers_local_override.caddyfiletest @@ -0,0 +1,98 @@ +{ + email test@example.com + dns mock + resolvers 1.1.1.1 8.8.8.8 + acme_dns +} + +example.com { + tls { + resolvers 9.9.9.9 + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "example.com" + ] + } + ], + "terminal": true + } + ] + } + } + }, + "tls": { + "automation": { + "policies": [ + { + "subjects": [ + "example.com" + ], + "issuers": [ + { + "challenges": { + "dns": { + "resolvers": [ + "9.9.9.9" + ] + } + }, + "email": "test@example.com", + "module": "acme" + } + ] + }, + { + "issuers": [ + { + "challenges": { + "dns": { + "resolvers": [ + "1.1.1.1", + "8.8.8.8" + ] + } + }, + "email": "test@example.com", + "module": "acme" + }, + { + "ca": "https://acme.zerossl.com/v2/DV90", + "challenges": { + "dns": { + "resolvers": [ + "1.1.1.1", + "8.8.8.8" + ] + } + }, + "email": "test@example.com", + "module": "acme" + } + ] + } + ] + }, + "dns": { + "name": "mock" + }, + "resolvers": [ + "1.1.1.1", + "8.8.8.8" + ] + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/global_options_resolvers_mixed.caddyfiletest b/caddytest/integration/caddyfile_adapt/global_options_resolvers_mixed.caddyfiletest new file mode 100644 index 000000000..96996a437 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/global_options_resolvers_mixed.caddyfiletest @@ -0,0 +1,112 @@ +{ + email test@example.com + dns mock + resolvers 1.1.1.1 8.8.8.8 + acme_dns +} + +site1.example.com { +} + +site2.example.com { + tls { + resolvers 9.9.9.9 8.8.4.4 + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "site1.example.com" + ] + } + ], + "terminal": true + }, + { + "match": [ + { + "host": [ + "site2.example.com" + ] + } + ], + "terminal": true + } + ] + } + } + }, + "tls": { + "automation": { + "policies": [ + { + "subjects": [ + "site2.example.com" + ], + "issuers": [ + { + "challenges": { + "dns": { + "resolvers": [ + "9.9.9.9", + "8.8.4.4" + ] + } + }, + "email": "test@example.com", + "module": "acme" + } + ] + }, + { + "issuers": [ + { + "challenges": { + "dns": { + "resolvers": [ + "1.1.1.1", + "8.8.8.8" + ] + } + }, + "email": "test@example.com", + "module": "acme" + }, + { + "ca": "https://acme.zerossl.com/v2/DV90", + "challenges": { + "dns": { + "resolvers": [ + "1.1.1.1", + "8.8.8.8" + ] + } + }, + "email": "test@example.com", + "module": "acme" + } + ] + } + ] + }, + "dns": { + "name": "mock" + }, + "resolvers": [ + "1.1.1.1", + "8.8.8.8" + ] + } + } +} diff --git a/modules/caddytls/tls.go b/modules/caddytls/tls.go index 7b49c0208..b2b811631 100644 --- a/modules/caddytls/tls.go +++ b/modules/caddytls/tls.go @@ -125,6 +125,12 @@ type TLS struct { DNSRaw json.RawMessage `json:"dns,omitempty" caddy:"namespace=dns.providers inline_key=name"` dns any // technically, it should be any/all of the libdns interfaces (RecordSetter, RecordAppender, etc.) + // The default DNS resolvers to use when performing DNS queries for ACME DNS challenges. + // If not specified, the system default resolvers will be used. + // + // EXPERIMENTAL: Subject to change. + Resolvers []string `json:"resolvers,omitempty"` + certificateLoaders []CertificateLoader automateNames map[string]struct{} ctx caddy.Context From 83175d6d59a160c9ed680190cb0124d146b4bfeb Mon Sep 17 00:00:00 2001 From: Pavel Siomachkin Date: Sun, 9 Nov 2025 22:20:47 +0100 Subject: [PATCH 2/4] Apply global DNS resolvers to reverse proxy and ACME server modules --- .../caddyhttp/reverseproxy/httptransport.go | 28 +++++++++ modules/caddyhttp/reverseproxy/upstreams.go | 57 +++++++++++++++++++ modules/caddypki/acmeserver/acmeserver.go | 15 ++++- 3 files changed, 99 insertions(+), 1 deletion(-) diff --git a/modules/caddyhttp/reverseproxy/httptransport.go b/modules/caddyhttp/reverseproxy/httptransport.go index 1e4cfa743..18d4f02b6 100644 --- a/modules/caddyhttp/reverseproxy/httptransport.go +++ b/modules/caddyhttp/reverseproxy/httptransport.go @@ -269,6 +269,34 @@ func (h *HTTPTransport) NewTransport(caddyCtx caddy.Context) (*http.Transport, e return d.DialContext(ctx, addr.Network, addr.JoinHostPort(0)) }, } + } else { + // If no local resolver is configured, check for global resolvers from TLS app + tlsAppIface, err := caddyCtx.App("tls") + if err == nil { + tlsApp := tlsAppIface.(*caddytls.TLS) + if len(tlsApp.Resolvers) > 0 { + // Create UpstreamResolver from global resolvers + h.Resolver = &UpstreamResolver{ + Addresses: tlsApp.Resolvers, + } + err := h.Resolver.ParseAddresses() + if err != nil { + return nil, err + } + d := &net.Dialer{ + Timeout: time.Duration(h.DialTimeout), + FallbackDelay: time.Duration(h.FallbackDelay), + } + dialer.Resolver = &net.Resolver{ + PreferGo: true, + Dial: func(ctx context.Context, _, _ string) (net.Conn, error) { + //nolint:gosec + addr := h.Resolver.netAddrs[weakrand.Intn(len(h.Resolver.netAddrs))] + return d.DialContext(ctx, addr.Network, addr.JoinHostPort(0)) + }, + } + } + } } dialContext := func(ctx context.Context, network, address string) (net.Conn, error) { diff --git a/modules/caddyhttp/reverseproxy/upstreams.go b/modules/caddyhttp/reverseproxy/upstreams.go index e9eb7e60a..d9eb1e9f7 100644 --- a/modules/caddyhttp/reverseproxy/upstreams.go +++ b/modules/caddyhttp/reverseproxy/upstreams.go @@ -15,6 +15,7 @@ import ( "go.uber.org/zap/zapcore" "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/modules/caddytls" ) func init() { @@ -106,6 +107,34 @@ func (su *SRVUpstreams) Provision(ctx caddy.Context) error { return d.DialContext(ctx, addr.Network, addr.JoinHostPort(0)) }, } + } else { + // If no local resolver is configured, check for global resolvers from TLS app + tlsAppIface, err := ctx.App("tls") + if err == nil { + tlsApp := tlsAppIface.(*caddytls.TLS) + if len(tlsApp.Resolvers) > 0 { + // Create UpstreamResolver from global resolvers + su.Resolver = &UpstreamResolver{ + Addresses: tlsApp.Resolvers, + } + err := su.Resolver.ParseAddresses() + if err != nil { + return err + } + d := &net.Dialer{ + Timeout: time.Duration(su.DialTimeout), + FallbackDelay: time.Duration(su.FallbackDelay), + } + su.resolver = &net.Resolver{ + PreferGo: true, + Dial: func(ctx context.Context, _, _ string) (net.Conn, error) { + //nolint:gosec + addr := su.Resolver.netAddrs[weakrand.Intn(len(su.Resolver.netAddrs))] + return d.DialContext(ctx, addr.Network, addr.JoinHostPort(0)) + }, + } + } + } } if su.resolver == nil { su.resolver = net.DefaultResolver @@ -326,6 +355,34 @@ func (au *AUpstreams) Provision(ctx caddy.Context) error { return d.DialContext(ctx, addr.Network, addr.JoinHostPort(0)) }, } + } else { + // If no local resolver is configured, check for global resolvers from TLS app + tlsAppIface, err := ctx.App("tls") + if err == nil { + tlsApp := tlsAppIface.(*caddytls.TLS) + if len(tlsApp.Resolvers) > 0 { + // Create UpstreamResolver from global resolvers + au.Resolver = &UpstreamResolver{ + Addresses: tlsApp.Resolvers, + } + err := au.Resolver.ParseAddresses() + if err != nil { + return err + } + d := &net.Dialer{ + Timeout: time.Duration(au.DialTimeout), + FallbackDelay: time.Duration(au.FallbackDelay), + } + au.resolver = &net.Resolver{ + PreferGo: true, + Dial: func(ctx context.Context, _, _ string) (net.Conn, error) { + //nolint:gosec + addr := au.Resolver.netAddrs[weakrand.Intn(len(au.Resolver.netAddrs))] + return d.DialContext(ctx, addr.Network, addr.JoinHostPort(0)) + }, + } + } + } } if au.resolver == nil { au.resolver = net.DefaultResolver diff --git a/modules/caddypki/acmeserver/acmeserver.go b/modules/caddypki/acmeserver/acmeserver.go index aeb4eab8e..cceadc401 100644 --- a/modules/caddypki/acmeserver/acmeserver.go +++ b/modules/caddypki/acmeserver/acmeserver.go @@ -40,6 +40,7 @@ import ( "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/modules/caddyhttp" "github.com/caddyserver/caddy/v2/modules/caddypki" + "github.com/caddyserver/caddy/v2/modules/caddytls" ) func init() { @@ -287,7 +288,19 @@ func (ash Handler) openDatabase() (*db.AuthDB, error) { // makeClient creates an ACME client which will use a custom // resolver instead of net.DefaultResolver. func (ash Handler) makeClient() (acme.Client, error) { - for _, v := range ash.Resolvers { + // If no local resolvers are configured, check for global resolvers from TLS app + resolversToUse := ash.Resolvers + if len(resolversToUse) == 0 { + tlsAppIface, err := ash.ctx.App("tls") + if err == nil { + tlsApp := tlsAppIface.(*caddytls.TLS) + if len(tlsApp.Resolvers) > 0 { + resolversToUse = tlsApp.Resolvers + } + } + } + + for _, v := range resolversToUse { addr, err := caddy.ParseNetworkAddressWithDefaults(v, "udp", 53) if err != nil { return nil, err From 1f47357e622dbe235fa60e8e9b14c124198e8369 Mon Sep 17 00:00:00 2001 From: Pavel Siomachkin Date: Fri, 21 Nov 2025 18:55:17 +0100 Subject: [PATCH 3/4] Rename resolvers to tls_resolvers and limit scope to TLS/ACME operations only --- caddyconfig/httpcaddyfile/options.go | 4 +- caddyconfig/httpcaddyfile/tlsapp.go | 4 +- .../global_options_resolvers.caddyfiletest | 2 +- ...ons_resolvers_http_challenge.caddyfiletest | 2 +- ..._resolvers_local_dns_inherit.caddyfiletest | 2 +- ...ons_resolvers_local_override.caddyfiletest | 2 +- ...obal_options_resolvers_mixed.caddyfiletest | 2 +- .../caddyhttp/reverseproxy/httptransport.go | 28 --------- modules/caddyhttp/reverseproxy/upstreams.go | 57 ------------------- modules/caddytls/tls.go | 3 +- 10 files changed, 11 insertions(+), 95 deletions(-) diff --git a/caddyconfig/httpcaddyfile/options.go b/caddyconfig/httpcaddyfile/options.go index a68d54dc4..2262864ac 100644 --- a/caddyconfig/httpcaddyfile/options.go +++ b/caddyconfig/httpcaddyfile/options.go @@ -64,7 +64,7 @@ func init() { RegisterGlobalOption("preferred_chains", parseOptPreferredChains) RegisterGlobalOption("persist_config", parseOptPersistConfig) RegisterGlobalOption("dns", parseOptDNS) - RegisterGlobalOption("resolvers", parseOptResolvers) + RegisterGlobalOption("tls_resolvers", parseOptTLSResolvers) RegisterGlobalOption("ech", parseOptECH) } @@ -306,7 +306,7 @@ func parseOptSingleString(d *caddyfile.Dispenser, _ any) (any, error) { return val, nil } -func parseOptResolvers(d *caddyfile.Dispenser, _ any) (any, error) { +func parseOptTLSResolvers(d *caddyfile.Dispenser, _ any) (any, error) { d.Next() // consume option name resolvers := d.RemainingArgs() if len(resolvers) == 0 { diff --git a/caddyconfig/httpcaddyfile/tlsapp.go b/caddyconfig/httpcaddyfile/tlsapp.go index a126d7d10..6682aedf7 100644 --- a/caddyconfig/httpcaddyfile/tlsapp.go +++ b/caddyconfig/httpcaddyfile/tlsapp.go @@ -363,7 +363,7 @@ func (st ServerType) buildTLSApp( } // set up "global" (to the TLS app) DNS resolvers config - if globalResolvers, ok := options["resolvers"]; ok && globalResolvers != nil { + if globalResolvers, ok := options["tls_resolvers"]; ok && globalResolvers != nil { tlsApp.Resolvers = globalResolvers.([]string) } @@ -630,7 +630,7 @@ func fillInGlobalACMEDefaults(issuer certmagic.Issuer, options map[string]any) e acmeIssuer.CertificateLifetime = globalCertLifetime.(caddy.Duration) } // apply global resolvers if DNS challenge is configured and resolvers are not already set - globalResolvers := options["resolvers"] + globalResolvers := options["tls_resolvers"] if globalResolvers != nil && acmeIssuer.Challenges != nil && acmeIssuer.Challenges.DNS != nil { // Check if DNS challenge is actually configured hasDNSChallenge := globalACMEDNSok || acmeIssuer.Challenges.DNS.ProviderRaw != nil diff --git a/caddytest/integration/caddyfile_adapt/global_options_resolvers.caddyfiletest b/caddytest/integration/caddyfile_adapt/global_options_resolvers.caddyfiletest index 7ab0fb4a4..7043b5da3 100644 --- a/caddytest/integration/caddyfile_adapt/global_options_resolvers.caddyfiletest +++ b/caddytest/integration/caddyfile_adapt/global_options_resolvers.caddyfiletest @@ -1,7 +1,7 @@ { email test@example.com dns mock - resolvers 1.1.1.1 8.8.8.8 + tls_resolvers 1.1.1.1 8.8.8.8 acme_dns } diff --git a/caddytest/integration/caddyfile_adapt/global_options_resolvers_http_challenge.caddyfiletest b/caddytest/integration/caddyfile_adapt/global_options_resolvers_http_challenge.caddyfiletest index 7b6c9de42..d375dc711 100644 --- a/caddytest/integration/caddyfile_adapt/global_options_resolvers_http_challenge.caddyfiletest +++ b/caddytest/integration/caddyfile_adapt/global_options_resolvers_http_challenge.caddyfiletest @@ -1,5 +1,5 @@ { - resolvers 1.1.1.1 8.8.8.8 + tls_resolvers 1.1.1.1 8.8.8.8 } example.com { diff --git a/caddytest/integration/caddyfile_adapt/global_options_resolvers_local_dns_inherit.caddyfiletest b/caddytest/integration/caddyfile_adapt/global_options_resolvers_local_dns_inherit.caddyfiletest index e715a2894..20385f84b 100644 --- a/caddytest/integration/caddyfile_adapt/global_options_resolvers_local_dns_inherit.caddyfiletest +++ b/caddytest/integration/caddyfile_adapt/global_options_resolvers_local_dns_inherit.caddyfiletest @@ -1,7 +1,7 @@ { email test@example.com dns mock - resolvers 1.1.1.1 8.8.8.8 + tls_resolvers 1.1.1.1 8.8.8.8 } example.com { diff --git a/caddytest/integration/caddyfile_adapt/global_options_resolvers_local_override.caddyfiletest b/caddytest/integration/caddyfile_adapt/global_options_resolvers_local_override.caddyfiletest index f78301c4d..27f7d09d3 100644 --- a/caddytest/integration/caddyfile_adapt/global_options_resolvers_local_override.caddyfiletest +++ b/caddytest/integration/caddyfile_adapt/global_options_resolvers_local_override.caddyfiletest @@ -1,7 +1,7 @@ { email test@example.com dns mock - resolvers 1.1.1.1 8.8.8.8 + tls_resolvers 1.1.1.1 8.8.8.8 acme_dns } diff --git a/caddytest/integration/caddyfile_adapt/global_options_resolvers_mixed.caddyfiletest b/caddytest/integration/caddyfile_adapt/global_options_resolvers_mixed.caddyfiletest index 96996a437..3a4b5571c 100644 --- a/caddytest/integration/caddyfile_adapt/global_options_resolvers_mixed.caddyfiletest +++ b/caddytest/integration/caddyfile_adapt/global_options_resolvers_mixed.caddyfiletest @@ -1,7 +1,7 @@ { email test@example.com dns mock - resolvers 1.1.1.1 8.8.8.8 + tls_resolvers 1.1.1.1 8.8.8.8 acme_dns } diff --git a/modules/caddyhttp/reverseproxy/httptransport.go b/modules/caddyhttp/reverseproxy/httptransport.go index 18d4f02b6..1e4cfa743 100644 --- a/modules/caddyhttp/reverseproxy/httptransport.go +++ b/modules/caddyhttp/reverseproxy/httptransport.go @@ -269,34 +269,6 @@ func (h *HTTPTransport) NewTransport(caddyCtx caddy.Context) (*http.Transport, e return d.DialContext(ctx, addr.Network, addr.JoinHostPort(0)) }, } - } else { - // If no local resolver is configured, check for global resolvers from TLS app - tlsAppIface, err := caddyCtx.App("tls") - if err == nil { - tlsApp := tlsAppIface.(*caddytls.TLS) - if len(tlsApp.Resolvers) > 0 { - // Create UpstreamResolver from global resolvers - h.Resolver = &UpstreamResolver{ - Addresses: tlsApp.Resolvers, - } - err := h.Resolver.ParseAddresses() - if err != nil { - return nil, err - } - d := &net.Dialer{ - Timeout: time.Duration(h.DialTimeout), - FallbackDelay: time.Duration(h.FallbackDelay), - } - dialer.Resolver = &net.Resolver{ - PreferGo: true, - Dial: func(ctx context.Context, _, _ string) (net.Conn, error) { - //nolint:gosec - addr := h.Resolver.netAddrs[weakrand.Intn(len(h.Resolver.netAddrs))] - return d.DialContext(ctx, addr.Network, addr.JoinHostPort(0)) - }, - } - } - } } dialContext := func(ctx context.Context, network, address string) (net.Conn, error) { diff --git a/modules/caddyhttp/reverseproxy/upstreams.go b/modules/caddyhttp/reverseproxy/upstreams.go index d9eb1e9f7..e9eb7e60a 100644 --- a/modules/caddyhttp/reverseproxy/upstreams.go +++ b/modules/caddyhttp/reverseproxy/upstreams.go @@ -15,7 +15,6 @@ import ( "go.uber.org/zap/zapcore" "github.com/caddyserver/caddy/v2" - "github.com/caddyserver/caddy/v2/modules/caddytls" ) func init() { @@ -107,34 +106,6 @@ func (su *SRVUpstreams) Provision(ctx caddy.Context) error { return d.DialContext(ctx, addr.Network, addr.JoinHostPort(0)) }, } - } else { - // If no local resolver is configured, check for global resolvers from TLS app - tlsAppIface, err := ctx.App("tls") - if err == nil { - tlsApp := tlsAppIface.(*caddytls.TLS) - if len(tlsApp.Resolvers) > 0 { - // Create UpstreamResolver from global resolvers - su.Resolver = &UpstreamResolver{ - Addresses: tlsApp.Resolvers, - } - err := su.Resolver.ParseAddresses() - if err != nil { - return err - } - d := &net.Dialer{ - Timeout: time.Duration(su.DialTimeout), - FallbackDelay: time.Duration(su.FallbackDelay), - } - su.resolver = &net.Resolver{ - PreferGo: true, - Dial: func(ctx context.Context, _, _ string) (net.Conn, error) { - //nolint:gosec - addr := su.Resolver.netAddrs[weakrand.Intn(len(su.Resolver.netAddrs))] - return d.DialContext(ctx, addr.Network, addr.JoinHostPort(0)) - }, - } - } - } } if su.resolver == nil { su.resolver = net.DefaultResolver @@ -355,34 +326,6 @@ func (au *AUpstreams) Provision(ctx caddy.Context) error { return d.DialContext(ctx, addr.Network, addr.JoinHostPort(0)) }, } - } else { - // If no local resolver is configured, check for global resolvers from TLS app - tlsAppIface, err := ctx.App("tls") - if err == nil { - tlsApp := tlsAppIface.(*caddytls.TLS) - if len(tlsApp.Resolvers) > 0 { - // Create UpstreamResolver from global resolvers - au.Resolver = &UpstreamResolver{ - Addresses: tlsApp.Resolvers, - } - err := au.Resolver.ParseAddresses() - if err != nil { - return err - } - d := &net.Dialer{ - Timeout: time.Duration(au.DialTimeout), - FallbackDelay: time.Duration(au.FallbackDelay), - } - au.resolver = &net.Resolver{ - PreferGo: true, - Dial: func(ctx context.Context, _, _ string) (net.Conn, error) { - //nolint:gosec - addr := au.Resolver.netAddrs[weakrand.Intn(len(au.Resolver.netAddrs))] - return d.DialContext(ctx, addr.Network, addr.JoinHostPort(0)) - }, - } - } - } } if au.resolver == nil { au.resolver = net.DefaultResolver diff --git a/modules/caddytls/tls.go b/modules/caddytls/tls.go index b2b811631..c64753a85 100644 --- a/modules/caddytls/tls.go +++ b/modules/caddytls/tls.go @@ -125,7 +125,8 @@ type TLS struct { DNSRaw json.RawMessage `json:"dns,omitempty" caddy:"namespace=dns.providers inline_key=name"` dns any // technically, it should be any/all of the libdns interfaces (RecordSetter, RecordAppender, etc.) - // The default DNS resolvers to use when performing DNS queries for ACME DNS challenges. + // The default DNS resolvers to use for TLS-related DNS operations, specifically + // for ACME DNS challenges and ACME server DNS validations. // If not specified, the system default resolvers will be used. // // EXPERIMENTAL: Subject to change. From 6784138bb189fa1b7b7c4851452dc4a6a03ba8fa Mon Sep 17 00:00:00 2001 From: Pavel Siomachkin Date: Fri, 21 Nov 2025 19:07:02 +0100 Subject: [PATCH 4/4] Update test to use tls_resolvers instead of resolvers --- caddyconfig/httpcaddyfile/options_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/caddyconfig/httpcaddyfile/options_test.go b/caddyconfig/httpcaddyfile/options_test.go index eb10efe4f..524187f30 100644 --- a/caddyconfig/httpcaddyfile/options_test.go +++ b/caddyconfig/httpcaddyfile/options_test.go @@ -75,7 +75,7 @@ func TestGlobalResolversOption(t *testing.T) { { name: "single resolver", input: `{ - resolvers 1.1.1.1 + tls_resolvers 1.1.1.1 } example.com { }`, @@ -85,7 +85,7 @@ func TestGlobalResolversOption(t *testing.T) { { name: "two resolvers", input: `{ - resolvers 1.1.1.1 8.8.8.8 + tls_resolvers 1.1.1.1 8.8.8.8 } example.com { }`, @@ -95,7 +95,7 @@ func TestGlobalResolversOption(t *testing.T) { { name: "multiple resolvers", input: `{ - resolvers 1.1.1.1 8.8.8.8 9.9.9.9 + tls_resolvers 1.1.1.1 8.8.8.8 9.9.9.9 } example.com { }`,