Using custom DNS-over-HTTPS with CoreDNS and Google Chrome

Run your own DoH with CoreDNS

I’ve been using CoreDNS on my home network infrastructure for a few years, and it’s worked really well. One of the things I use it for is translating local DNS queries into DNS-over-TLS queries, which keeps my ISP from inspecting and manipulating DNS lookups. That use case is well documented and works well.

Today I found myself wanting to override the DNS provider used by just one browser on my computer. I’m often connected to a corporate VPN that hijacks DNS and makes it difficult to connect to sites on my home network. Having a dedicated browser for these sites means I don’t need to disconnect from the VPN or mess with /etc/hosts to reach them.

I don’t think any browsers allow overriding normal DNS resolvers anymore, but both Chrome and Firefox support DNS-over-HTTPS with custom resolvers.

Here’s how to make that work with Chrome and CoreDNS:

First, you’ll need a valid TLS certificate for your resolver. I used the awesome LEGO client for Let’s Encrypt to generate one, along with some simple automation to keep it fresh. It might be possible to create your own CA and add it to your computer’s trust store; I haven’t tried that. The CN for the cert must be a valid DNS name.

The CoreDNS config should look something like this:

https://.:853 {
	tls /etc/coredns/ssl/coredns.crt /etc/coredns/ssl/coredns.key {
	 	client_auth nocert
	}
        forward . 127.0.0.1:53
#	log
}

This tells CoreDNS to listen on port 853 using the DNS-over-HTTPS protocol, and to resolve anything using the . wildcard. It then forwards those queries to itself using standard DNS. That’s it! I suggest enabling logging at first but turn it off once everything works.

Note: CoreDNS supports RFC 8484 queries only, not Google’s JSON query language.

You can test that it works with cURL:

curl -i 'https://you-resolver-fqdn:853/dns-query?dns=AAABAAABAAAAAAAAA3d3dwdleGFtcGxlA2NvbQAAAQAB'
HTTP/1.1 200 OK
Cache-Control: max-age=3455.000000
Content-Length: 64
Content-Type: application/dns-message
Date: Thu, 17 Sep 2020 16:56:23 GMT

Warning: Binary output can mess up your terminal. Use "--output -" to tell
Warning: curl to output it to your terminal anyway, or consider "--output
Warning: <FILE>" to save to a file.

The 200 OK response is what you’re looking for.

If you get a certificate validation problem, don’t ignore it with -k. Fix that before you continue.

The query above will resolve example.com. (The base64 is an encoded DNS query.) If you have logging enabled in CoreDNS, you should see that query in your logs.

Now you can configure it in Chrome:

In Chrome 85, the setting lives in Preferences under Privacy and security, Security, Advanced, Use secure DNS. Turn it on, select the custom provider, and enter your resolver like so:

https://your-resolver-fqdn:853/dns-query

It’s the last bit that took me forever to figure out. RFC 8484 specifies the /dns-query path, but Chrome doesn’t append it implicitly.

If Chrome is satisfied, that’s it. If it isn’t, you’ll see Please verify that this is a valid provider or try again later. If you see that after following the advice here, I’m not sure what to suggest, but it’s most likely a problem with the cert, or Chrome can’t resolve the resolver itself (try using /etc/hosts), or CoreDNS isn’t resolving correctly (check the logs, try cURL).

Since DoH uses standard HTTPS, you can do further troubleshooting with mitmproxy in reverse proxy mode, using the same cert/key as your resolver.

I tried this with Firefox and I never got it working. Firefox 80.0 accepts a custom DoH config and then silently refuses to use it. I didn’t put Firefox under the mitmproxy microscope, however, since getting it to work with Chrome was enough for me.