Secure, Redundant DNS Using CoreDNS

Although an increasing portion of the web is adopting HTTPS, a core part of the web infrastrcture lags behind in the form of plaintext DNS. The authentication problem of DNS is resolved by DNSSEC, but the queries are still plaintext. There’s currently a number of competing solutions that offer both authenticity and privacy concerns, namely DNS-over-TLS, DNS-over-HTTPS, and DNSCrypt. I’ll leave it to Cloudflare’s blog to explain DNS, DoT and DoH, and the work towards both: DNS Encryption Explained. Due to operating systems not yet supporting encrypted DNS resolvers, some work needs to be done by the user, and this post describes part of the work using CoreDNS.

CoreDNS is pluggable DNS and service discovery server written in Go and the blessed DNS resolver of Kubernetes. Configuring CoreDNS is handled via setting up a series of middlewares in the Corefile, which is what its config file is called. The CoreDNS Manual page does a brilliant job describing what CoreDNS is, how it works, how to install it, the structure of the configuration file, and more. To build our secure resolver, let’s pick up one of the examples off that page and gradually build the enhanced version.

Start by creating a file named Corefile containing the following, which is lifted verbatim from CoreDNS website:

1
2
3
4
. {
    forward . 8.8.8.8 9.9.9.9
    log
}

This is very simple, but let’s break it apart. The . means listen on port 53, the default port for UDP DNS, and across all interfaces. The configuration instructs CoreDNS to forward all requests to either 8.8.8.8 (Google DNS) or 9.9.9.9 (Quad9), picking either of them at random, and logs everything to stdout. This is not secure yet. This setup will forward all DNS queries in plaintext UDP packets. Let’s start by forwarding everything to Google DNS over TLS resolvers without fallback:

1
2
3
4
5
6
7
8
. {
    forward . tls://8.8.8.8 tls://8.8.4.4 {
		# This tells CoreDNS the subject name
		# on the certificate is: dns.google
		tls_servername dns.google
	}
    log
}

At this point the setup has redundancy against Google’s services only. What if Google DNS service is down? The possibiity of a service going down is not far fetched, and we must account for it. However, note how the tls_servername directive can only be defined once, so it is bound to whatever upstream IP address we’re using. We can get around this by breaking the upstream into more local resolvers. I’ll add Cloudflare DNS as backup:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
.:53 {
	forward . 127.0.0.1:5301 127.0.0.1:5302 [::1]:5301 [::1]:5302
	log
}
.:5301 {
	forward . tls://8.8.8.8 tls://8.8.4.4 {
		tls_servername dns.google
	}
}
.:5302 {
	forward . tls://1.1.1.1 tls://1.0.0.1 {
		tls_servername cloudflare-dns.com
	}
}

Now we have established the pattern. To add more backups, add more server blocks where the upstream is defined and the respective tls_servername is configured, and add the localhost IP address to the main server of .:53. This can be further enhanced by adding caching, prefetching, and loading the hosts file. The final setup will look like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
.:53 {
	hosts /path/to/hosts {
		fallthrough
	}
	cache {
		prefetch 2 30m
	}
	forward . 127.0.0.1:5301 127.0.0.1:5302 [::1]:5301 [::1]:5302 127.0.0.1:5303 [::1]:5303 {
		policy random
	}
}
.:5301 {
	forward . tls://8.8.8.8 tls://8.8.4.4 {
		tls_servername dns.google
	}
}
.:5302 {
	forward . tls://1.1.1.1 tls://1.0.0.1 {
		tls_servername cloudflare-dns.com
	}
}
.:5303 {
	cache
	forward . tls://9.9.9.9 {
		tls_servername dns.quad9.net
	}
}

Note that policy directive added in the first server block. Using random is superfluous because it is the default policy. The other policies available are sequential and round_robin. Regardless of the configured policy, CoreDNS will still perform health-checks and use a healthy upstream, so your queries are still answered even if one Google and/or Cloudflare are down. Configure your operating system to use your loopback address as the DNS server, set up CoreDNS service to start at boot, and your DNS queries are secure going forward.