Knot DNS: One Tame and Sane Authoritative DNS Server
How to install and minimally configure Knot to act as your home lab's local domain master and slave servers.
If you were a regular viewer of the original Saturday Night Live era, you will remember the Festrunks, two lewd but naïve Czech brothers who were self-described "wild and crazy guys!" For me, Gyorg and Yortuk (plus having my binomial handed to me by tests designed by a brilliant Czech professor at the local university's high-school mathematics contests) were the extent of my knowledge of the Czech Republic.
I recently discovered something else Czech, and it's not wild and crazy at all, but quite tame and sane, open-source and easy to configure. Knot DNS is an authoritative DNS server written in 2011 by the Czech CZ.NIC organization. They wrote and continue to maintain it to serve their national top-level domain (TLD) as well as to prevent further extension of a worldwide BIND9 software monoculture across all TLDs. Knot provides a separate fast caching server and resolver library alongside its authoritative server.
Authoritative nameserver and caching/recursive nameserver functions are separated for good reason. A nameserver's query result cache can be "poisoned" by queries that forward to malicious external servers, so if you don't allow the authoritative nameserver to answer queries for other domains, it cannot be poisoned and its answers for its own domain can be trusted.
A software monoculture means running identical software like BIND9 everywhere rather than different software providing identical functionality and interoperability. This is bad for the same reasons we eventually will lose our current popular species of banana—being genetically identical, all bananas everywhere can be wiped out by a single infectious agent. As with fruit, a bit of genetic diversity in critical infrastructure is a good thing.
In this article, I describe how to install and minimally configure Knot to act as your home lab's local domain master and slave servers. I will secure zone transfer using Transaction Signatures (TSIG). Although Knot supports DNSSEC, I don't discuss it here, because I like you and want you to finish reading before we both die of old age. I assume you already know what a DNS zone file is and what it looks like.
You may download the latest version (2.8.x as I write) source tarball via
https://www.knot-dns.cz/download and build it yourself using the ubiquitous
configure
; make
; make install
incantation. I recommend running the latest
Knot version if you intend to have your resolver face the public internet. I
found that, to build Knot on Ubuntu 18.04, I needed to install the packages
pkg-config
, libgnutls28
, liburcu-dev
and libedit-dev
in addition to the
standard essential build toolkit. Knot's website has further
requirements, which CentOS 7 and Ubuntu 18.04 appear to meet. The tar extract
and build process also oddly demanded to make some hard file links. Building
as an unprivileged user was successful despite the hard link failure errors.
For my home lab, the 2.6.x package in Ubuntu "universe" and EPEL repos is adequate. To install it on Ubuntu 18.04, I enabled the "bionic universe" repo in my /etc/apt/sources.list and did this:
$ sudo apt update
$ sudo apt install knot
On CentOS 7, I would have run the commands:
$ sudo yum install epel-release
$ sudo yum install knot
I can now proceed to configure the Knot master instance. I hate reading BIND9 config files, and although I know the major distros are trying to be helpful, I particularly hate the sliced and diced versions they provide. Knot.conf is a breeze by comparison. Knot's mercifully terse single-file configuration uses YAML format. The Knot package deploys a sample configuration in /etc/knot.conf. I moved that file aside and created a fresh one using my preferred editor, vi:
$ sudo mv /etc/knot.conf /etc/knot.conf-save
$ sudo vi /etc/knot.conf
My home network is cpu-chow.local; name yours as you see fit. Since nobody but you will recognize this server as authoritative, you can choose your TLD as well. You feed this domain to caching nameservers in your home lab by configuring them to forward queries explicitly for your domain to your authoritative resolver.
The first section defines the server's presence on the host and network. Section names start at column 1, entries are indented four spaces:
server:
identity: dns1.cpu-chow.local
listen: [ 127.0.0.9@53, 172.28.1.22@53 ]
user: knot:knot
rundir: /run/knot
In the server section, I'm telling Knot its hostname and to run on port 53 of the local interface but at a different address, 127.0.0.9. Since Knot can resolve queries only for cpu-chow.local, it has little use serving from the default 127.0.0.1; besides, my host's caching nameserver is there already. I'm also directing it to listen on the host's lab network address, 172.28.1.22. My lab network is 172.28.1.0/24, and I want to offer authoritative nameservice for cpu-chow.local to the other hosts in my lab. I could instead offer the authoritative server to my host's caching server only and have that caching server listen on the lab net IP and serve all the other hosts' query needs if I desired.
Installing Knot created a user id knot, and I'm directing Knot to run as
that UID/GID for least-privilege best practice. The rundir
will be the home
of Knot's management socket. If you run multiple Knot instances, use a
unique /run directory for each.
After rundir
, I insert a blank line to signal the section's end and start
the key section to define my TSIG key. Note that id
has a dash in column 3.
That is YAML, and it means that what follows is specifically associated with
key tsigkey1.
alone:
key:
- id: tsigkey1.
Algorithm: hmac-sha256
secret: base64-keyvalue
To define another key, add another id
line
immediately after the first secret line.
This is the secret that authorized slave servers will provide when they request an administrative zone transfer (AXFR), a request to sync up with the master. I've given my arbitrarily named key a fully lowercase name followed by a period to ensure interoperability with other DNS server software like PowerDNS, which seemed always to lowercase and append a period to whatever key name I gave it when I ran it as a slave to a Knot master.
The secret is a randomly generated Base64 value. Knot includes the
keygen
utility to generate the key section for you, and you can paste the output
into your config file:
$ keymgr -t tsigkey1.
key:
- id: tsigkey1.
algorithm: hmac-sha256
secret: oxRKAUfGN3R6fGjWX/V+i4rjCl1zRuYslX0c4se+GWs=
Another blank line, and I move on to the template section, which defines common presets that can be applied in other sections. You add multiple templates just like multiple keys. The template section and the template id default must be present in knot.conf, or else Knot will emit a cryptic error message when you start it:
template:
- id: default
storage: "/var/lib/knot"
default
is a special template that is applied automatically; other templates
must be referenced in a section to apply there. I am declaring here that by
default, all files to be read are in /var/lib/knot. I intend to use
nsupdate
to manage my zones, so keeping them in /etc/knot will not be appropriate
(nsupdate
is beyond the scope of this article.)
And now I define my authorized slaves with the remote section. This session associates a host and various attributes with a name you can use later in the config:
remote:
- id: slave1
address: 127.0.0.19@53
key: tsigkey1.
- id: slave2
address: 172.28.1.20@53
key: tsigkey1.
id
is an arbitrary name. In addition to a remote slave on lab host
172.28.1.2, I will run a slave on the master host at 172.0.0.19 to
illuminate an arcane Linux networking issue.
The acl
section defines the server's access control lists
(ACLs)—to whom Knot will listen when they send administrative requests
to it:
acl:
- id: acl_axfr
address: [ 127.0.0.0/24, 172.28.1.20 ]
action: transfer
key: tsigkey1.
- id: acl_upme
address: 127.0.0.0/24
action: update
key: tsigkey1.
I've configured two ACLs, one to receive AXFR
requests from slaves
and the other to facilitate nsupdate
requests originating on the local host.
Each will use the same key, but they don't have to.
So why did I employ a /24 mask on acl_xfer
? Shouldn't it be 127.0.0.19?
Indeed, it should, but Linux networking has a small quirk. Sometimes when my
localhost slave sends a request, the data will appear to be coming from the
main IP associated with the lo interface: 127.0.0.1. Before a web
search revealed this oddity, I stared at my logs in disbelief and pored over
tcpdump
output for some time when the master kept denying
mysterious AXFR
requests from 127.0.0.1. Several DNS server software packages have a config
option to specify the source address for slave transmissions. I have yet to
find such an option in Knot. I'm causing the issue here because I'm
being cheeky in my use of 127.0.0.* addresses; this would not happen if I
used a lab or public network address. So I'm comfortable specifying a
range for localhost on my home lab.
How, during the above incident, did I get Knot to tell me why it wasn't working? I ratcheted up the logging level. The log section controls what gets logged and where it goes:
log:
- target: syslog
any: info
This config emits all log entries at info
level and worse, and sends them to
syslog. To get debug-level entries as well and dump it out to your screen
(running Knot in the foreground), I used this config instead:
log:
- target: stdout
any: debug
target
also can be stderr or a filename.
I'm now ready to declare my authoritative zones with the zone section. For this article, I'm just setting up one zone:
zone:
- domain: cpu-chow.local
file: "cpu-chow-local.zone"
notify: slave1
acl: acl_axfr
acl: acl_upme
semantic-checks: on
disable-any: on
serial-policy: increment
Note that a period is not required at the end of the domain name. The file attribute does not allow a path; the default template supplies the storage attribute, /var/lib/knot, the location of cpu-chow.local.zone.
notify
designates to which remote id my master will send a
"notify" message advising them that they should promptly respond with an
AXFR
request. The first acl
designates the IP addresses to whom the master will
listen for AXFR
requests and the second for
nsupdate
requests. I could map a
non-default template here by adding a template: template-id
line
under this zone.
I have included some other attributes in the zone to illustrate
best practices: semantic-checks
does extra syntax checking on the zone file.
disable-any
alters Knot's response to, say, a -t
ANY
query to
prevent DNS zone-reflection attacks. serial-policy
ensures that a dynamic
update will trigger a serial number increment in the zone file.
I saved the file, set its owner to root:knot
and permissions to 640. I can
now start Knot, either via systemd:
$ sudo systemctl restart knot
$ sudo systemctl status knot
or by running it in the foreground with great verbosity:
$ sudo /usr/sbin/knotd -vvv -c /etc/knot/knot.conf
Note that systemd may not immediately report that Knot startup has failed, so following up with a status check is a good practice.
On my local caching server at 127.0.0.1, I update the config to tell it to consider my master at 127.0.0.9 as authoritative for my zone. Since I'm running PowOerDNS Recursor, I edit the file /etc/powerdns/recursor.conf:
forward-zones-recurse=cpu-chow.local.=127.0.0.9
I'll do the same elsewhere in my lab, using the lab net address 172.28.1.22 instead.
Let's look at a config for a slave server on 127.0.0.19. I'll call
it /etc/knot/slave.conf, same owner and permission as the master config.
Since it is a second Knot instance, I'll need to create a second
rundir
and storage location. Since it's a slave Knot, let's call it
snot
:
$ sudo mkdir -p /run/snot /var/lib/snot
$ sudo chown knot:knot /run/snot /var/lib/snot
$ sudo chmod 755 /run/snot /var/lib/snot
And here is /etc/knot/slave.conf:
server:
identity: dns2.cpu-chow.local
listen: 127.0.0.19@53
user: knot:knot
rundir: /run/snot
key:
- id: tsigkey1.
algorithm: hmac-sha256
secret: oxRKAUfGN3R6fGjWX/V+i4rjCl1zRuYslX0c4se+GWs=
template:
- id: default
storage: /var/lib/snot
remote:
- id: master1
address: 127.0.0.9@53
key: tsigkey1.
acl:
- id: acl_axfrnfy
address: 127.0.0.0/24
action: notify
key: tsigkey1.
log:
- target: stdout
any: debug
zone:
- domain: cpu-chow.local
master: master1
acl: acl_axfrnfy
It looks quite similar to the master. Note the acl
allowing the slave to
accept the notify message from the master. I have logging set to
debug-level
and standard output because I intend to run it in foreground.
You may create a systemd unit file to start the slave if you wish (beyond this article's scope). I'm running it manually here:
$ sudo /usr/sbin/knotd -vvv -c /etc/knot/slave.conf
I can control a running instance of knotd
by using the control program
/usr/sbin/knotc, thus:
$ sudo /usr/sbin/knotc -c config-file [ command ]
Specifying the config file tells knotc
which rundir
contains the socket to
which to connect. Use the knotc
command
zone-notify
on the master to have it
notify slaves to request an AXFR
, or zone-flush
to flush the cache. Many
other commands are available, use --help
to see them.
I have barely scratched the surface of Knot's capabilities; it really is designed to handle a full TLD with ease, so it can handle your load. Consider using it alongside your existing DNS software to avoid a DNS software monoculture in your organization.
Now that you've been introduced to Knot DNS and walked through an example master and slave configuration, you can install, configure and evaluate it for yourself. And you may like it enough that it will make your chest hairs all crispy, just like the Festrunk brothers.