BlackSponge's Blog

Migrating my DNS server from Bind9 to Knot DNS

Posted on in Tutorials by BlackSponge . DNS DNSSEC Knot Ops

I own and manage a few domain names, including the bksp.space zone containing the domain of this blog, and run and host their nameservers myself. Since the beginning I have always been using Bind9 as this is the tool I learnt first and then I have not really looked into others. First because I didn't really care, I mean it worked already, why would I changed? And then after spending some time configuring DNSSEC, including signature rotation and such, using bash scripts I did not wanted to go through the same burden again.

Even though, for a while I came upond other authoritative DNS software but none of the really catched my eyes, I mean PowerDNS seams nice feature wise but does not look that much simpler to configure and operate. But recently I come accross people on the Fediverse using Knot DNS to host their zones and being some curious I decided to have a look. So Knot-DNS is developped and used by the Czech NIC and, compared to the other softwares mentionned, is rather recent, Bind and PowerDNS came out in 1986 and 1999 respectvely while Knot-DNS has been initially released in 2011. What I really liked though is the simplicity of the configuration and the possibility to have the keys and zone signature management fully automated, without too much hassle.

I you want to know more about it I recommned you to visit the documentation and guides, now I will simply describe what I have done to migrate the zone.

Knot configuration

To install knot simply fetch it from you distribution repositories, I am running Archlinux so in my case it would be pacman -Sy knot. In my previous setup I had a directory my.zone.tld.d for each zone containing the keys and the zone file, for bksp.space it looked like this.

/var/named/bksp.space.d/
├── Kbksp.space.+007+05001.key
├── Kbksp.space.+007+05001.private
├── Kbksp.space.+007+15557.key
├── Kbksp.space.+007+15557.private
├── bksp.space.zone
├── bksp.space.zone.signed
└── dsset-bksp.space.

While the keys will not be living their anymore but in Knot keystore I could go back to having a single file per zone but I will stick to that structure in case I want to do some includes in the future. So to migrate the zone I simply copy the folder to what will be Knot storage directory.

$ mkdir -p /var/lib/knot/zones
$ cp -r /var/named/bksp.space.d /var/lib/knot/zones
$ chown -R knot:knot /var/lib/knot/zones

Then it's time for the configuration, while it may look like YAML, experience has taught that it is not really, the first new blocks are quite simple, I configure the server to use the knot user, default, to listen on my public IPs and then configure logging to use the syslog and change server log level to waring to avoid unnessary notices about TCP client disconnection. Finally as mentionned before the base storage directory will be /var/lib/knot, again the default on my distribution.

server:
    rundir: "/run/knot"
    user: knot:knot
    listen: [ 198.51.100.1@53, 2001:db8:cafe:bc68::1@53 ]

log:
  - target: syslog
    zone: info
    control: info
    server: warning

database:
    storage: "/var/lib/knot"

Then I created a default policy for DNSSEC management. I am using rsasha1-nsec3-sha1 algorithm as this is what my keys have been generated with, change that to your will if that is not what you have been using. I like me your not sure what algorithm you used, it's been two years since I have done, you can check the content of you private key, in Bind9 format, the second line indicates the algorithm.

$ cat /var/lib/knot/zones/bksp.space.d/Kbksp.space.+007+05001.private
Private-key-format: v1.3
Algorithm: 7 (NSEC3RSASHA1)
....

The next configuration items is related to the signature and keys lifetime, I am neither a security nor DNS expert but rotating the signatures every week and the zone signing key every month looked like sane values. Next I enabled NSEC3 records to prevent zone walking. Finally I temporarily set the key management to manual as I will manually import the keys later and I wanted to test the zone as early as possible.

policy:
  - id: default
    algorithm: rsasha1-nsec3-sha1
    rrsig-lifetime: 14d
    rrsig-refresh: 7d
    zsk-lifetime: 30d
    dnskey-ttl: 24h
    nsec3: on
    nsec3-iterations: 10
    nsec3-opt-out: on
    nsec3-salt-lifetime: 14d
    nsec3-salt-length: 40
    manual: on

A nice feature about Knot DNS is the configuration templates that allows to define template of configuration that you will apply to zones to quickly configure zones with similar configurations.

I defined two templates for primary, or master, zones, default and signed the only difference between the two begin the last to additional lines of the signed template. The first enable automatic DNSSEC signing and the second one refer to the DNSSEC policy previously created.

Now for the remaining fields, I indicate again the storage directory and then the zone file path, %s will be replaced by the zone name. The journal here refers to an database backed changelog to persist changes between restarts if they have not been flushed to the zone file. Here the value is all and the automatic flush to file is disabled by setting a value of -1 to zonefile-sync period. Means that the whole zone is stored in the journal and the original zone file is never altered by dynamic update like zone signing (or DDNS but I am not currently using that). As stated in the documentation:

