Mon 05 September 2022 | -- (permalink)
I've been using BIND9 as a hidden bump-on-the-wire DNSSEC signer for about a dozen years now. The configuration was excessively complex, mostly for historical reasons that were my own fault, but it was time to clean it up, and I wanted to try Knot as an alternative to BIND9.
Basic hidden bump-on-the-wire configuration is the same for any DNSSEC signer:
-
I edit plain old pre-DNSSEC zone files (with Emacs, yes this is old school), check them into a git repository, then push a button to tell the signer to wave its magic wand.
-
The signer picks up my zone files, adds and updates
DNSKEY
,RRSIG
, andNSEC
RRs, makes the signed zones available as a DNS primary server, and notifies the secondaries that new versions are available. -
The secondaries pick up the updated zones, and they're the service that the outside world sees. Nobody but the secondaries talks to my signer.
-
The signer remains active even when I'm not changing anything, because signatures expire, and keys may be configured to time out, so the signer needs to keep this stuff up to date on its own, which means that the
SOA SERIAL
number presented to the world may not be the one in my hand-edited master file.
Extracting old data from BIND9
Extracting the old data from BIND9 was relatively straightforward. Due to having recently gotten out of the business of hosting DNS primaries for other people, I had only my own zone to worry about, so all I needed was the current zone master file and its DNSSEC keys.
Due to the way that bump-on-the-wire signing works in BIND9, there are really two separate zones as far as BIND9 is concerned: the unsigned zone, and the signed zone. I wanted to extract the latest copy of the unsigned zone from BIND9's journal file, so I did:
named-compilezone -j -s relative -o extracted.zone -f text -F text example.org example.zone
If you already have a good copy of the unsigned master file you can
skip this step, it was mostly paranoia on my part. The only thing
that was likely to be different was the SOA SERIAL
, and since I use
the "unixtime" convention for generating serial numbers, I'll be
updating that to something current as part of the migration anyway.
Extracting the keys was even easier, because BIND9 just keeps them all in a messy pile of disk files with obscure names like:
Kexample.org.+014+42103.key
Kexample.org.+014+42103.private
Kexample.org.+014+60548.key
Kexample.org.+014+60548.private
example.org
is the zone name, in this case the keys are
ECDSAP384SHA384
(algorithm number 14) with Key IDs 42103
and
60548
. The public keys are in the .key
files, the private keys
are in the .private
files. There are two keysets because one set is
a ZSK and the other is a KSK: you can't tell which is which just from
the filenames, but there's a comment in each of the .key
files.
At any rate, Knot will want all of the key files associated with a particular zone.
At this point the alert reader might be wondering about all the DNSSEC data that's not present in the unsigned master file. Short answer: we could salvage that if necessary, but there's no real need unless the zone in question is so large that re-signing the whole zone will take a significant amount of time. The easy path is just to let Knot re-sign the whole zone when it takes control, so long as we keep the same keys this will just look like normal maintenance to the outside world.
Extracting old data from something other than BIND9
Sorry, I have not tried this. In theory any signing authoritative name server should have the same information, somewhere, but extracting it from a particular implementation may be exciting.
Keys, in particular, may be a problem. The general problem here partitions into two cases:
-
Software key store, where the problem is just finding, extracting, and converting the keys; or
-
Hardware Signing Module (HSM) where the keys live on separate hardware designed with the express purpose of making it impossible for you to extract the private keys.
The first case is tedious; the second case is impossible (if it's not, demand a refund on that HSM...after extracting the keys). Knot claims to support using a HSM via PKCS #11, I have no reason to disbelieve this but haven't tried it myself, and identifying the keys to be used correctly when switching from one application to another may be challenging.
If it's "just" a software problem, well...at the end of the day there
are only so many ways to represent key pairs for the handful of
algorithms that DNSSEC supports, so this is mostly a data retrieval
and decoding problem. Knot's keymgr
tool knows how to import
several key formats directly, including so-called "PEM" format, so if
you can coerce your private keys into anything that OpenSSL can
handle, you should be able to import them into Knot.
Alternatively, you could just decide that it's time for a key roll, in
which case you will want to look at the keymgr
man page.
Installing Knot
sudo apt-get install knot
Hard, I know. Debian Bullseye is only at Knot version 3.0.5, upstream is at version 3.2, but I didn't find any pressing need for bleeding edge so I just went with what APT installed.
Importing DNSSEC keys into Knot
This was the part that took me longest to figure out, for what turned
out to be very silly reasons: I mis-read the manual page, and the
error message telling me that I'd gotten the command line syntax for
the keymgr
tool wrong was, um, not helpful. To save others the
tuition I paid for this: for most keymgr
commands, the syntax is
keymgr <zone-name> <command> <arguments>
So, once I had that sorted out, importing keys was simple:
sudo keymgr example.org import-bind Kexample.org.+014+42103.key
sudo keymgr example.org import-bind Kexample.org.+014+60548.key
As noted in the Knot documentation, both files (.key
and .private
)
must be present, even though one only specifies one of them per
command. Supposedly one can give either filename, or omit the .key
/ .private
suffix entirely, but I didn't try that.
Since keymgr
reads /etc/knot/knot.conf
to figure out where to put
the key database, you can't do this until you have at least a minimal
knot.conf
in place. The minimal version installed by apt-get
is
probably good enough (it specifies the database
/ storage
directory, and there's no reason to change that) but, full confession,
I had already started tinkering with knot.conf
by the time I figured
out what I was doing wrong with keymgr
so if you run into problems
here you may want to try again after setting up knot.conf
.
Configuration
Knot uses a mutant variant of YAML as its configuration language. To a first approximation, this means that YAML syntax just works, but there are a few things it doesn't support, and a few extensions that would not parse as correct YAML. When in doubt, stick to syntax as shown in the Knot manual.
Most of the following is a straightforward setup for a signing primary with two secondaries, but there are a few peculiarities which you might not want to copy.
Sanitized version of my /etc/knot/knot.conf
:
server:
rundir: "/run/knot"
user: knot:knot
listen: [ 0.0.0.0@53, ::@53 ]
log:
- target: syslog
any: info
database:
storage: "/var/lib/knot"
key:
- id: "ns0-ns1.tsig.example.org"
algorithm: hmac-sha256
secret: "..."
- id: "ns0-ns2.tsig.example.org"
algorithm: hmac-sha256
secret: "..."
acl:
- id: tsig
key: ["ns0-ns1.tsig.example.org", "ns0-ns2.tsig.example.org"]
action: transfer
remote:
- id: ns1
address: 192.0.2.53
key: "ns0-ns1.tsig.example.org"
- id: ns2
address: 198.51.100.53
key: "ns0-ns2.tsig.example.org"
policy:
- id: P-384-manual
algorithm: ECDSAP384SHA384
manual: on
template:
- id: primary
storage: "/etc/knot/zones"
file: "%s"
zonefile-sync: -1
zonefile-load: difference
journal-content: all
serial-policy: unixtime
zone:
- domain: example.org
template: primary
dnssec-signing: on
dnssec-policy: P-384-manual
acl: tsig
notify: [ns1, ns2]
# Local Variables:
# mode:yaml
# compile-command: "knotc reload"
# End:
Taking this from the top:
-
The
server
,log
, anddatabase
sections are bog-standard. -
The
key
section defines two TSIG keys. The values of theid
fields here must match the TSIG key names used by the peers. -
The
acl
section defines a single ACL namedtsig
which allows transfer authenticated by either of the specified TSIG keys. -
The
remote
section defines two remotes, associating addresses and a TSIG key with each. This isn't needed for inbound*XFR
requests, but is needed for outboundNOTIFY
messages (Knot needs to know where to send the notifications and how to sign them). -
The
policy
section defines a single DNSSEC policy namedP-384-manual
, with DNSSEC algorithmECDSAP384SHA384
and specifying that key rolls are only done under manual control.Long term, this should probably be one of the automatic key roll policies, at least for the ZSK. Knot supports automatic KSK rolls via the mechanism described in RFC 7344, but I don't know whether my zone's parent supports that yet, so that's a research topic.
-
The
template
section defines a template for individual primary zones.This is where things start to get weird: I want to be able to edit my zone files with Emacs and keep them under version control, so I'm putting them in a non-standard place (
/etc/knot/zones/
) and am telling Knot that I want it to leave my hand-edited zone files alone and keep its own additions and changes in the journal file. Oh, and I like theunixtime
convention for theSOA SERIAL
number so please use that when changing anything, thanks.This may happen to fit your usage pattern. It may not. This is not, for example, the way I would configure things for use with zc, and by configuring things this way I've taken on the responsibility for updating the
SOA SERIAL
number myself when I edit anything by hand (not a big deal since Emacs'sdns-mode
wants to update that for me by default anyway).But anyway, if there is any point in this recipe where you really ought to read the manual rather than just blindly copying what I did, this is it.
-
The
zone
section defines one zone (example.org
), mostly using stuff (template, acl, remotes) defined in the previous sections.Given that this sample configuration defines only one zone, everything that's defined in the template could have been defined here instead. More of what's done here could have been done in the template. Just a matter of taste. In general, the point of Knot templates is to minimize the amount of repetitive configuration, so slice and dice this as needed.
-
The Emacs file variables at the end just tell Emacs that the syntax is (approximately) YAML and that when I type
M-x compile
I want Emacs to runknotc reload
for me.knotc reload
is a moderately large hammer, one might conceivably want to fine tune this, but since part of the point of a hidden signer is that it doesn't get a lot of traffic, keeping things simple here works for me.
Update 2022-09-06
The original version of this article reported that the NOTIFY
setup
above was untested due to my not having figured out how to tunnel the
NOTIFY
message past Comcast's DNS filtering. It turns out that both
the Knot configuration and the tunnel configuration were fine, the
real problem was that the allow-notify
ACL on the nsd
secondary
was rejecting the NOTIFY
messages (and was returning FORMERR
,
which makes no sense -- so many windmills, so little time). After
fixing the nsd
ACL, notifications from Knot work fine.
Update 2022-09-22
Enabling automatic ZSK rollover turns out to be trivial:
-
Remove
manual: on
from the policy definition (and rename the policy if the name is now wrong, but Knot won't care); -
knotc reload
, or restartknotd
, whichever is easier.
Yes, it really is that simple. You can configure the ZSK validity period if you like, but the default is probably fine.
I haven't yet tried enabling KSK rollovers. In theory, this should
also be simple, just follow instructions in the manual to enable KSK
rollover via CDNSKEY
and CDS
records, then come back in a few days
to see whether anything happened. I haven't been able to find any
documentation on whether my zone's parent supports KSK rollover via
this mechanism, but if I understand Knot's algorithm correctly nothing
terrible will happen if not, the rollover will just never complete.
Not urgent, so leaving well enough alone for the moment.