If CoreDNS had existed when I wrote Alternative DNS Servers I’d have included it; it’s quite a versatile beast.

CoreDNS was created by Miek Gieben, and he tells me there was a time during which CoreDNS was actually a forked Web server doing DNS, but that changed a bit. Whilst CoreDNS has its roots in and resembles Caddy, it’s a different beast. It’s not difficult to get to know, but some of the terminology CoreDNS uses confused me: for example the term middleware: I see that as a plugin, all the more so because this program’s option to list said middleware is called … drum roll … -plugins. Another thing I needed assistance for was some of the syntax, or rather the semantics, within the configuration file.

CoreDNS is another one of those marvelous single-binary, no-dependencies Go programs which I download and run. All that’s missing is a configuration file called a Corefile. (I associate a core file with the word Corefile … #justkidding ;-)

Launching coredns as root (so that the process may bind to port 53 – use -dns.port 5353 on the command line to specify an alternative, or cap_net_bind_service with systemd) will bring up a DNS server which uses the whoami middleware to provide no response, but an additional section to queries for any domain:

$ dig @192.168.1.207 www.example.org

;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 8021
;; flags: qr aa rd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 3

;; QUESTION SECTION:
;www.example.org.		IN	A

;; ADDITIONAL SECTION:
www.example.org.	0	IN	A	192.168.1.130
_udp.www.example.org.	0	IN	SRV	0 0 60934 .

Quite useless, if you ask me, but at least I know the server’s running and it’s doing something.

The hosts middleware serves a zone from an /etc/hosts-type file, checking the file for changes and reloading the zone accordingly. A, AAAA, and PTR records are supported.

$ cat /etc/hosts
1.2.3.4         laptop.example.hosts
1.2.3.10         bigbox

$ cat Corefile
example.hosts {
        hosts /etc/hosts
}

With this configuration CoreDNS will respond authoritatively to a query for laptop.example.hosts only; the entry for bigbox is not found.

Let’s do something authoritative, and create a zone master file (in zone.db) and a Corefile:

$ cat Corefile
example.aa {
    file zone.db {
        transfer to *
        transfer to 192.168.1.130:53
    }
}

The file middleware loads the specified master zone file and serves it. That’s it. Simple. Not only that, but it also periodically checks whether the file has changed and actually reloads the zone when the SOA serial number changes. In the transfer stanza I specify that any client (*) may transfer the zone and that the host 192.168.1.130 gets a DNS NOTIFY when the zone is reloaded. (The port number on the address defaults to 53, I just show that it can be specified.) I tested NOTIFY with nsnotifyd and it works reliably.

Similar to file, the auto middleware can serve a whole directory of zone files, determining their origins using a regular expression.

The following Corefile uses the slave^H secondary middleware to create a slave zone which is transferred into RAM from the specified address. (Adding appropriate transfer to stanzas would make this secondary zone transferable by other secondaries.)

$ cat Corefile
ww.mens.de {
    secondary {
        transfer from 192.168.1.10
    }
    errors stdout
    log stdout
}

Note that the zone is served from RAM which means, in the event coredns launches and cannot contact any of its zone masters, the zone cannot be served.

If I need a forwarder, I configure it, here for the root zone, i.e. for all zones not explicitly defined within the Corefile:

$ cat Corefile
. {
    proxy . 192.168.1.10:53
}

Other middleware includes bind which overrides the address to which CoreDNS should bind to, and cache which can cap TTL values when operating as a forwarder. Middleware probably worth looking at is etcd which can read zone data from an etcd instance and kubernetes. If you’re into that sort of stuff of course.

Then there’s the dnssec middleware which promises to enable on-the-fly, a.k.a. “online”, DNSSEC signing of data with NSEC as authenticated denial of existence. In order to test this, I first create a key and then configure an authoritative zone in the Corefile which uses that key file:

$ ldns-keygen -a ECDSAP256SHA256 -k sec.aa
Ksec.aa.+013+28796

$ cat Corefile
sec.aa {
    file sec.aa
    dnssec {
        key file Ksec.aa.+013+28796
    }
}
;; ANSWER SECTION:
sec.aa.		3600 IN	DNSKEY 257 3 13 (
			meU/r4MKJ73gDanOfsiysUWn1PKDCGz6NxulydpAeFx3
			zNrepJTSVc65vJXt9koLI+PI+1uu9TadUlhEosyPjA==
			) ; KSK; alg = ECDSAP256SHA256 ; key id = 28796
sec.aa.		3600 IN	RRSIG DNSKEY 13 2 3600 (
			20170917103509 20170909073509 28796 sec.aa.
			k296ZBgiScV72AYXDuDFxBNaoZEXiBVhE57yAfgYVKYi
			nY9cmdO8tB81KX+OGA7d7V4cb6wrk876B5qRUWUZ2A== )

CoreDNS signs all records online; if I specify more than one key during configuration it signs each record with all keys.

CoreDNS binaries are provided with middleware for logging and monitoring. For example dnstap enables it to use dnstap.info’s structured binary log format, and I decide for which of the authoritative zones or proxy entries I want to log queries and responses by configuring dnstap accordingly. On the other hand, the health middleware enables an HTTP endpoint at a port you specify, and it returns a simple string if the server is alive:

$ cat Corefile
example.aa {
    health :8080
}

$ curl http://192.168.1.207:8080/health
OK

The tls middleware allows me to create a DNS-over-TLS (RFC 7858) respectively a DNS-over-gRPC (does anybody really need that?) server.

The server can act as a round-robin DNS loadbalancer, and it can provide responses to TXT queries in the CH (chaos) class:

$ cat Corefile
# define the CH "tlds"
bind {
    chaos CoreDNS-010 "Miek Gieben" miek@miek.nl
}
server {
    chaos CoreDNS-010 "Miek Gieben" miek@miek.nl
}
$ dig ...
...
;; ANSWERS:
version.bind.		0	CH	TXT	"CoreDNS-010"
version.server.		0	CH	TXT	"CoreDNS-010"
authors.bind.		0	CH	TXT	"Miek Gieben"
authors.bind.		0	CH	TXT	"miek@miek.nl"
hostname.bind.		0	CH	TXT	"t420.ww.mens.de"
id.server.		0	CH	TXT	"t420.ww.mens.de"

There are more middleware “plugins” (rewrite is fun!) and there’s also some documentation as to how to write your own.

Apparently it’s not possible to configure middleware globally. So, for example if you have two servers configured in a single Corefile (by specifying different ports, for example), both blocks need the middleware you want to share configured (documented here). This in turn means, that certain things cannot be done, e.g. dnstap into the same Unix socket.

Apropos documentation: that is, very unfortunately, a bit lacking in clarity. While the information is there, it’s presented in a form which made me pull lots of hair, and I frequently found myself grepping (is that a verb?) my way through the project’s Github issues in search of how, respectively where to write a directive in the Corefile, and Miek prodded me along, for which I thank him!

Other than that, CoreDNS is huge fun and has a lot of potential.