Back to BIND (with OpenDNSSEC)
What
This 2-part how-to will present how to set up Bind9 and OpenDNSSEC to work together to provide some of the many possible features offered by Bind while relying on the solid implementation and easy management of DNSSEC using OpenDNSSEC.
Setting up OpenDNSSEC is not part of this article.
Once in place, the second part of this series will look into implementing dynamic DNS in order to allow Let’sEncrypt® EFF certbot client to automatically obtain or renew a wildcard certificate on a sub-domain.
While Bind9 supports DNSSEC natively, I suffered set-backs in the past which lead me to move to NSD + OpenDNSSEC at the time. From what I read, the rolling implementation is still fairly manual with Bind9. OpenDNSSEC is pretty good at maintaining and rolling your DNSSEC keys for you, making it the perfect tool for this.
Why
Until recently I was quite happy with an NSD / OpenDNSSEC pair. Both tools have been pretty solid (as long as you take particular care for the big opendnssec version jump from 1.4 to 2.X) and I haven’t had any DNS issues for a very long time. So why changing?
2 reasons:
- dyn-dns
Dynamic DNS has been around for a long time now, with full support from Bind9. Unfortunately NSD does not currently support it and has made no announcement regarding implementing this feature. - split view horizon
DNS split view is another Bind9-supported feature which allows an administrator to provide different response depending of where the request initiates from.
How
I found little documentation on this online while I think this is a really interesting set up to keep things separate. Splitting your components makes it easier to identify what could cause issue in the future. Typically while implementing and troubleshooting, depending on which daemon log was raising errors (bind9 or opendnssec-signerd) I could easily identify at which point my requests were failing.
Basic Bind setup
This tutorial is based on a GNU/Linux Debian Stretch distribution. The paradigm will be similar for other distributions, however some paths may be different, in which case you’ll have to adjust.
all the following commands are run as the root user
Let’s install bind first:
apt-get update && apt-get install bind9
systemctl stop bind9
We now have a working BIND9 server. Let’s start by disabling the named.conf.default-zones sourcing in /etc/bind/named.conf
//include "/etc/bind/named.conf.default-zones";
We need to do this because we are going to use views to handle the communication/ transfer between BIND and OpenDNSSEC. When using views, no zone should be declared outside of a view.
Fantastic TSIG keys and where to set them
The following is largely inspired from the following article, which was a great way for me to get started: https://0x4a42.net/2016/bind-with-dynamic-zone-updates-and-opendnssec/
TSIG (Transaction SIGnature) is a computer-networking protocol defined originally in RFC 2845 and implemented as part of RFC 2136 — Dynamic Updates in the Domain Name System (DNS UPDATE). Primarily it enables the Domain Name System (DNS) to authenticate updates to a DNS database. – Source: Wikipedia
While the dynamic side of things will be explored further in the next article, for now we will use TSIG keys as a way to authenticate and provide authorisations between BIND and OpenDNSSEC.
To generate a TSIG key we’ll use BIND’s tool dnssec-keygen. Please note that the key name and hash algorithms need to match on each side of the setup, otherwise you will get the dreaded error in bind logs: tsig verify failure (BADKEY)
.
Let’s generate 2 keys, one to send the unsigned zone to OpenDNSSEC, and another one to receive the signed zone from OpenDNSSEC. It’s 2018, so let’s be on the safer side and use sha512 for the generated hash.
mkdir /etc/bind/tsig && cd /etc/bind/tsig
dnssec-keygen -a HMAC-SHA512 -b 512 -n HOST opendnssec-in
dnssec-keygen -a HMAC-SHA512 -b 512 -n HOST opendnssec-out
This will create K.key and K.private files (one pair for each key). We’ll be using the information contained in K**.private whenever we need to get the keys TSIG parameters.
cat Kopendnssec-*.private
[output]
Private-key-format: v1.3
Algorithm: 165 (HMAC_SHA512)
Key: JAWISMt/foRqHlnpbyBlrU8etMKd6HhRkKjGkYA5UT0o8bFRgN7T/nXeFmQGa/PSrBJV72ckFCUMP0D0ZbGNDQ==
Bits: AAA=
Created: 20181111110556
Publish: 20181111110556
Activate: 20181111110556
Private-key-format: v1.3
Algorithm: 165 (HMAC_SHA512)
Key: 2q67mwtvvmh7z/sSg9dwp78fo7/r2G8CYUaVBbX4fLwyeECcKBrJ/LepdeR34ncNc295BdRGrdauVz3uMkyWXw==
Bits: AAA=
Created: 20181111110559
Publish: 20181111110559
Activate: 20181111110559
Of course, do not make your key publicly visible to anyone for obvious safety reasons.
Getting Bind ready to send unsigned zone and receive signed ones
This is where views come in handy.
First we set up the required keys and acls, then we declare our unsigned view, which will contain our traditional zone file.
To prepare for the rest of the tutorial, create 2 directories:
/etc/bind/unsigned/
will contain your regular flat text zone files./etc/bind/signed/
is where the signed zone files will be stored, in BIND binary/raw after retrieving the zone from OpenDNSSEC. This is because the signed zone is defined as a slave of OpenDNSSEC (no files should be created or moved manually in this directory).
After editing /etc/bind/named.conf.local
you should get something similar to:
// ####### Keys #########
key opendnssec-in {
algorithm hmac-sha512;
secret "JAWISMt/foRqHlnpbyBlrU8etMKd6HhRkKjGkYA5UT0o8bFRgN7T/nXeFmQGa/PSrBJV72ckFCUMP0D0ZbGNDQ==";
};
key opendnssec-out {
algorithm hmac-sha512;
secret "2q67mwtvvmh7z/sSg9dwp78fo7/r2G8CYUaVBbX4fLwyeECcKBrJ/LepdeR34ncNc295BdRGrdauVz3uMkyWXw";
};
// ####### ACLs #########
acl unsigned {
key opendnssec-in;
};
// ####### VIEWS #########
view unsigned {
// We want this view to apply to all connections using one
// of the keys in the 'unsigned' ACL, so we just reference
// that ACL here.
match-clients {
unsigned;
};
// Now follows a pretty normal zone definiton.
zone example.com {
type master;
file "/etc/bind/unsigned/example.com";
// allow transfers to opendnssec using its key.
allow-transfer {
key "opendnssec-in";
};
// notify opendnssec which listens on port 51.
also-notify {
::1 port 51;
};
};
view default {
// This view applies to everything else.
// ###########################
// shared options for the view
// ###########################
match-clients { any; };
// ############
// Zones
// ############
zone example.com {
type slave;
file "/etc/bind/signed/db.example.com";
masters {
::1 port 51 key opendnssec-out;
};
};
So, pretty self-explanatory: anything querying with the opendnssec-in TSIG key will fall into the "unsigned" view, containing unsigned zone file, while anything else will be served by the "default" view, containing DNSSEC-signed zone files.
Upon reload of the example.com zone from unsigned view, BIND will notify OpenDNSSEC on localhost and TCP port 51 (cf also-notify). The later will request a zone transfer using the TSIG key so that again the request reached the unsigned zone and BIND allows the transfer (cf allow-transfer).
After the zone is received by OpenDNSSEC, it will sign it and send the result to BIND whenever requested through the default view, since it is declared as BIND master for the example.com zone in that view.
So how is that actually set up on OpenDNSSEC side? Pretty simply actually!
Getting OpenDNSSEC ready to work with BIND
IMPORTANT: during set-up and first tests we had problems with what appeared to be a bug in the current Debian Stretch version of OpenDNSSEC, which is fixed on the version currently available in Sid (cf changelog for version 2.1.3). For this reason we start with upgrading to latest available version on Sid repository.
Ensure you enable Sid repository and upgrade to latest version (2.1.3 at the time of this writing).
egrep sid /etc/apt/sources.list
deb http://deb.debian.org/debian sid main contrib
apt-get install -t sid opendnssec opendnssec-enforcer opendnssec-common opendnssec-enforcer-sqlite3 opendnssec-signer
systemctl status opendnssec-signer opendnssec-enforcer
Now we are going to edit the following 3 files:
- conf.xml
We need to add a Listener on localhost port 51 to allow communication with BIND:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
[..]
<Common>
<Logging>
<!-- Command line verbosity will overwrite configure file -->
<Verbosity>3</Verbosity>
<Syslog><Facility>local0</Facility></Syslog>
</Logging>
<PolicyFile>/etc/opendnssec/kasp.xml</PolicyFile>
<ZoneListFile>/etc/opendnssec/zonelist.xml</ZoneListFile>
</Common>
<Enforcer>
[..]
</Enforcer>
<Signer>
[..]
<Listener>
<Interface>
<Port>51</Port>
<Address>::1</Address>
</Interface>
</Listener>
[..]
</Signer>
</Configuration>
- addns.xml
Here we declare the same TSIG keys so that we are able to authenticate (opendnssec-out) and get authenticated (opendnssec-in) by BIND.
<?xml version="1.0" encoding="UTF-8"?>
<Adapter>
<DNS>
<TSIG>
<Name>opendnssec-in</Name>
<Algorithm>hmac-sha512</Algorithm>
<Secret>LONG_SECRET_HASH</Secret>
</TSIG>
<TSIG>
<Name>opendnssec-out</Name>
<Algorithm>hmac-sha512</Algorithm>
<Secret>LONG_SECRET_HASH</Secret>
</TSIG>
<Inbound>
<!-- Address of host to request XFR from -->
<RequestTransfer>
<!-- send request to localhost on port 5353 -->
<Remote>
<Address>::1</Address>
<Port>53</Port>
<Key>opendnssec-in</Key>
</Remote>
</RequestTransfer>
<!-- Allow NOTIFY messages from host -->
<AllowNotify>
<!-- allow notifies from localhost -->
<Peer>
<Prefix>::1</Prefix>
</Peer>
</AllowNotify>
</Inbound>
<Outbound>
<!-- Provide XFR to host -->
<ProvideTransfer>
<Peer>
<Prefix>::1</Prefix>
<Key>opendnssec-out</Key>
</Peer>
</ProvideTransfer>
<!-- Send NOTIFY messages to host -->
<Notify>
<Remote>
<Address>::1</Address>
<Port>53</Port>
</Remote>
</Notify>
</Outbound>
</DNS>
</Adapter>
- zonelist.xml
This is a bit of a tricky one. Officially not required anymore, and not read on reload or restart of the daemons. However, I haven’t found any other way to update a zone settings than by using this file. Here we will basically tell OpenDNSSEC to treat our example.com zone input and output requests as an on-demand network service.
<ZoneList>
<Zone name="example.com">
<Policy>default</Policy>
<SignerConfiguration>/var/lib/opendnssec/signconf/example.com.xml</SignerConfiguration>
<Adapters>
<Input>
<Adapter type="DNS">/etc/opendnssec/addns.xml</Adapter>
</Input>
<Output>
<Adapter type="DNS">/etc/opendnssec/addns.xml</Adapter>
</Output>
</Adapters>
</Zone>
[.. other zones ..]
</ZoneList>
That’s pretty much it, now restart both bind9 and opendnssec-signer daemon and ensure they start properly:
systemctl restart bind9 opendnssec-signer
ods-enforcer zonelist import
systemctl status bind9 opendnssec-signer
Also, I recommend to open another session locally to follow the logs of both services:
journalctl -fu bind9 -fu opendnssec-signer
Testing
Testing is pretty straightforward, update your /etc/bind/unsigned/example.com
zone file and reload it using bind command-line tool rndc
.
If all goes well, you will see the whole zone going back and forth between BIND and OpenDNSSEC, and you’ll be able to test your new entry straight after by querying the BIND server directly.
-
Update your zone file (don’t forget to increase the serial number)
-
Reload the zone
rndc reload example.com IN unsigned
-
Query the server with the default view
dig newrecord.example.com @localhost
That should be it! Looking at your logs you should see something similar to:
Nov 09 16:52:59 myhots2 named[5276]: client ::1#39363/key opendnssec-in (example.com): view unsigned: transfer of 'example.com/IN': AXFR-style IXFR started: TSIG opendnssec-in (serial 2018110802)
Nov 09 16:52:59 myhots2 named[5276]: client ::1#39363/key opendnssec-in (example.com): view unsigned: transfer of 'example.com/IN': AXFR-style IXFR ended
Nov 09 16:52:59 myhots2 ods-signerd[142]: [xfrd] zone example.com transfer done [notify acquired 1541782379, serial on disk 2018110802, notify serial 2018110802]
Nov 09 16:52:59 myhots2 named[5276]: all zones loaded
Nov 09 16:52:59 myhots2 named[5276]: running
Nov 09 16:52:59 myhots2 ods-signerd[142]: [STATS] example.com 2018110902 RR[count=1 time=0(sec)] NSEC3[count=1 time=0(sec)] RRSIG[new=2 reused=241 time=0(sec) avg=0(sig/sec)] TOTAL[time=0(sec)]
Nov 09 16:52:59 myhots2 named[5276]: client ::1#49877: view default: received notify for zone 'example.com'
Nov 09 16:52:59 myhots2 named[5276]: zone example.com/IN/default: notify from ::1#49877: serial 2018110902
Nov 09 16:52:59 myhots2 named[5276]: zone example.com/IN/default: Transfer started.
Nov 09 16:52:59 myhots2 named[5276]: transfer of 'example.com/IN/default' from ::1#51: connected using ::1#45669
Nov 09 16:52:59 myhots2 named[5276]: zone example.com/IN/default: transferred serial 2018110902: TSIG 'opendnssec-out'
Now let’s add a little bit of dynamic DNS update to the mix: Part 2: Let’s Encrypt wildcard certificates via certbot and RFC2136 with BIND.
Links
Here below are various resources used in the past regarding BIND, NSD and OpenDNSSEC.
http://movingpackets.net/2013/06/10/bind-enabling-tsig-for-zone-transfers/
https://calomel.org/nsd_dns.html
http://www.bortzmeyer.org/opendnssec-nsd.html (French)
https://0x4a42.net/2016/bind-with-dynamic-zone-updates-and-opendnssec/