DNSSEC with BIND 9

A Beginner's Guide to DNSSEC with BIND 9

DNSSEC with BIND 9

DNSSEC is a large topic and as such can initially appear quite daunting. But if we take a simple configuration and break it down into a series of steps then you can quickly build confidence converting your existing insecure zones to DNSSEC signed zones with BIND 9.

This article shows how to configure a new or existing domain for DNSSEC with BIND 9.19, as well as the additional steps necessary so that your domain registrar also acknowledges that your domain is now DNSSEC enabled.

Server names, Domain name, and other Assumptions

Throughout this article I’ll use the following values for server names (for the BIND 9 servers), and the domain name (for which we’re enabling DNSSEC):

  • Server names: ns1.flodns.net, ns2.flodns.net
  • Domain name: talkdns.com

Wherever you see these values below you should of course replace them with your own server names and domain name. All commands should be run on the primary name server unless specified otherwise.

I’ll also be making the following assumptions:

  • You have a working BIND 9 primary & secondary setup that’s publicly accessible
  • You know how to instruct your registrar to use your BIND 9 servers for the domain you want to host on BIND 9
  • BIND configuration files are stored in /etc/bind
  • Zone files are all going to be stored in /etc/bind/zones
  • Key files are all going to be stored in /etc/bind/keys

I use Namecheap as my registrar so I’ve also published an article about that second bullet point here.

A quick word of warning about copy & paste: if you’re copy/pasting between Windows & Linux VMs it can cause problems with spaces, tabs and line breaks. If you get any syntax errors reported the first thing to do is to try using something like Windows Notepad as a go between when copying between your browser and your config files.

Configuration Steps

We’ll go through the following steps in this article:

  1. Create a fresh unsigned zone for a new domain
  2. Setup the keys directory and give BIND the appropriate permissions
  3. Update AppArmor (for those using Ubuntu)
  4. Update the primary name server to instruct BIND that we want it to sign the zone file
  5. Update the secondary name server
  6. Instruct BIND to reload and sign the zone file
  7. Test locally to make sure that our zone is now signed
  8. Backup our key files
  9. Generate the DS (Delegation Signer) key required by some domain registrars
  10. Update our domain registrar with the DS key so that it starts to publish our domain as being DNSSEC-enabled
  11. Externally validate DNSSEC using both dig and a third party tool
  12. Adding, deleting or updating records once the zone has been signed
  13. Viewing the signed zone file
  14. All done!

1. Create a fresh unsigned zone for a new domain

You can skip this step if you’re already hosting the domain on BIND 9 for which you now want to enable DNSSEC. But if you want to enable DNSSEC for a new domain it’s a good idea to first setup the domain as a regular unsigned zone file to confirm that it’s working as expected. To do so you simply need to create the zone as a file on the primary name server, and then reference it in your named.conf.local like this:

zone "talkdns.com" {
     type primary;
     file "/etc/bind/zones/db.talkdns.com";
     allow-transfer { 198.244.241.102; };      # this is ns2.flodns.net
};

Remember to add the zone to your secondary name server as well:

zone "talkdns.com" {
     type secondary;
     file "db.talkdns.com";
     primaries { 198.244.241.101; };      # this is ns1.flodns.net
};

Once you’ve defined the zones on both your primary and secondary name server you can (re)load the zone into BIND like this:

$ sudo rndc reload talkdns.com

Then just check that BIND is responding as expected for that domain name on both name servers:

$ dig @ns1.flodns.net www.talkdns.com
$ dig @ns2.flodns.net www.talkdns.com

2. Setup the keys directory and give BIND the appropriate permissions

We’re going to store our keys in a subdirectory of our main /etc/bind directory and BIND needs full read/write access to that directory. Naturally we only need to do this step once since the key files for all DNSSEC zones will be stored here. On my name servers BIND is running with a username of ‘bind‘ so that’s what you see in the chown command below:

$ sudo mkdir /etc/bind/keys
$ sudo chown -R bind /etc/bind/keys

3. Update AppArmor (for those using Ubuntu)

