StackExchange / dnscontrol

Infrastructure as code for DNS!
https://dnscontrol.org/
MIT License
3.14k stars 400 forks source link

DNS over HTTPS (DoH) provider not checking delegation #1402

Closed juliusrickert closed 2 years ago

juliusrickert commented 2 years ago

The DNS over HTTPS (DoH) provider is solely a registrar provider, intended to check whether the nameservers at the registrar are matching those of the zone.

But instead, it checks the zone's NS records, so it does not check whether the delegation is correct.

Example (from my list of weird DNS setups :D)

$ dig @a.gtld-servers.net vallourec.com ns
…
;; AUTHORITY SECTION:
vallourec.com.      172800  IN  NS  dns1.vallourec.fr.
vallourec.com.      172800  IN  NS  dns2.vallourec.fr.
vallourec.com.      172800  IN  NS  dns3.vallourec.fr.
…

$ dig @dns1.vallourec.fr vallourec.com ns
…
;; ANSWER SECTION:
vallourec.com.      28800   IN  NS  dns2.vallourec.fr.
vallourec.com.      28800   IN  NS  dns1.vallourec.fr.
…
dnsconfig.js
var DOH = NewRegistrar('doh', 'DNSOVERHTTPS');

var BIND = NewDnsProvider('bind', 'BIND');

D('vallourec.com', DOH, DnsProvider(BIND), [
  NAMESERVER('dns1.vallourec.fr.'),
  NAMESERVER('dns2.vallourec.fr.'),
]);
Result
$ dnscontrol push
******************** Domain: vallourec.com
----- Getting nameservers from: bind
----- DNS Provider: bind...
0 corrections
----- Registrar: doh...
0 corrections
Done. 0 corrections.
Expected
$ dnscontrol push
******************** Domain: vallourec.com
----- Getting nameservers from: bind
----- DNS Provider: bind...
0 corrections
----- Registrar: doh...
1 correction
#1: Update nameservers dns1.vallourec.fr,dns2.vallourec.fr,dns3.vallourec.fr -> dns1.vallourec.fr,dns2.vallourec.fr
FAILURE! DNS-over-HTTPS 'Registrar' is read only, changes must be applied to vallourec.com manually
Done. 1 corrections.
completed with errors
tlimoncelli commented 2 years ago

CC @mikenz

tlimoncelli commented 2 years ago

Here's my take on this:

This is a documentation issue, not a code bug. The documentation should be clear about that.

Here's my suggested change: https://github.com/StackExchange/dnscontrol/issues/1402

Thoughts?

juliusrickert commented 2 years ago

So this provider can only check that it exists a delegation that results in an authoritative nameserver returning the expected nameserver set or a nameserver set differing from the nameserver set expected.

So running DNSControl without changes expected—assuming no other state changes—depends on the resolver choosing an authoritative nameserver that provides the nameservers DNSControl expects.

Imagine I were to dual host my zone with Cloudflare (NS for Cloudflare only) and Route 53 (NS for Cloudflare and Route 53). The zone is correctly delegated to both. My dnsconfig.js only features Cloudflare, so DNSControl expects only Cloudflare to be featured in the nameserver set.

Now, depending on which delegation the resolver chooses, my run will either fail or succeed. If the resolver chooses Cloudflare the run will succeed. If it chooses Route 53 it will fail.

This issue can persist for some time when the resolver caches the nameserver set provided by the authoritative nameserver and not the one indicated by the parent zone.

tlimoncelli commented 2 years ago

