Migrating from OpenDNSSEC to Knot DNS

Written for the CZ.NIC staff blog

This article is written in an effort to aid those who are considering Knot DNS as a replacement for OpenDNSSEC.

More specifically, in this article we’ll be showing how to:

  • make Knot use HSMs via the PKCS11 interface
  • seamlessly transition from OpenDNSSEC to Knot
  • then transition from HSM to automatically managed in-memory keys

If you’ve never interacted with Knot before, please familiarize yourself with the basics. Our documentation provides a great novice-friendly introduction.

Preparation

Let’s assume ODS is currently running and taking its input data from /var/lib/opendnssec/unsigned/example.com.

First we need to prepare a transient configuration. We won’t be running Knot itself just yet, but we will be using the tools that come with it (especially keymgr; the key management utility) and which share its settings.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# in /etc/knot/knot.conf

server:
  listen: 127.0.0.1

log:
  - target: syslog
    any: debug # decrease the log level after setup is stable

keystore:
  - id: hsmstore
    backend: pkcs11
    config: "pkcs11:token=$TOKEN_LABEL;pin-value=$PIN /path/to/hsm.so"

policy:
  - id: manual-hsm
    manual: on
    keystore: hsmstore

zone:
  - domain: example.com.
    file: "%s"
    storage: /var/lib/opendnssec/unsigned
    zonefile-sync: -1 # don't overwrite the source zonefile
    dnssec-policy: manual-hsm
    dnssec-signing: on

This should be simple enough. We set up Knot to:

  • source its data from the same zonefile ODS uses as input
  • sign the data, but leave the key administration to the operator
  • keep the keys stored safely in a HSM

keystore->config is the PKCS11 token URI combined with the HSM’s module path − see relevant documentation. The template provided above should work well enough. Both $TOKEN_LABEL and $PIN should be specified in your existing ODS configuration (/etc/opendnssec/conf.xml). The resulting URI could look like:

"pkcs11:token=OpenDNSSEC;pin-value=1234 /usr/lib/softhsm/libsofthsm2.so"

Finally make sure that the Knot process has access rights to the zonefile and any necessary data linked to your HSM. Then, if you’ve set up everything correctly thus far, you should see something like the following:

1
2
3
4
5
6
7
8
9
$ keymgr hsmstore keystore-test

Keystore id 'hsmstore', type PKCS #11

Algorithm           Generate    Import    Remove       Use
RSASHA256                yes       yes       yes       yes
ECDSAP256SHA256          yes       yes       yes       yes
ED25519                   no       yes       yes       yes
ED448                     no        no        no        no

If, in the Knot configuration, you used a different id in the keystore section, you need to use that instead of hsmstore.

Key importing

We’ve configured Knot to be able to access our HSM storage. Next we need to seamlessly phase-out ODS while phasing-in Knot.

To do that we need to synchronize private keys between ODS and Knot. Knot has a separate utility for key management named keymgr which allows us to do exactly that. HSMs don’t provide keymgr with metadata about the key, so first we have to figure out what those are:

1
2
3
4
5
6
$ ods-enforcer key list -v

Keys:
Zone:       Keytype: State: Size: Algo: CKA_ID:    Repository:
example.com KSK      active 2048  8     ce8c..90e0 SoftHSM
example.com ZSK      active 1024  8     302f..0df4 SoftHSM

And then we may:

$ keymgr example.com. import-pkcs11 ce8c..90e0 algorithm=8 size=2048 ksk=yes zsk=no
$ keymgr example.com. import-pkcs11 302f..0df4 algorithm=8 size=1024 ksk=no zsk=yes

Now make sure everything went well, and the keys are actually imported:

$ keymgr example.com. list

ce8c..90e0 14609 KSK RSASHA256/2048 publish=1754915577 active=1754915577
302f..0df4 20836 ZSK RSASHA256/1024 publish=1754915618 active=1754915618

To be clear: all these commands do, is make Knot aware of the keys’ parameters and existence inside your HSM. They do not extract the cryptographic secret.

If you haven’t done so yet, now is a good time to actually start the Knot daemon. If you ran it previously, in its logs you would have seen complaints about missing keys and the zone wouldn’t be signed. (Re)starting now, everything should work fine:

info: [example.com.] DNSSEC, key, tag 14609, algorithm RSASHA256, KSK, public, active
info: [example.com.] DNSSEC, key, tag 20836, algorithm RSASHA256, public, active
info: [example.com.] DNSSEC, signing started
info: [example.com.] DNSSEC, successfully signed, serial 16, new RRSIGs 7

Before attempting any of this, you should make sure ODS isn’t currently in the midst of a rollover or that there isn’t a rollover planned in the near future. A more rigorous approach would be to disable automatic key generation and rollovers by issuing <ManualKeyGeneration/> in conf.xml and <ManualRollover/> in kasp.xml.

From adapters to remotes

