How To... | Why not..? | Scripts | Patches |
Last update: 2023-09-12
This post is about configuring nsd(8) as a public name server for your own domain, providing DNS over TLS (DoT). This version of the configuration includes XFR over TLS (XoT) as defined in RFC 9103. This protects the zone transfers between primaries and secondaries from eavesdropping. Everything needed for this task is already there in OpenBSD base installation. You don’t need to install a single additional package for this.
Any certificate provider who supports the ACME protocol can be used for this. Personally I go with the most popular provider nowadays: Let’s Encrypt.
The challenges issued by the certificate provider will be answered by
httpd(8) using a configuration in
/etc/httpd.conf
similar to this one:
server "ns1.example.net" {
listen on egress port http
root "/"
location "/.well-known/acme-challenge/*" {
request strip 2
root "/acme"
}
}
types {
include "/usr/share/misc/mime.types"
}
Test your configuration, enable and start httpd(8):
$ doas httpd -n
$ doas rcctl enable httpd
$ doas rcctl start httpd
The next step is to configure
acme-client(1). To save some
typing I suggest you use /etc/examples/acme-client.conf
as source and
modified accordingly. The resulting
acme-client.conf must be
saved in /etc
and look similar:
authority letsencrypt {
api url "https://acme-v02.api.letsencrypt.org/directory"
account key "/etc/acme/letsencrypt-privkey.pem"
}
authority letsencrypt-staging {
api url "https://acme-staging.api.letsencrypt.org/directory"
account key "/etc/acme/letsencrypt-staging-privkey.pem"
}
domain ns1.example.net {
domain key "/etc/ssl/acme/private/ns1.example.net.key"
domain certificate "/etc/ssl/acme/ns1.example.net.crt"
domain full chain certificate "/etc/ssl/acme/ns1.example.net.fullchain.pem"
sign with letsencrypt
}
Before you can get your certificate you must make sure pf(4) lets the requests to httpd(8) pass through. Add a rule similar to the following one to your pf.conf(5):
pass in on egress proto tcp from any to egress port http
Check the configuration in /etc/pf.conf
and load it into pf(4) using
the following commands:
$ doas pfctl -nf /etc/pf.conf
$ doas pfctl -f /etc/pf.conf
Now you are ready to fetch the certificate for nsd(8). Don’t forget to create the OCSP file too:
$ dest=/etc/ssl/ns1.example.net
$ doas acme-client ns1.example.net
$ doas ocspcheck -No ${dest}.ocsp ${dest}.fullchain.pem
You want to make sure that the certificate as well as the OCSP response
get renewed before these expire. I suggest you add something like this
to /etc/daily.local
:
#!/bin/sh
dest=/etc/ssl/ns1.example.net
acme-client ns1.example.net
if [ $? -eq 0 ] ; then
ocspcheck -No ${dest}.ocsp ${dest}.fullchain.pem
rcctl restart nsd
fi
oscpcheck -i ${dest}.ocsp ${dest}.fullchain.pem
if [ $? -eq 1 ] ; then
ocspcheck -No ${dest}.ocsp ${dest}.fullchain.pem
rcctl restart nsd
fi
The script checks the cerificate first. If it gets renewed the script fetches the OCSP response for the new certificate and restarts nsd(8) to load the new files. In the second step the script checks if the OCSP response has expired. If this is the case it fetches the new OCSP response and restarts nsd(8) to load the new OCSP response.
The correct configuration of nsd(8) depends on the role the server is going to play: primary or secondary. Most domain providers require you to run at least two name servers in preferably two different sub-nets. I will show you both configurations. First the configuration for the primary:
server:
hide-identity: yes
hide-version: yes
ip-address: 192.0.2.11
ip-address: 192.0.2.11@853
ip-address: 2001:db8:1::c000:020b
ip-address: 2001:db8:1::c000:020b@853
server-count: 1
statistics: 86400
tls-service-key: "/etc/ssl/ns1.example.net.key"
tls-service-pem: "/etc/ssl/ns1.example.net.fullchain.pem"
tls-service-ocsp: "/etc/ssl/ns1.example.net.ocsp"
verbosity: 1
xfrdfile: "/var/nsd/db/xfrd.state"
remote-control:
control-enable: yes
key:
name: nskey
algorithm: sha256
secret: "IAmASecretKeyForDomainTransfers"
zone:
name: "example.net"
zonefile: "/var/nsd/zones/master/%s.dns"
notify: 198.51.100.12 nskey
notify: 2001:db8:2::c633:640c nskey
provide-xfr: 198.51.100.12 nskey
provide-xfr: 2001:db8:2::c633:640c nskey
outgoing-interface: 192.0.2.11
outgoing-interface: 2001:db8:1::c000:020b
Make sure both primary and secondary server use the same secret key for domain transfer or it will not work. You can generate the secret for the domain transfer key using the following command:
$ for i in $(jot -r -s " " 32 0 255) ; do
> echo ${i} | awk '{ printf "%c", $1 }'
> done | sha256 -b
The next step is to make sure the file for your zone contains some valid data. Either you already have a valid zone file or you can use the following one as a starting point:
$ORIGIN .
$TTL 3600 ; 1 hour
example.net IN SOA ns1.example.net. hostmaster.example.net. (
1 ; serial
10800 ; refresh (3 hours)
600 ; retry (10 minutes)
241900 ; expire (4 weeks)
3600 ; minimum (1 hour)
)
NS ns1.example.net.
NS ns2.example.net.
$ORIGIN example.net.
ns1 A 192.0.2.11
AAAA 2001:db8:1::c000:020b
ns2 A 198.51.100.12
AAAA 2001:db8:2::c633:640c
The configuration of the secondary server needs slightly other options to make it a secondary, but looks similar to the one for the primary server:
server:
hide-identity: yes
hide-version: yes
ip-address: 198.51.100.12
ip-address: 198.51.100.12@853
ip-address: 2001:db8:2::c633:640c
ip-address: 2001:db8:2::c633:640c@853
server-count: 1
statistics: 86400
tls-cert-bundle: "/etc/ssl/cert.pem"
tls-service-key: "/etc/ssl/ns2.example.net.key"
tls-service-pem: "/etc/ssl/ns2.example.net.fullchain.pem"
tls-service-ocsp: "/etc/ssl/ns2.example.net.ocsp"
verbosity: 1
xfrdfile: "/var/nsd/db/xfrd.state"
remote-control:
control-enable: yes
key:
name: nskey
algorithm: sha256
secret: "IAmASecretKeyForDomainTransfers"
tls-auth:
name: xot.example.net
auth-domain-name: ns1.example.net
zone:
name: "example.net"
zonefile: "/var/nsd/zones/slave/%s.dns"
allow-notify: 192.0.2.11 nskey
allow-notify: 2001:db8:1::c000:020b nskey
request-xfr: AXFR 192.0.2.11 nskey xot.example.net
request-xfr: AXFR 2001:db8:1::c000:020b nskey xot.example.net
outgoing-interface: 198.51.100.12
outgoing-interface: 2001:db8:2::c633:640c
On both servers you must run the following command to make remote control using nsd-control(8) work:
$ doas nsd-control-setup
Now it is time to check your configuration. Run the following command on each server to find the typos:
$ doas nsd-checkconf /var/nsd/etc/nsd.conf
On the secondary server you must make sure that nsd(8) is able to verify
the certificate of the primary in order for XoT to work. As nsd(8) runs
chroot(2) to /var/nsd
you need to copy the original file to the right
place:
$ doas mkdir /var/nsd/etc/ssl
$ doas install -m 444 -o root -g bin /etc/ssl/cert.pem /var/nsd/etc/ssl/cert.pem
As the file cert.pem changes from time to time - if you upgrade your
system - you may want to enter the above install(1)
command into /etc/rc.local
to make sure the copy in /var/nsd/etc/ssl
gets updated too.
If everything looks good you are ready to enable and start nsd(8) on both servers:
$ doas rcctl enable nsd
$ doas rcctl start nsd
The last piece of the puzzle are the rules for pf(4) that allow external
access to nsd(8). Add these two lines to /etc/pf.conf
:
pass in on egress proto { tcp udp } from any to egress port domain
pass in on egress proto { tcp udp } from any to egress port domain-s
Check and load the changed configuration:
$ doas pfctl -nf /etc/pf.conf
$ doas pfctl -f /etc/pf.conf