diff --git a/cmd/controller/app/options/options.go b/cmd/controller/app/options/options.go index c03c4632b..5689242a7 100644 --- a/cmd/controller/app/options/options.go +++ b/cmd/controller/app/options/options.go @@ -91,6 +91,8 @@ type ControllerOptions struct { ACMEDNS01CheckMethod string + DnsOverHttpsJsonEndpoint string + ClusterIssuerAmbientCredentials bool IssuerAmbientCredentials bool @@ -148,6 +150,8 @@ const ( defaultACMEDNS01CheckMethod = dnsutil.ACMEDNS01CheckViaDNSLookup + defaultDnsOverHttpsJsonEndpoint = "https://8.8.8.8/resolve" + defaultClusterResourceNamespace = "kube-system" defaultNamespace = "" @@ -338,6 +342,11 @@ func (s *ControllerOptions) AddFlags(fs *pflag.FlagSet) { "than the rest of the world (aka DNS split horizon).", dnsutil.ACMEDNS01CheckViaDNSLookup, dnsutil.ACMEDNS01CheckViaHTTPS)) + fs.StringVar(&s.DnsOverHttpsJsonEndpoint, "dns-over-https-json-endpoint", defaultDnsOverHttpsJsonEndpoint, fmt.Sprintf( + "[%s, %s] Only used when specifying \"dns-over-https\" for the \"acme-dns01-check-method\" option. "+ + "This allows specifying what JSON endpoint to use for doing the DNS-over-HTTPS verification."+ + "Examples: 'https://1.1.1.1/dns-query', 'https://8.8.8.8/resolve', ''https://8.8.4.4/resolve'. or 'https://9.9.9.9:5053/dns-query'")) + fs.StringVar(&s.ACMEHTTP01SolverResourceRequestCPU, "acme-http01-solver-resource-request-cpu", defaultACMEHTTP01SolverResourceRequestCPU, ""+ "Defines the resource request CPU size when spawning new ACME HTTP01 challenge solver pods.") diff --git a/pkg/controller/context.go b/pkg/controller/context.go index b8b75b2e6..a34a3927a 100644 --- a/pkg/controller/context.go +++ b/pkg/controller/context.go @@ -170,6 +170,14 @@ type ACMEOptions struct { // ACMEDNS01CheckMethod specifies how to check for DNS propagation for DNS01 challenges ACMEDNS01CheckMethod string + // DnsOverHttpsJsonEndpoint allows specifying what Json endpoint to use for doing the DNS-over-HTTPS verification. + // Examples: + // - "https://1.1.1.1/dns-query" + // - "https://8.8.8.8/resolve" + // - "https://8.8.4.4/resolve" + // - "https://9.9.9.9:5053/dns-query" + DnsOverHttpsJsonEndpoint string + // ACMEHTTP01SolverImage is the image to use for solving ACME HTTP01 // challenges HTTP01SolverImage string diff --git a/pkg/issuer/acme/dns/dns.go b/pkg/issuer/acme/dns/dns.go index 6e4e21f4b..6c819473b 100644 --- a/pkg/issuer/acme/dns/dns.go +++ b/pkg/issuer/acme/dns/dns.go @@ -116,7 +116,7 @@ func (s *Solver) Check(ctx context.Context, issuer v1.GenericIssuer, ch *cmacme. log.V(logf.DebugLevel).Info("checking DNS propagation", "nameservers", s.Context.DNS01Nameservers) ok, err := util.PreCheckDNS(fqdn, ch.Spec.Key, s.Context.DNS01Nameservers, - s.Context.DNS01CheckAuthoritative, s.Context.ACMEDNS01CheckMethod) + s.Context.DNS01CheckAuthoritative, s.Context.ACMEDNS01CheckMethod, s.Context.DnsOverHttpsJsonEndpoint) if err != nil { return err } diff --git a/pkg/issuer/acme/dns/util/wait.go b/pkg/issuer/acme/dns/util/wait.go index 91a1acc72..4a99ff49d 100644 --- a/pkg/issuer/acme/dns/util/wait.go +++ b/pkg/issuer/acme/dns/util/wait.go @@ -23,7 +23,7 @@ import ( ) type preCheckDNSFunc func(fqdn, value string, nameservers []string, - useAuthoritative bool, acmeDNS01CheckMethod string) (bool, error) + useAuthoritative bool, acmeDNS01CheckMethod string, dnsOverHttpsJsonEndpoint string) (bool, error) type dnsQueryFunc func(fqdn string, rtype uint16, nameservers []string, recursive bool) (in *dns.Msg, err error) var ( @@ -48,6 +48,8 @@ const ( ACMEDNS01CheckViaHTTPS = "dns-over-https" ) +const DefaultDnsOverHttpsJsonEndpoint = "https://8.8.8.8/resolve" + var defaultNameservers = []string{ "8.8.8.8:53", "8.8.4.4:53", @@ -132,14 +134,27 @@ func checkDNSPropagationWithDNSLookup(fqdn, value string, nameservers []string, return checkAuthoritativeNss(fqdn, value, authoritativeNss) } -func checkDNSPropagationWithHTTPS(fqdn, value string, nameservers []string, useAuthoritative bool) (bool, error) { +// The dnsOverHttpsJsonEndpoint has to be a JSON GET endpoint and NOT an RFC 8484 GET endpoint. +// This decision was taken because the JSON format is much easier to parse, test and debug, and is a standard +// for the big DNS-over-HTTPS DNS providers such as Google, Cloudflare, or Quad9. +// Examples: +// - "https://1.1.1.1/dns-query" +// - "https://8.8.8.8/resolve" +// - "https://8.8.4.4/resolve" +// - "https://9.9.9.9:5053/dns-query" +func checkDNSPropagationWithHTTPS(fqdn, value string, dnsOverHttpsJsonEndpoint string) (bool, error) { logf.V(logf.InfoLevel).Infof("Checking DNS propagation for FQDN %s using Google's API for DNS over HTTPS", fqdn) - req, err := http.NewRequest("GET", "https://8.8.8.8/resolve?name="+fqdn+"&type=TXT", nil) + if dnsOverHttpsJsonEndpoint == "" { + dnsOverHttpsJsonEndpoint = DefaultDnsOverHttpsJsonEndpoint + } + + req, err := http.NewRequest("GET", dnsOverHttpsJsonEndpoint+"?name="+fqdn+"&type=TXT", nil) if err != nil { return false, err } req.Header.Add("Cache-Control", "no-cache") + req.Header.Add("accept", "application/dns-json") r, err := http.DefaultClient.Do(req) if err != nil { return false, fmt.Errorf("Unable to lookup DNS via HTTPS: %s", err) @@ -158,7 +173,7 @@ func checkDNSPropagationWithHTTPS(fqdn, value string, nameservers []string, useA if resp.Status == 0 && len(resp.Answer) >= 1 { for _, answer := range resp.Answer { if txt := strings.Trim(answer.Data, "\""); txt == value { - logf.V(logf.DebugLevel).Infof("Selfchecking using the DNS-over-HTTPS Lookup method was successful") + logf.V(logf.DebugLevel).Infof("Self-checking using the DNS-over-HTTPS Lookup method was successful") return true, nil } } @@ -169,14 +184,14 @@ func checkDNSPropagationWithHTTPS(fqdn, value string, nameservers []string, useA } func checkDNSPropagation(fqdn, value string, nameservers []string, - useAuthoritative bool, acmeDNS01CheckMethod string) (bool, error) { + useAuthoritative bool, acmeDNS01CheckMethod string, dnsOverHttpsJsonEndpoint string) (bool, error) { switch acmeDNS01CheckMethod { case ACMEDNS01CheckViaDNSLookup: - logf.V(logf.DebugLevel).Infof("Selfchecking using the DNS Lookup method") + logf.V(logf.DebugLevel).Infof("Self-checking using the DNS Lookup method") return checkDNSPropagationWithDNSLookup(fqdn, value, nameservers, useAuthoritative) case ACMEDNS01CheckViaHTTPS: - logf.V(logf.DebugLevel).Infof("Selfchecking using the DNS over HTTPS Lookup method") - return checkDNSPropagationWithHTTPS(fqdn, value, nameservers, useAuthoritative) + logf.V(logf.DebugLevel).Infof("Self-checking using the DNS-over-HTTPS Lookup method") + return checkDNSPropagationWithHTTPS(fqdn, value, dnsOverHttpsJsonEndpoint) default: return false, fmt.Errorf("Unknown DNS propagation method") } diff --git a/pkg/issuer/acme/dns/util/wait_test.go b/pkg/issuer/acme/dns/util/wait_test.go index 33371a7ac..304d7d9eb 100644 --- a/pkg/issuer/acme/dns/util/wait_test.go +++ b/pkg/issuer/acme/dns/util/wait_test.go @@ -166,7 +166,7 @@ func TestMatchCAA(t *testing.T) { } func TestPreCheckDNSOverHTTPS(t *testing.T) { - ok, err := PreCheckDNS("google.com.", "v=spf1 include:_spf.google.com ~all", []string{"8.8.8.8:53"}, true, "dns-over-https") + ok, err := PreCheckDNS("google.com.", "v=spf1 include:_spf.google.com ~all", []string{"8.8.8.8:53"}, true, "dns-over-https", "https://8.8.8.8/resolve") if err != nil || !ok { t.Errorf("preCheckDNS failed for acme-staging.api.letsencrypt.org: %s", err.Error()) } @@ -174,7 +174,7 @@ func TestPreCheckDNSOverHTTPS(t *testing.T) { func TestPreCheckDNS(t *testing.T) { // TODO: find a better TXT record to use in tests - ok, err := PreCheckDNS("google.com.", "v=spf1 include:_spf.google.com ~all", []string{"8.8.8.8:53"}, true, "dnslookup") + ok, err := PreCheckDNS("google.com.", "v=spf1 include:_spf.google.com ~all", []string{"8.8.8.8:53"}, true, "dnslookup", "") if err != nil || !ok { t.Errorf("preCheckDNS failed for acme-staging.api.letsencrypt.org: %s", err.Error()) } @@ -182,7 +182,7 @@ func TestPreCheckDNS(t *testing.T) { func TestPreCheckDNSNonAuthoritative(t *testing.T) { // TODO: find a better TXT record to use in tests - ok, err := PreCheckDNS("google.com.", "v=spf1 include:_spf.google.com ~all", []string{"1.1.1.1:53"}, false, "dnslookup") + ok, err := PreCheckDNS("google.com.", "v=spf1 include:_spf.google.com ~all", []string{"1.1.1.1:53"}, false, "dnslookup", "") if err != nil || !ok { t.Errorf("preCheckDNS failed for acme-staging.api.letsencrypt.org: %s", err.Error()) }