Configure your DNS server to check zone signatures using DNSSEC.
Last month, I described, in detail, the problem of DNS cache poisoning and why it's fundamentally changed our understanding of DNS security. Whereas previously it seemed good enough to keep one's DNS server patched and limit the hosts for which it performed recursive queries and zone transfers, we now have no choice but to pay attention to the authenticity of DNS data that our local resolvers and recursing DNS servers receive from other servers.
This is because the way DNS recursion works makes it too easy for an attacker to trigger events that lead directly to that attacker's injecting forged DNS data into a recursing server's cache, resulting in all users who rely on that server being redirected to impostor “evil twin” sites for specific e-commerce and on-line banking sites, or malicious, malware-spreading Web sites and so forth.
I concluded last month by explaining that although the short-term fix to Kaminsky's cache-poisoning attack is to patch DNS software so that recursing servers randomize their source UDP ports for DNS queries, this only makes the attack take longer (albeit, much longer); it doesn't eliminate it as a threat. The best protection, rather, is for administrators of authoritative DNS servers to sign all their zone data cryptographically, and for administrators of recursing or caching DNS servers to configure their servers to check the signatures of all signed zones they come across.
All of this signing/validating functionality is achieved by way of DNSSEC, a set of extensions to the DNS protocol. Most modern DNS server software packages now support DNSSEC (with djbdns as the most notable exception).
This month, I explain how to configure your recursing/caching DNS server to check DNS zone data signatures. Because the Internet Software Consortium's BIND package is by far the most popular DNS server application for UNIX and UNIX-like systems, my examples all involve BIND.
If you administer your own DNS zones, you also should sign your own zones and publish your certificates and signatures, but that's out of scope for this article. (See Resources for links to other DNSSEC information and tutorials. I may cover zone signing and DNSSEC key management in a future column as well.)
Note that my preferred Linux server distribution nowadays is Ubuntu Server 10.10, so my examples all apply directly to Ubuntu and other Debian derivatives. If you run some other distribution, my examples still should be useful, because the only peculiar thing Ubuntu and Debian do in how they package BIND is to break up its configuration file (named.conf) into several parts (named.conf.options, named.conf.local and named.conf.default-zones) that are read into named.conf via “include” statements.
Mainly what I want you to get out of this article is how to enable DNSSEC validation on your BIND-based nameserver. I probably could fill most of this space with an overview of what DNSSEC is, how it works and so forth, but in the interest of conciseness, I give the low-attention-span version instead.
DNSSEC is a Public Key Infrastructure (PKI) for DNS zone data. When a zone administrator digitally signs all of the different types of Resource Records (RRs) in a given zone, and publishes those signatures and the zone's signing key's public certificate, it then becomes possible for any recursing nameserver that makes queries against that zone to validate those signatures and, therefore, to have cryptographic proof that the answer to a given DNS query hasn't been forged or tampered with.
This probably doesn't sound simple to begin with, but in practice, it's much more complicated even than that. This is because DNS is both hierarchical and distributed, with a “root” zone at the top and individual hostnames and other Resource Records at the bottom. In between are layers of zones and subzones.
Consider the top-level domain (TLD) .us as an example. It consists of more than 50 subzones, each representing a different state or protectorate in the United States of America—for example, mn.us for Minnesota, wi.us for Wisconsin and so forth. Within each “state” subzone there can be hundreds of sub-subzones representing cities, counties, state or municipal government agencies and so forth. lib.mn.us, for example, is used by public libraries in the state of Minnesota, and stpaul.lib.mn.us is used by the Saint Paul Public Library system.
Suppose I'm the DNS administrator for mycowtown.lib.mn.us, and I sign all of the records in that zone and publish the corresponding RRSIG, DNSKEY and other related records. How praiseworthy of me!
However, if someone tries to resolve names in my domain, for example, interwebs.mycowtown.lib.mn.us, they'll speak to as many as four other nameservers before they make it all the way down the hierarchy to my beautiful, signed zone—that is, the respective authoritative nameservers for “.” (the root zone), .us, .mn.us and .lib.mn.us. What's to stop someone from tampering with the answer to one of those prior, recursive queries? (Who's authoritative for .us? Who's authoritative for .mn.us? Who's authoritative for .lib.mn.us?)
Obviously, there has to be a “chain of validation” all the way from the zone I really want to validate (mycowtown.lib.mn.us), all the way up to the root domain. As it happens, “.” root is signed, and I'll show you how to download and verify the initial root key shortly. So are .us and .mn.us. However, .lib.mn.us isn't yet signed (at the time of this writing). Does that mean it's pointless to sign zones below that?
Not at all. The Internet Software Consortium, creators and maintainers of BIND, maintain a DNS Look-aside Validation (DLV) database of keys for zones having precisely this sort of gap in their chains of validation. If I sign mycowtown.lib.mn.us and register my key-signing key with dlv.isc.org, resolving nameservers that are configured to use DLV still will be able to construct a complete enough chain of validation by seeing that isc.org vouches for the validity of my key-signing key, which actually is used to sign the keys (zone-signing keys) with which I actually sign zone data.
In recent versions of BIND, DNS Look-aside Validation is not only enabled by default, but preconfigured as well.
There's just one more DNSSEC mechanism I should describe before diving in to nameserver configuration, and that's automated key management. I alluded to there being two kinds of DNS keys: key-signing keys (KSKs) and zone-signing keys (ZSKs). Both types of keys must be regenerated periodically—every few months in the case of ZSKs, with which you actually sign zone data (although in my opinion, the need to do so does not speak well of how securely PKI is implemented in DNSSEC). Naturally, every time you change a KSK or ZSK, you must re-sign your entire zone.
Saying that this makes zone-signature management a bit of a headache is a gross understatement; however, there are various ways to automate this process. Luckily, right now you and I are concerned only with validating keys, not maintaining them, and recent versions of BIND 9 have a mechanism for automatically checking and updating a caching nameserver's cache of DNS keys.
This is the managed-keys{} statement in named.conf, which can be used in lieu of the static trusted-keys{} definition. When you set up BIND with the root zone's signing key, you'll do so using a managed-keys{} statement that specifies an “initial” key that is itself not a KSK or ZSK, but is used in a transparent, cryptographic transaction in which your nameserver queries a root zone authority for a copy of its current public ZSK and caches the answer it receives.
But I'm getting a little ahead of myself. Let's set up a caching nameserver and then enable DNSSEC validation on it.
If you already have a caching-only nameserver (or a general-purpose nameserver that also caches), and you need to know only how to set up DNSSEC validation, you can skip ahead to the Setting Up DNSSEC Validation section below. In the interest of completeness, however, and for the purpose of pointing out a few default settings it's good to change, here's a quick procedure on setting one up.
First, install whatever (sub-)version of BIND9 your distribution supports. At the time of this writing, Ubuntu 10.10 includes BIND version 9.7.1; to install it, use this command:
sudo apt-get install bind9 bind9utils
The bind9 package provides BIND 9.7.1 itself in the form of the dæmon named, plus its configuration files, man pages and libraries. The bind9utils package provides handy commands, such as rndc and named-checkconf, and the DNSSEC commands dnssec-keygen and dnssec-signzone. Those last two are used only for creating and maintaining actual DNSSEC zone keys and signatures, respectively. You won't actually need those if all you're doing with DNSSEC is validating signatures from other zones.
On Debian and Ubuntu systems, the bind9 package places its configuration files in /etc/bind. The files we're concerned with here are /etc/bind/named.conf, /etc/bind/named.conf.options and /etc/bind/bind.keys.
Actually, of those three files, we'll edit only one, named.conf.options. I mention the other two in order to point out that named.conf uses “include” statements to pull content from /etc/bind/named.conf.options, /etc/bind/named.conf.local (which contains your local zone files) and /etc/bind/named.conf.default-zones (which contains default zone information for local loopback interfaces).
At this point, I have good news for you: your Debian or Ubuntu system's named.conf.options file is, technically, already set up to run named as a caching-only nameserver. The bad news is, it needs to be tightened up a bit before you can consider it to be a secure caching nameserver.
Listing 1 shows the default Debian/Ubuntu named.conf.options file (with comment lines omitted).
Let's discuss why Listing 2 is better. First, I've defined an Access Control List (ACL) that specifies two IP networks in “CIDR notation”. Technically, this is not an option, but it needs to be loaded before any option statements, so it needs to go either here or in named.conf prior to this line:
include "/etc/bind/named.conf.options";
In and of itself, this acl doesn't do anything. But once it's defined, I can create an “allow-query” option that refers to it, and as you can see in Listing 2, that's exactly what I've done. Obviously, in adapting this file for your own use, you should replace the list in my acl statement (“192.168.100.0/24; 10.10.0.0/16;”) with a list of your organization's local IP subnets.
The other security tweak I've made is to change the value for the “listen-on-v6” option from “any” to “none”. Because none of my local subnets use IPv6, there's no reason to listen on any local IPv6 interfaces. Technically, this shouldn't matter if I don't even have any IPv6 interface attached to my server and if I've set an acl and specified it in an allow-query statement. So, maybe I'm just being paranoid by turning off IPv6 altogether here, but turning off unused features is nearly always a good thing to do.
Once you've edited and saved your /etc/bind/named.conf.options file, you can check your work by running the named-checkconf command with no arguments, like so:
bash-$ sudo named-checkconf
Assuming that doesn't return any configuration errors (I have a tendency to misplace or omit semicolons, myself), you then can make your running named process reload its configuration and zone files using the rndc command, like this:
bash-$ sudo rndc reload
Now, you can test your server by logging on to some other host on your network and running a dig query or two against it. For example, if my caching nameserver's IP address is 192.168.100.253, I can have it look up DNS information for www.linuxjournal.com like so:
mick@someotherhost:/home/mick$ dig @192.168.100.253 ↪www.linuxjournal.com
You can, of course, simply configure your client system to use your caching nameserver as its default nameserver, in which case you can omit the @192.168.100.253 in the above command. But, you probably don't want to do that until you're sure it works.
If it doesn't work, make sure your client system's IP address falls into one of the IP networks you specified in any acls you've set in /etc/bind/named.conf.options, as I described earlier.
At this point, your caching-only nameserver is up and working properly. Now you can configure DNSSEC validation.
Back on your caching nameserver, all you need to do to add DNSSEC validation is add three lines in the options{} section of your /etc/bind/named.conf.options file, plus a new managed-keys{} section, as shown in Listing 3.
The first two new lines in Listing 3, dnssec-enable yes; and dnssec-validation yes;, enable DNSSEC on your caching nameserver. This is actually a redundant setting, because “yes” is the default value for both these settings in BIND versions 9.5 and later, but it doesn't hurt to specify them.
The third new line, dnssec-lookaside auto;, tells BIND/named to use DLV automatically any time it can't validate a complete chain of trust from a given Resource Record all the way from root (.) downward. See the DNSSEC Overview section earlier in this article if you've forgotten how DLV works.
As I mentioned in that section, recent versions of BIND are preconfigured to find isc.org's DLV repositories. All you have to do to take advantage of this is set “dnssec-lookaside” to “auto”, and BIND will do the rest. As more and more TLDs are signed, this feature will become less important.
And, that brings me to the last new element in the named.conf.options file: the managed-keys{} section. This specifies a key for the DNS “root” domain, which is the top of any chain of DNSSEC trust.
You don't necessarily need to specify any keys “lower” in the DNS hierarchy than root; if you start out knowing the root key, you can trust signed replies from root nameservers. That trust flows downward to signed data from TLDs (.gov, .us, .net and so on) and so forth. “Gaps” in the downward chain of validation hopefully will be handled by DLV.
For heaven's sake, do not simply copy Listing 3's key entry for “.” verbatim! Tony Finch has written a quick-and-easy procedure on checking and validating the (initial) root certificate (see Resources). Summarized, this procedure consists of the following steps.
1) Use the following dig command to obtain the current root certificate and save it to the file root-dnskey:
bash-$ dig +multi +noall +answer DNSKEY . >root-dnskey
2) Create a hash of this certificate and save it to the file root-ds with this command:
bash-$ $ dnssec-dsfromkey -f root-dnskey . >root-ds
3) Pull the official root certificate's hash from https://data.iana.org/root-anchors/root-anchors.xml, and compare it to the root-ds file you just created. For extra paranoia, you can use PGP to check the signature of root-anchors.xml (see Tony Finch's article).
4) If the hashes match, copy the key (the long one, number 257) from root-dnskey into your managed-keys statement, as shown in Listing 3. The first line of this block (after the managed-keys { line) should be the same as in Listing 3.
As with your previous changes, after you save named.conf.options, you should check it with named-checkconf, and then load it with rndc reload.
Finally, to test DNSSEC validation, test some known-signed record, such as www.isc.org, using dig. Be sure to use the +dnssec flag, like this:
mick@someotherhost:/home/mick$ dig @192.168.100.253 ↪www.isc.org +dnssec
If everything is working, dig's output should indicate that the “ad” (authenticated data) flag is set. Listing 4 shows the first part of what a successful reply to our example dig command would look like. Note the line that begins ;; flags: qr rd ra ad;.
And with that, your nameserver is successfully validating signed zone data! For now, I wish you thanks and goodbye. As I seem to do every couple years, I'm going to take a hiatus for a few months. I do plan on resuming the Paranoid Penguin after that, however, refreshed and renewed for your reading pleasure.
Until then, take care of yourself and especially your Linux systems!