If your BIND name servers are running on Ubuntu then you’re going to need to update AppArmor to tell it that BIND is allowed to write to both our zones subdirectory and our keys subdirectory. If you’ve also followed my guide to setup DNS over HTTPS on Ubuntu then you’ve already created a separate AppArmor named profile which we’ll just be editing now, but in case you haven’t I’ll explain it again here:

By default AppArmor will block the named process from writing to the zones and keys directories regardless of the permissions applied to those directories (remember we’ve already made our bind user the owner of the keys subdirectory). Rather than modifying AppArmor’s named profile (which would be overwritten during any AppArmor upgrade) we create a special  AppArmor local file. These local files are a supported way of telling AppArmor that we have a slightly special configuration and they are preserved during an upgrade. We only need to create or edit this file on our primary name server because that’s the one which managed the keys and actively signs the zone files. The secondary name server simply stores a copy of the signed zone files (but not the keys) just as it stores copies of unsigned zone files.

$ sudo nano /etc/apparmor.d/local/usr.sbin.named

This file should contain either two or three lines as follows, including the comma at the end of each line. It allows recursive read-only (r) or read/write (rw) access to all files and folders in the specified directory:

/etc/letsencrypt/** r,     # only required if you're also running DNS over HTTPS as per one of my other guides
/etc/bind/zones/** rw,     # gives read/write access to our zones directory
                           #  - (BIND creates a new zonefile.signed file for each zone)
/etc/bind/keys/** rw,      # gives read/write access to our keys directory
                           #  – (which is where BIND will store the key files for our zones)

Now we need to reload the named AppArmor profile:

$ sudo apparmor_parser -r /etc/apparmor.d/usr.sbin.named

4. Update the primary name server to instruct BIND that we want it to sign the zone file

Now that we’re happy our unsigned zone is working and that we’ve created and permissioned the required subdirectories our next step it to tell BIND to go ahead and sign the zone file for us, creating the corresponding RRSIG record for each DNS record. It’s possible to tell BIND to sign all zone files automatically by setting an appropriate option in named.conf.options but since this is our first DNSSEC zone we want to do them one at a time. This gives us full control over which zones are signed and which are unsigned, and it allows us to get used to DNSSEC and troubleshoot any problems before mass-signing all zones on our name servers.

Take a look at step 1 (above) to remind yourself how the unsigned zone definition looks for talkdns.com. Update the zone definition in named.conf.local on your primary name server as follows:

zone "talkdns.com" {
     type primary;
     file "/etc/bind/zones/db.talkdns.com";
     allow-transfer {198.244.241.102; };      # this is ns2.flodns.net
     dnssec-policy default;                   # this enables BIND's fully automated key and signing policy
                                              #  - (ISC's recommended way to manage DNSSEC)
     key-directory "/etc/bind/keys";          # this sets the directory in which this zone's DNSSEC keys will be stored
     inline-signing yes;                      # this allows BIND to transparently update our signed zone file
                                              # whenever we change the unsigned file
};

5. Update the secondary name server

This isn’t strictly necessary since all we’re going to do is change the filename used by the secondary name server to store our signed zone file information. But I like to do it so that I can see at a glance which zones are signed on my secondary, and it will also confirm that BIND on the secondary is correctly transferring the signed zone from the primary. Once again take a quick look at step 1 (above) to remind yourself how I referenced the unsigned zone on my secondary name server, then edit named.conf.local on your secondary name server as follows:

zone "talkdns.com" {
     type secondary;
     file "db.talkdns.com.signed";
     primaries { 198.244.241.101; };      # this is ns1.flodns.net
};

As you can see, all we’ve done is change the zone’s file name to end in ‘.signed‘ for easy identification.

6. Instruct BIND to reload and sign the zone file

Before instructing BIND to reload and sign our zone file it’s a good idea to validate our updated named.conf.local on both the primary and secondary name server:

$ sudo named-checkconf /etc/bind/named.conf.local

As a reminder we should get no output if there are no errors in the file. If the tool spots an error in the config file it will tell us the reason and the line number so that we can correct it. Very often it’s something as simple as a syntax error such as forgetting to end the line with a semicolon.

If our config files are OK then we can instruct BIND to re-read its configuration files which will trigger BIND to create the additional RRSIG records, sign the zone, and create a new binary zone file ending in ‘.signed‘:

$ sudo rndc reconfig

If all goes well you’ll get dropped straight back to the command prompt, and if you quickly type tail var/log/syslog you should see something like this:

general: info: zone talkdns.com/IN: (primary) removed
general: info: reloading configuration succeeded
scheduled loading new zones
zoneload: info: zone talkdns.com/IN (unsigned): loaded serial 1
zoneload: info: zone talkdns.com/IN (signed): loaded serial 1
general: info: any newly configured zones are now loaded
general: notice: running
general: info: zone talkdns.com/IN (signed): receive_secure_serial: unchanged

If we now do the same on the secondary (sudo rndc reconfig followed by tail /var/log/syslog) we should see something similar to this:

general: info: zone talkdns.com/IN: (secondary) removed
general: info: reloading configuration succeeded
scheduled loading new zones
general: info: any newly configured zones are now loaded
general: notice: running

If you got results similar to the above then you’re all set – BIND has created the additional RRSIG records, signed the zone, and successfully transferred it to the secondary.

7. Test locally to make sure that our zone is now signed

We should of course now test that it is indeed working as expected with DNSSEC before updating our domain registrar to assert that this domain is now DNSSEC-enabled. This is very easy to do using BIND’s dig command:

$ dig @ns1.flodns.net talkdns.com DNSKEY +dnssec +multi

This is essentially instructing dig to query ns1.flodns.net for the SOA record (Start of Authority) for talkdns.com, and it specifically asks for the DNSSEC records to also be sent in return. You should see something like this in the result:

;; ANSWER SECTION:

talkdns.com.            3600 IN DNSKEY 257 3 13 (
                                J686S3QB5JL86P0hq5333H+GSvreN7HKaJFWN…==);
                                KSK; alg = ECDSAP256SHA256 ; key id = 7337

talkdns.com.            3600 IN RRSIG DNSKEY 13 2 3600 (
                                20221101233149 20221018223149 7337 talkdns.com.
                                Qf92gUEUc8VvwsepgiJIA4v35veG9GVvxOJA1zx…== )

I’ve trimmed the public key in each case (the line ending ‘…==’).  This shows you the key tag (or key ID): 7337 in this case. It also confirms the algorithm used to generate the private/public key pair: ECDSAP 256 SHA 256 in our case. These results are your confirmation that BIND has successfully enabled DNSSEC for this domain. You should now repeat the dig command but aim it at your secondary name server to make sure it’s successfully received a copy of the signed zone from the primary name server:

dig @ns2.flodns.net talkdns.com DNSKEY +dnssec +multi

Provided you get back exactly the same result as when querying your primary name server you can be happy that DNSSEC is now working correctly this domain.

8. Backup our key files

The next step is to backup our key files. This is important as they will be required in disaster recovery scenarios if our primary name server fails catastrophically. Based on what we set in named.conf.local in step 4 (above) you will find the key files in /etc/bind/keys. You’ll need to temporarily grant yourself access permissions to the key files, then use something like SFTP to copy them across to another system. In a future article I’ll explain how we recover from such an event without having to rebuild our zone file from scratch, re-sign the zone, and republish fresh keys with our registrar.

9. Generate the DS (Delegation Signer) key required by some domain registrars

DNSSEC is now working locally between our two BIND servers but we haven’t yet asserted to our registrar that our domain is DNSSEC enabled. This is an important step in the trust verification process and will ensure that remote clients trust the RRSIG records they’re given by your BIND name servers in response to any DNS queries.

The registrar for both flodns.net and talkdns.com is Namecheap, and Namecheap require that I generate and publish a DS key for each zone I wish to enable for DNSSEC. BIND comes with a tool to generate the DS key from the private key generated by BIND for our zone file.

Take another look at the file names of those key files you backed-up in step 8 (you did back them up, right?). You’ll see they’re called something like this:

Ktalkdns.com.+013+07337.key
Ktalkdns.com.+013+07337.private
Ktalkdns.com.+013+07337.state

Section 5.1.1 of the BIND 9.19 ARM explains the purpose and naming convention of each of these files in more detail but suffice to say the .key file contains the public key for our signed zone file, and it’s that which we will use to generate our DS key which Namecheap needs.

Let’s use BIND’s built-in tool to generate our DS key from our zone’s public key:

$ dnssec-dsfromkey -2 Ktalkdns.com.+013+07337

Replace the filename Ktalkdns.com.+013+07337 with the filename in your etc/bind/keys directory. You don’t need to specify the .key suffix.

The -2 means “use the SHA-256 algorithm” to generate the DS key, and that’s the best option for Namecheap. Your registrar might require something slightly different, but you should be able to generate what it needs by reviewing the dnssec-dsfromkey documentation here. The output should be something like this:

talkdns.com. IN DS 7337 13 2 7924C6DA5606094802F79F869FE1ED871B980A9CEA7C69CAFF0BB1DEB63C7EF4

This can be broken down as follows:

  • 7337: this is the key tag, a non-unique identifier
  • 13: this is the algorithm number (which in this case corresponds to ECDSAP256SHA256)
  • 2: this is the digest type (in this case SHA-256)
  • 7924C6DA…: this is the digest (a string value generated by the algorithm)

10. Update our domain registrar with the DS key

This will likely be a slightly different process if you’re using a registrar other than Namecheap but hopefully it’s similar enough that you can follow the steps. In Namecheap we go to our Domains control panel, locate the domain for which we want to enable DNSSEC, set it to enabled, and then type in the values given to us by the dnssec-dsfromkey tool for the key tag, the algorithm number, the digest type and the digest. That’s it. We then wait up to an hour for this to be pushed upstream within Namecheap.

11. Externally validate DNSSEC using both dig and a third party tool

Once you’ve asserted to your registrar that your domain is now DNSSEC-enabled it’s a good idea to test it using an external tool. You can of course use dig from another device, but VERISIGN makes available a free tool for DNSSEC validation which validates the entire chain and alerts you to any potential problems: VERISIGN DNSSEC Analyzer.

Another extremely helpful tool is dnsviz.net – this is a free service that provides a wealth of information about your DNS zone. It’s well worth a visit and it provides a great deal of useful troubleshooting information if there are any problems with your DNS records.

12. Adding, deleting or updating records once the zone has been signed

There’s no change to the process of adding, deleting or updating records in our zone file once it’s been signed by BIND. We simply update the traditional zone file as normal (so in my case, db.talkdns.com). Once we’ve updated the serial number, saved the file and run sudo rndc reload talkdns.com BIND will read our updated file and update the binary version of the file in the same folder: db.talkdns.com.signed. Any changes will then be propagated to the secondary name server as normal.

It’s therefore very important that you don’t delete the original zone file for our unsigned domain. The process of enabling DNSSEC for our domain doesn’t change how we maintain the domain, whether by hand-editing the unsigned zone file or using whatever method we may have previously used (e.g. nsupdate).

13. Viewing the signed zone file

The signed zone file is a binary file that isn’t hand-editable but if you’re curious as to its contents you can output the file in human friendly format as follows:

$ sudo rndc sync
$ sudo named-checkzone -D -f raw -o - talkdns.com /etc/bind/zones/db.talkdns.com.signed | less

Here the rndc sync command simply tells BIND to synchronise all pending changes in the journal file (db.talkdns.com.jnl) into the primary zone file before we then run the named-checkzone command to output the contents.

14. All done!

That’s it! You’ve now created your first DNSSEC-enabled domain & zone, published the DS records with your registrar, and externally validated it using a third party tool!

References and Acknowledgements

I used the following guides to create my own DNSSEC-enabled zone as part of writing this article. My thanks to everyone who’s contributed to these. Some of the steps in the NSRC article were unfortunately out of date (it uses deprecated syntax from an older version of BIND 9) which is what prompted me to write this article instead.

https://downloads.isc.org/isc/bind9/9.19.5/doc/arm/html/chapter5.html

https://bind9.readthedocs.io/en/v9_19_5/dnssec-guide.html

https://bind9.readthedocs.io/en/v9_19_5/manpages.html#std-iscman-dnssec-dsfromkey

https://nsrc.org/activities/agendas/en/dnssec-3-days/dns/materials/labs/en/dnssec-bind-inline-signing-howto.html

A special thanks to JP Mens on the BIND users mailing list for critiquing this guide and making a number of suggestions and improvements, all of which have been incorporated.