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 ;).