According to the feature comparison chart (https://stackexchange.github.io/dnscontrol/provider-list) Cloudflare doesn't support dual homed DNS. Thus, the concern is moot. You can't get into that situation without using unsupported features. So, in theory that's the end of it.

But where would the fun in that be? So I did some experimenting...

I just tried monitoring with Google DNS + Route 53 and they do it right. The domain I used is stackoverflow.com, which is dual hosted on both.

Cloudflare, as you pointed out, does not accept delegations other than itself. [One of my top 5 reasons I dislike cloudflare.] But let's try it anyway!

See the experiment below. You are correct. It doesn't work.

I think it would be sufficient to include a disclaimer for users of Cloudflare (and similar) providers that don't officially support dual providers.

Of course, if someone wants to write a true "monitor delegations" provider that does a 100% accurate delegation check, I'm open to a PR.


Here's the experiment I ran:

/tmp/doh$ cat dnsconfig.js 
var DOH_CLOUDFLARE = NewRegistrar("cloudflare_doh", "DNSOVERHTTPS");
var DOH_GOOGLE = NewRegistrar("google_doh", "DNSOVERHTTPS");
var DOH_QUAD9 = NewRegistrar("quad9_doh", "DNSOVERHTTPS");
var DOH_CLEAN = NewRegistrar("cleanbrowsing_doh", "DNSOVERHTTPS");
var DOH_ADGUARD = NewRegistrar("adguard_doh", "DNSOVERHTTPS");
var DOH_COMCAST = NewRegistrar("comcast_doh", "DNSOVERHTTPS");
var DOH_DEFAULT = NewRegistrar("default", "DNSOVERHTTPS");

var BIND = NewDnsProvider("bind", "BIND");

// FYI: this domain is defined as follows:
// D("miniprofiler.com", REG_NAMECOM,
//    DnsProvider(DSP_CLOUDFLARE, 2),
//    DnsProvider(DSP_ROUTE53, 2),
//    ...

var DELS = [
  NAMESERVER("bayan.ns.cloudflare.com."),
  NAMESERVER("lana.ns.cloudflare.com."),
  NAMESERVER("ns-1960.awsdns-53.co.uk."),
  NAMESERVER("ns-657.awsdns-18.net."),
];

D("miniprofiler.com!cloudflare", DOH_CLOUDFLARE, DnsProvider(BIND), DELS);
D("miniprofiler.com!google", DOH_GOOGLE, DnsProvider(BIND), DELS);
D("miniprofiler.com!quad9", DOH_QUAD9, DnsProvider(BIND), DELS);
D("miniprofiler.com!clean", DOH_CLEAN, DnsProvider(BIND), DELS);
D("miniprofiler.com!adguard", DOH_ADGUARD, DnsProvider(BIND), DELS);
D("miniprofiler.com!comcast", DOH_COMCAST, DnsProvider(BIND), DELS);
D("miniprofiler.com!default", DOH_DEFAULT, DnsProvider(BIND), DELS);

/tmp/doh$ cat creds.json 
{
  "cloudflare_doh": {
    "host": "cloudflare-dns.com"
  },
  "google_doh": {
    "host": "dns.google"
  },
  "quad9_doh": {
    "host": "dns.quad9.net"
  },
  "cleanbrowsing_doh": {
    "host": "doh.cleanbrowsing.org/doh/family-filter"
  },
  "adguard_doh": {
    "host": "dns.adguard.com"
  },
  "comcast_doh": {
    "host": "doh.xfinity.com"
  },
  "default": {
  }
}

Here's the output:

/tmp/doh$ dnscontrol push 
******************** Domain: miniprofiler.com!cloudflare
----- Getting nameservers from: bind
----- DNS Provider: bind...
0 corrections
----- Registrar: cloudflare_doh...
1 correction
#1: Update nameservers bayan.ns.cloudflare.com,lana.ns.cloudflare.com -> bayan.ns.cloudflare.com,lana.ns.cloudflare.com,ns-1960.awsdns-53.co.uk,ns-657.awsdns-18.net
FAILURE! DNS-over-HTTPS 'Registrar' is read only, changes must be applied to miniprofiler.com manually
******************** Domain: miniprofiler.com!google
----- Getting nameservers from: bind
----- DNS Provider: bind...
0 corrections
----- Registrar: google_doh...
0 corrections
******************** Domain: miniprofiler.com!quad9
----- Getting nameservers from: bind
----- DNS Provider: bind...
0 corrections
----- Registrar: quad9_doh...
1 correction
#1: Update nameservers bayan.ns.cloudflare.com,lana.ns.cloudflare.com -> bayan.ns.cloudflare.com,lana.ns.cloudflare.com,ns-1960.awsdns-53.co.uk,ns-657.awsdns-18.net
FAILURE! DNS-over-HTTPS 'Registrar' is read only, changes must be applied to miniprofiler.com manually
******************** Domain: miniprofiler.com!clean
----- Getting nameservers from: bind
----- DNS Provider: bind...
0 corrections
----- Registrar: cleanbrowsing_doh...
1 correction
#1: Update nameservers bayan.ns.cloudflare.com,lana.ns.cloudflare.com -> bayan.ns.cloudflare.com,lana.ns.cloudflare.com,ns-1960.awsdns-53.co.uk,ns-657.awsdns-18.net
FAILURE! DNS-over-HTTPS 'Registrar' is read only, changes must be applied to miniprofiler.com manually
******************** Domain: miniprofiler.com!adguard
----- Getting nameservers from: bind
----- DNS Provider: bind...
0 corrections
----- Registrar: adguard_doh...
0 corrections
******************** Domain: miniprofiler.com!comcast
----- Getting nameservers from: bind
----- DNS Provider: bind...
0 corrections
----- Registrar: comcast_doh...
1 correction
#1: Update nameservers bayan.ns.cloudflare.com,lana.ns.cloudflare.com -> bayan.ns.cloudflare.com,lana.ns.cloudflare.com,ns-1960.awsdns-53.co.uk,ns-657.awsdns-18.net
FAILURE! DNS-over-HTTPS 'Registrar' is read only, changes must be applied to miniprofiler.com manually
******************** Domain: miniprofiler.com!default
----- Getting nameservers from: bind
----- DNS Provider: bind...
0 corrections
----- Registrar: default...
0 corrections
Done. 4 corrections.
completed with errors
/tmp/doh$ 
juliusrickert commented 2 years ago

That's not the point I was trying to make. Sorry for the confusion. I didn't pick Cloudflare to demonstrate that this is broken with single-homed providers, but rather to pick an example where it's clear that the zones wouldn't match.

Example dnsconfig.js:

var REG_DOH = NewRegistrar('doh', 'DNSOVERHTTPS');

var DNS_CLOUDFLARE = NewDnsProvider('cloudflare', 'CLOUDFLARE');

D('example.com', REG_DOH, DnsProvider(DNS_CLOUDFLARE, 2));

Delegation in com. zone, featuring an unexpected delegation to Route 53:

Performing dnscontrol push can lead to two states now:

1. The DoH resolver chooses a Cloudflare DNS delegation

Cloudflare will return the expected nameserver set. DNSControl will succeed.

2. The DoH resolver chooses a Route 53 delegation

Route 53 will return the expected nameservers and two additional, unexpected nameservers. DNSControl will fail.

tlimoncelli commented 2 years ago

Since @mikenz wrote the provider, I'll let him reply.