diff --git a/caddyconfig/httpcaddyfile/builtins.go b/caddyconfig/httpcaddyfile/builtins.go index 71aa0c2b8..228a64d38 100644 --- a/caddyconfig/httpcaddyfile/builtins.go +++ b/caddyconfig/httpcaddyfile/builtins.go @@ -481,7 +481,7 @@ func parseTLS(h Helper) ([]ConfigValue, error) { // Validate DNS challenge config: any DNS challenge option except "dns" requires a DNS provider if acmeIssuer != nil && acmeIssuer.Challenges != nil && acmeIssuer.Challenges.DNS != nil { dnsCfg := acmeIssuer.Challenges.DNS - providerSet := dnsCfg.ProviderRaw != nil || h.Option("dns") != nil + providerSet := dnsCfg.ProviderRaw != nil || h.Option("dns") != nil || h.Option("acme_dns") != nil if len(dnsOptionsSet) > 0 && !providerSet { return nil, h.Errf( "setting DNS challenge options [%s] requires a DNS provider (set with the 'dns' subdirective or 'acme_dns' global option)", diff --git a/caddyconfig/httpcaddyfile/tlsapp.go b/caddyconfig/httpcaddyfile/tlsapp.go index 1023a9d21..c5ff3bb23 100644 --- a/caddyconfig/httpcaddyfile/tlsapp.go +++ b/caddyconfig/httpcaddyfile/tlsapp.go @@ -564,23 +564,22 @@ func fillInGlobalACMEDefaults(issuer certmagic.Issuer, options map[string]any) e if globalACMECARoot != nil && !slices.Contains(acmeIssuer.TrustedRootsPEMFiles, globalACMECARoot.(string)) { acmeIssuer.TrustedRootsPEMFiles = append(acmeIssuer.TrustedRootsPEMFiles, globalACMECARoot.(string)) } - if globalACMEDNSok { + if globalACMEDNSok && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.DNS == nil || acmeIssuer.Challenges.DNS.ProviderRaw == nil) { globalDNS := options["dns"] - if globalDNS != nil { - // If global `dns` is set, do NOT set provider in issuer, just set empty dns config - acmeIssuer.Challenges = &caddytls.ChallengesConfig{ - DNS: &caddytls.DNSChallengeConfig{}, - } - } else if globalACMEDNS != nil { - // Set a global DNS provider if `acme_dns` is set and `dns` is NOT set - acmeIssuer.Challenges = &caddytls.ChallengesConfig{ - DNS: &caddytls.DNSChallengeConfig{ - ProviderRaw: caddyconfig.JSONModuleObject(globalACMEDNS, "name", globalACMEDNS.(caddy.Module).CaddyModule().ID.Name(), nil), - }, - } - } else { + if globalDNS == nil && globalACMEDNS == nil { return fmt.Errorf("acme_dns specified without DNS provider config, but no provider specified with 'dns' global option") } + if acmeIssuer.Challenges == nil { + acmeIssuer.Challenges = new(caddytls.ChallengesConfig) + } + if acmeIssuer.Challenges.DNS == nil { + acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig) + } + // If global `dns` is set, do NOT set provider in issuer, just set empty dns config + if globalDNS == nil && acmeIssuer.Challenges.DNS.ProviderRaw == nil { + // Set a global DNS provider if `acme_dns` is set and `dns` is NOT set + acmeIssuer.Challenges.DNS.ProviderRaw = caddyconfig.JSONModuleObject(globalACMEDNS, "name", globalACMEDNS.(caddy.Module).CaddyModule().ID.Name(), nil) + } } if globalACMEEAB != nil && acmeIssuer.ExternalAccount == nil { acmeIssuer.ExternalAccount = globalACMEEAB.(*acme.EAB) diff --git a/caddytest/integration/caddyfile_adapt/acme_dns_configured.caddyfiletest b/caddytest/integration/caddyfile_adapt/acme_dns_configured.caddyfiletest index eaf8d0623..3f43a082a 100644 --- a/caddytest/integration/caddyfile_adapt/acme_dns_configured.caddyfiletest +++ b/caddytest/integration/caddyfile_adapt/acme_dns_configured.caddyfiletest @@ -53,6 +53,7 @@ example.com { "challenges": { "dns": { "provider": { + "argument": "foo", "name": "mock" } } diff --git a/caddytest/integration/caddyfile_adapt/tls_dns_override_acme_dns.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_dns_override_acme_dns.caddyfiletest new file mode 100644 index 000000000..2f7c6096a --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/tls_dns_override_acme_dns.caddyfiletest @@ -0,0 +1,79 @@ +{ + acme_dns mock foo +} + +localhost { + tls { + dns mock bar + resolvers 8.8.8.8 8.8.4.4 + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "terminal": true + } + ] + } + } + }, + "tls": { + "automation": { + "policies": [ + { + "subjects": [ + "localhost" + ], + "issuers": [ + { + "challenges": { + "dns": { + "provider": { + "argument": "bar", + "name": "mock" + }, + "resolvers": [ + "8.8.8.8", + "8.8.4.4" + ] + } + }, + "module": "acme" + } + ] + }, + { + "issuers": [ + { + "challenges": { + "dns": { + "provider": { + "argument": "foo", + "name": "mock" + } + } + }, + "module": "acme" + } + ] + } + ] + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/tls_dns_override_global_dns.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_dns_override_global_dns.caddyfiletest new file mode 100644 index 000000000..fba67b566 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/tls_dns_override_global_dns.caddyfiletest @@ -0,0 +1,68 @@ +{ + dns mock foo +} + +localhost { + tls { + dns mock bar + resolvers 8.8.8.8 8.8.4.4 + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "terminal": true + } + ] + } + } + }, + "tls": { + "automation": { + "policies": [ + { + "subjects": [ + "localhost" + ], + "issuers": [ + { + "challenges": { + "dns": { + "provider": { + "argument": "bar", + "name": "mock" + }, + "resolvers": [ + "8.8.8.8", + "8.8.4.4" + ] + } + }, + "module": "acme" + } + ] + } + ] + }, + "dns": { + "argument": "foo", + "name": "mock" + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/tls_dns_resolvers_with_global_provider.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_dns_resolvers_with_global_provider.caddyfiletest new file mode 100644 index 000000000..0292e8d07 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/tls_dns_resolvers_with_global_provider.caddyfiletest @@ -0,0 +1,76 @@ +{ + acme_dns mock +} + +localhost { + tls { + resolvers 8.8.8.8 8.8.4.4 + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "terminal": true + } + ] + } + } + }, + "tls": { + "automation": { + "policies": [ + { + "subjects": [ + "localhost" + ], + "issuers": [ + { + "challenges": { + "dns": { + "provider": { + "name": "mock" + }, + "resolvers": [ + "8.8.8.8", + "8.8.4.4" + ] + } + }, + "module": "acme" + } + ] + }, + { + "issuers": [ + { + "challenges": { + "dns": { + "provider": { + "name": "mock" + } + } + }, + "module": "acme" + } + ] + } + ] + } + } + } +} diff --git a/caddytest/integration/mockdns_test.go b/caddytest/integration/mockdns_test.go index 31dc4be7b..e55a6df58 100644 --- a/caddytest/integration/mockdns_test.go +++ b/caddytest/integration/mockdns_test.go @@ -15,7 +15,9 @@ func init() { } // MockDNSProvider is a mock DNS provider, for testing config with DNS modules. -type MockDNSProvider struct{} +type MockDNSProvider struct { + Argument string `json:"argument,omitempty"` // optional argument useful for testing +} // CaddyModule returns the Caddy module information. func (MockDNSProvider) CaddyModule() caddy.ModuleInfo { @@ -31,7 +33,15 @@ func (MockDNSProvider) Provision(ctx caddy.Context) error { } // UnmarshalCaddyfile sets up the module from Caddyfile tokens. -func (MockDNSProvider) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { +func (p *MockDNSProvider) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume directive name + + if d.NextArg() { + p.Argument = d.Val() + } + if d.NextArg() { + return d.Errf("unexpected argument '%s'", d.Val()) + } return nil }