Knot DNS is one of the easier to setup authoritative dns servers out there, made by NIC.CZ.
In this tutorial I’ll show how to setup Knot with DNSSEC, authenticated master -> slave sync, RFC2136 (for automatic dns-based certs in caddy and such) and GeoDNS, which makes the server give an IP closest to the user.
I assume you have two Debian based systems, with port 53 (tcp+udp) forwarded.
Installing Knot
This guide covers debian, but instructions for other distributions can be found on the download page Run the following on both the master and the slave:
apt-get -y install apt-transport-https lsb-release ca-certificates wget
wget -O /usr/share/keyrings/knot.gpg https://deb.knot-dns.cz/apt.gpg
sh -c 'echo "deb [signed-by=/usr/share/keyrings/knot.gpg] https://deb.knot-dns.cz/knot-latest/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/knot-latest.list'
apt-get update
apt-get install knot knot-dnsutils
Basic Configuration
knot.conf
Now, to add in the basic configuration, overwrite the
/etc/knot/knot.conf
file on the master with the following
text:
server:
rundir: "/run/knot"
user: knot:knot
listen: 0.0.0.0@53
log:
- target: syslog
any: info
database:
storage: "/var/lib/knot"
remote:
- id: secondary
address: your.other.servers.ip@53
acl:
- id: acl_secondary
address: your.other.servers.ip
action: transfer
template:
- id: default
storage: "/etc/knot/zones"
file: "%s.zone"
semantic-checks: on
# Don't override zonefile
zonefile-sync: -1
zonefile-load: difference-no-serial
journal-content: all
zone:
- domain: your.domain
notify: secondary
acl: acl_secondary
On the slave, overwrite the same file with the following text:
server:
rundir: "/run/knot"
user: knot:knot
listen: 0.0.0.0@53
log:
- target: syslog
any: info
database:
storage: "/var/lib/knot"
remote:
- id: primary
address: your.main.servers.ip@53
acl:
- id: acl_primary
address: your.main.servers.ip
action: notify
template:
- id: default
storage: "/etc/knot/zones"
file: "%s.zone"
zone:
- domain: your.domain
master: primary
acl: acl_primary
Zonefile
At this point you need to create /etc/knot/zones
on both
the master and slave as that is where the zonefiles will be stored. Now
create a file named your.domain.zone in the directory on the master
alone and add the following text to it:
$ORIGIN your.domain. ; 'default' domain as FQDN for this zone
$TTL 3600 ; default time-to-live for this zone
your.domain. IN SOA ns1.your.domain. ns2.your.domain. (
YYYYMMDD01 ;Serial
14400 ;Refresh
3600 ;Retry
1209600 ;Expire
3600 ;Negative response caching TTL
)
@ IN NS ns1.your.domain.
@ IN NS ns2.your.domain.
ns1 A your.main.servers.ip
ns2 A your.other.servers.ip
@ A your.main.servers.ip
; PTR Records (for mailservers)
your.ip.in.reverse.in-addr.arpa. PTR mail.your.domain.
At this point, you can run systemctl restart knot
(restart since its a major change) on both nodes.
Updating the zonefile
Updating the zonefile is not hard.
- Since we set the
zonefile-load
todifference-no-serial
, we do not need to increment the serial as it will automatically be computed whenknotc reload
is run. - All records must be before the PTR in the zonefile.
- Wildcards can be done with the hostname as
*
- If the hostname ends with ., it must include your.domain, that is
something.your.domain will be
something.your.domain.
- If the hostname does not end with a ., it is relative to the domain,
that is something.your.domain will just be
something
. - Always use tabs, not spaces.
Token-based zonefile authentication
At the moment, the authentication for sending and receiving the zonefile is completely based on the IP address, which is not very secure.
To remediate this, we can use token-based authentication.
First, you need to generate a key with
keymgr -t zonesync hmac-sha256
This is the key that will authenticate the zone transfers.
You can copy-paste the output it gives you into your knot.conf, above the remote section on both master and slave.
Now, you need to add key: zonesync
to the remote,
acl_primary/secondary sections on both master and slave.
At this point, you can run systemctl restart knot
, and
all the syncs will be more secure!
DNSSEC
DNSSEC is an extension to DNS designed to protect applications using DNS from accepting forged or manipulated DNS data by using zone signing.
Enabling DNSSEC on knot is as simple as adding the following line to the template section:
template:
...
dnssec-signing: on
...
At this point, you need to upload the DS record to your Registry. This is usually done through your registrar’s WebUI.
The DNSSEC-enabled DNS servers use the DS record from your domain registry to validate your records.
You can get your DS record with the following command:
keymgr your.domain ds
The output will look something like this:
54674 13 2 E28E3DB78E5517A577353A43799AD14EC044720BAE4906D134F5EA40 74AC0287
In the example given:
- Key tag - 54674
- Algorithm - 13
- Digest Type - 2
- Digest - E28E3…287 (omit space)
On namecheap, you add this at Advanced DNS -> DNSSEC
You can check if your DNSSEC is working properly at DNSViz and DNSSEC-Analyzer.
RFC2136
RFC2136 is an RFC that allows dynamic updates for DNS.
This works completely over DNS and does not require a special API.
To set it up, you need to create an ACL as follows:
acl:
...
- id: acl_dynupdates
address: [an.authorized.ip.addr, another.authorized.ip.addr]
action: update
...
zone:
- domain: your.domain
notify: secondary
acl: [acl_secondary, acl_dynupdates]
You can also add token-based auth by generating another key with
keymgr -t rfc2136 hmac-sha256
You can add it to the config as follows:
key:
...
- id: rfc2136
algorithm: hmac-sha256
secret: xxx
...
acl:
...
- id: acl_dynupdates
address: [an.authorized.ip.addr, another.authorized.ip.addr]
action: update
key: rfc2136
After this, you can run systemctl restart knot
to apply
the changes.
Caddy
Caddy is a modern webserver which supports automatic cert generation from letsencrypt/zerossl with acme.
It uses http-01 for the challenge by default, but can use a dns challenge too.
For the DNS challenge, you first need to install the RFC2136 DNS plugin with the following command:
xcaddy build --with github.com/caddy-dns/rfc2136@master
Now, add the following lines to the top of your caddyfile
{
acme_dns rfc2136 {
key_name "rfc2136"
key_alg "hmac-sha256"
key "xxx"
server "your.main.servers.ip:53"
}
acme_ca https://acme-v02.api.letsencrypt.org/directory
}
Now, all certs can be generated using the DNS challenge!
This is especially useful in a GeoDNS environment.
GeoDNS
GeoDNS allows geographical split horizon based on a GeoIP database, such as Maxmind’s free GeoLite2.
Firstly, you need to procure the GeoLite2 database from Maxmind.
Due to policy changes, you now need to signup on Maxmind’s website in order to get access.
However, older GeoIP DBs can still be found in many places, including distribution package repositories.
Once you procure your copy of GeoLite2, you need to install the GeoIP module for knot-dns on both master and slave.
On Debian, the package’s name is knot-module-geoip
After installing the module, you can add it to the knot.conf (must be before zone section however):
mod-geoip:
- id: geo
config-file: "/etc/knot/geo.conf"
mode: geodb
geodb-file: "/var/lib/knot/GeoLite2-City.mmdb"
geodb-key: [ continent/code, country/iso_code, city/names/en ]
You also need to include the GeoIP module for your domain. You can do that by adding this line to your domain’s zone section:
zone:
- domain: your.domain
...
module: mod-geoip/geo
Now, you need to configure the GeoDNS.
To do so, create a file called /etc/knot/geo.conf
with
the following:
geodnsubdom.your.domain:
- geo: "*;*;*" # Fallback incase DNS server doesn't send ECS
A: your.main.servers.ip
TXT: "Worldwide"
- geo: "EU;*;*" # Europe
A: your.europe.servers.ip
TXT: "Europe"
...
However, the file needs to be manually synced to the slave on every update.
It is also painful to use if you have multiple root subdomains you want to use GeoDNS with.
Due to these reasons, I made a kinda hacky script to remediate this:
#!/usr/bin/env bash
geoconf=/etc/knot/geo.conf
remote='geodns@other.servers.ip'
printf '' > $geoconf
for i in $(</etc/knot/geodnsdomains); do
cat /etc/knot/geodnstemplate >> $geoconf
sed -i "s/REPLACEME/${i}/" $geoconf
done
scp $geoconf "${remote}":/var/geo.conf
ssh $remote "sudo systemctl restart knot"
systemctl restart knot
In order to allow the unprivileged user on the slave to restart knot, I used a sudo ALLOW_CMDS flag:
Cmnd_Alias KNOT_CMDS = /usr/bin/systemctl restart knot
geodns ALL=(ALL) NOPASSWD: KNOT_CMDS
And thats it. Thanks for reading!