Suppose we want our Knot signer to act as a hidden primary for one or more secondaries. This is easy to achieve:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# in /etc/knot/knot.conf

server:
    # ...
    automatic-acl: on

remote:
  - id: secondary1
    address: 172.0.0.1
  - id: secondary2
    address: 172.0.0.2

zone:
  - domain: example.com.
    # ...
    notify: [ secondary1, secondary2 ]

Of course, this has to be reciprocated on the secondaries’ side.

Now, what if we wanted the source data to come from an XFR instead of a zonefile, similarly to ODS’s input adapter? No problem on that front either!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# in /etc/knot/knot.conf

server:
    # ...
    automatic-acl: on

remote:
  - id: primary
    address: 172.0.0.3

zone:
  - domain: example.com.
    # ...
    master: primary

Nothing prevents us from defining both notify and master statements at the same time, providing multiple masters in a multisigner-setup, or using the server as a validator (dnssec-validation option).

The options are vast, but they’re out of scope for this article.

By default communication with a remote happens in plain-text over TCP. We’ve previously discussed the mechanisms through which communication may be secured here, but in a nutshell Knot DNS supports TSIG, TLS and QUIC in opportunistic, strict and mutual modes.

Keystore migration

The upcoming Knot v3.5 will bring support for multiple keystores, which is a feature useful for those of you who want to migrate away from HSM keystores to internally managed keys. You will also be able to keep your KSKs in a HSM for security, with ZSKs staying in memory for efficient signing.

Some HSMs don’t allow private key extraction, so the only way to migrate is to do a rollover over different keystores. The steps are:

  1. in knot.conf define the keystore that’s being migrated to
  2. key rollover
  3. remove old keystore from configuration

Let’s demonstrate on an example migration from SoftHSM to Knot’s PEM keystore:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# ...

keystore:
  - id: hsmstore
    backend: pkcs11
    config: "pkcs11:token=OpenDNSSEC;pin-value=1234 /usr/lib/softhsm/libsofthsm2.so"

  - id: knotstore
    backend: pem

policy:
  - id: manual-hsm
    manual: off                       # let Knot manage keys automatically
    algorithm: rsasha256              # replace with appropriate value
    keystore: [ knotstore, hsmstore ] # order matters!

zone:
  - domain: example.com.
    dnssec-policy: manual-hsm
    # ...

It’s important to set the algorithm option so that it corresponds to the algorithm used by your HSM. Otherwise, in combination with automatic key management, Knot would automatically initiate an algorithm rollover.

Notice that the new knotstore is referenced first in the policy section. In this way it is denoted as the default store for newly generated keys.

Now reload Knot’s configuration:

$ knotc reload

Unless you’ve manually set key lifetimes in your policy, the default values apply: indefinite for KSKs and 30 days for ZSKs. We can’t afford to wait that long, so let’s move it along a little:

$ knotc zone-key-rollover example.com. zsk

We’ve issued the command to schedule a ZSK rollover. This is reflected in the logs:

info: [example.com.] control, received command 'zone-key-rollover'
info: [example.com.] DNSSEC, signing zone
info: [example.com.] DNSSEC, ZSK rollover started
info: [example.com.] DNSSEC, next key action, ZSK tag 5641, replace at 2025-08-19T17:49:22+0200

If that went well, repeat the same command, only this time with ksk. After a while you should see a log:

notice: [example.com.] DNSSEC, KSK submission, waiting for confirmation

Make sure the new DS is submitted to the parent zone and then:

$ knotc zone-ksk-submitted example.com.

Alternatively define a submission section and ksk-submission to handle DS checks automatically.

Now just sit back and watch the rollover happen:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# before
$ keymgr example.com. list

4a4..be4 14687 KSK RSASHA256/2048 PKCS11/softhsm # ...
b7d..148 17080 ZSK RSASHA256/1024 PKCS11/softhsm # ...

# after
$ keymgr example.com. list

330..3a8 51979 KSK RSASHA256/2048 PEM/knotstore # ...
473..245 33546 ZSK RSASHA256/2048 PEM/knotstore # ...

Now you can safely remove any mention of hsmstore from the configuration. If you wish, you can now also change the algorithm and perform an algorithm rollover.

This process could alternatively be left for Knot to deal with “automatically”, by adjusting the values of propagation-delay, dnskey-ttl, zone-max-ttl and (ksk|zsk)-lifetime. However describing the various nuances and pitfalls there would be out of scope for this article.

Wrapping up

Hopefully this was helpful to get you started on your transition from OpenDNSSEC to Knot DNS. We aim for Knot to be a powerful, yet approachable and coherent software package, which, fingers crossed, was apparent throughout this article.

We’re always improving and always open to user feedback. If you’re looking to report a bug, request a feature or simply say “hi”, feel free to join one of our communication channels − we hope to see you there.

Comments

It's awfully quiet in here...