Some users dislike that the server overwrites their prettily prepared zone file.

I am one of those users.

Finally difference-no-serial means that the serial is automatically updated when reloading the zone file content using the dateserial format (YYYYMMDDNN).

I haved also prepared a secondary template for secondary or slave zone. In that case no file is used and the content of the zone is exclusively stored in the journal.

template:
  - id: default
    storage: "/var/lib/knot"
    file: "zones/%s.d/%s.zone"
    journal-content: all
    zonefile-load: difference-no-serial
    zonefile-sync: -1
    serial-policy: dateserial

  - id: signed
    storage: "/var/lib/knot"
    file: "zones/%s.d/%s.zone"
    journal-content: all
    zonefile-load: difference-no-serial
    zonefile-sync: -1
    serial-policy: dateserial
    dnssec-signing: on
    dnssec-policy: default

  - id: secondary
    journal-content: all
    zonefile-load: none
    zonefile-sync: -1

It is almost time to define the zone but I still need to do one thing before, defining remotes and ACLs to allow transfer to secondary name servers.

It might looks a bit confusing but here I have two dns servers acting both as primary and secondary servers for each other. So the remotes define the servers and the ACL the permission, I am using IP based ACL, I could of course be using shared key based ACL.

So I authorized the transfer of the zone to the ACL bksp_secondary_acl and authorized the zone to be transfer to me for the ACL primary_acl.

remote:
  - id: bksp_secondary
    address: [ 198.51.100.2, 2001:db8:cafe:bc68::2 ]
  - id: primary
    address: [ 198.51.100.2, 2001:db8:cafe:bc68::2 ]

acl:
  - id: bksp_secondary_acl
    address: [ 198.51.100.2, 2001:db8:cafe:bc68::2 ]
    action: transfer
  - id: primary_acl
    address: [ 198.51.100.2, 2001:db8:cafe:bc68::2 ]
    action: notify

Then the zone configuration block looks like this, using the defined templates, remotes and ACLs.

zone:
  # Primary zones
  - domain: bksp.space
    notify: bksp_secondary
    acl: bksp_secondary_acl
    template: signed

  # Secondary zones
  - domain: example.com
    master: primary
    acl: primary_acl
    template: secondary

You can check you configuration using the knotc tool:

$ knotc conf-check
Configuration is valid

Importing the keys

The tool to manage keys is keymgr, to import the key run keymgr <zone> import-bind on both the KSK and the ZSK. Be sure that the files are owned by the knot user, if not it might not work. By default once imported the keys are stored in pem format in the /var/lib/knot/keys/keys/ directory.

$ keymgr bksp.space import-bind /var/lib/knot/bksp.space.d/Kbksp.space.+007+05001
$ keymgr bksp.space import-bind /var/lib/knot/bksp.space.d/Kbksp.space.+007+15557

If you want to be sure you can list the keys owned by a zone with:

$ keymgr bksp.space list
<long hash> ksk=yes zsk=no  tag=05001 algorithm=7  size=4096 public-only=no  pre-active=0 publish=1564190151 ready=0 active=1564190151 retire-active=0 retire=0 post-active=0 revoke=0 remove=0

<long hash> ksk=no  zsk=yes tag=05528 algorithm=7  size=2048 public-only=no  pre-active=0 publish=1610134471 ready=0 active=0 retire-active=0 retire=0 post-active=0 revoke=0 remove=0

<long hash> ksk=no  zsk=yes tag=15557 algorithm=7  size=2048 public-only=no  pre-active=0 publish=1564190124 ready=0 active=1564190124 retire-active=0 retire=0 post-active=0 revoke=0 remove=0

Here I have three keys because I have rotated the ZSK.

You can now reload Knot to take you changes in account and don't forget to check for potential errors by checking the service status. I everythin went well you should see logs telling that you zone has been signed.

$ knotc reload
Reloaded
$ systemctl status knotc

Finally you can turn off the manual configuration entry to let Knot rotate the KSZ for you.

Next steps

Now that the ZSK rollover has been automated the final step for me would be to rotate the KSK in a similar manner. This one is more complex as the new DS record needs to be pushed in the parent zone. Fortunately, according to documentation the key submission event is logged in a way that allow automation.

If systemd is available, the KSK submission event is logged into journald in a structured way. The intended use case is to trigger a user-created script.

So now the *only thing* that needs to be done is to write a script that update the DS using my registrar API, but that will probably have to wait ;).


References