a local, augmented root-zone with DNSSEC
Sunday Dec 22, 2013
Why a local root zone
Sometimes I get this question in my DNSSEC training classes: "now DNSSEC seems to be a good technology, but the root-zone is controlled by the US government. Because of that, can we trust DNSSEC?".
My answer is not to mix technology (DNSSEC) with implementation (the DNS system of the Internet).
Both are separate. While I can understand that some people do not trust the organisations in control of the Internet DNS root-zone, I see no flaw in DNSSEC (at this moment).
One way to solve the trust issue with the Internet root-zone is to host your own root-zone for the Internet. Then you are in full control of that zone. Have that zone in your own network, or on your Laptop computer und use it for the starting.point of all DNSSEC validation (the trust anchor) for DNS.
Besides the trust question, there might be another reason to operate a local root-zone: some organisations have created an internal, private top-level domain (TLD)1. A local, dnssec-signed root-zone enables the operator to remove or add any delegations, while still being able to validate all DNSSEC signed data in the Internet, as well as data that is stored in their own private DNS namespace.
The following tutorial explains the steps required to generate a local augmented and DNSSEC signed root-zone. The tutorial requires some understanding of DNS concepts and basic knowledge on DNSSEC. A good starting point to learn about DNSSEC (besides a training) is the book DNSSEC mastery by Michael W. Lucas.
All the tools used are part of the BIND DNS Server for ISC (Internet Systems Consortium). This tutorial has been tested using BIND 9.9.4-P1. Older or newer versions of BIND might require a different setup.
The setup
We need one or more authoritative servers to host the augmented root-zone. For production deployments in an internal network, at least two authoritative servers are required. For a local root-zone on a mobile device (Laptop etc.), one single authoritative server might be good enough.
We also need at least one caching DNS server (smart resolver). This cannot be the same DNS server instance as the authoritative server, however it is possible to run both (caching and authoritative server) on the same machine, but have them listen to different IP addresses. In this tutorial, I will use two separate machines:
- authoritative server: a.myroot-server.loc:192.0.2.53
- caching server: cache.loc:192.0.2.153
On both servers, the BIND configuration file will be in
/etc/named.conf
and the BIND "data" directory will be /var/named
.
The operating-system used is Debian 7.x.
Authoritative Server (DNS root-server)
Root-Zone
The starting point is the official root-zone. The "F" root-server
allows zone transfer of the full root-zone (alternatively, the
root-zone can be downloaded by ftp from ftp.internic.net
).
shell> mkdir -p /var/named/root shell> cd /var/named/root shell> dig @f.root-servers.net . axfr +onesoa | grep -v DNSKEY > root.zone shell> named-checkzone . root.zone
The grep command will remove the public DNSSEC keys (the DNSKEY records) from the zone, as we will use our own DNSSEC keys for signing the root zone.
The command named-checkzone
will test if the zone is completely
transferred and will load. Expect to see a couple of "glue record"
warnings from the tool, we can ignore them.
Next, we create a new Zone-Signing-Key (ZSK) and a Key-Signing-Key (KSK) for the root-zone:
shell> dnssec-keygen -K /var/named/keys/ -a RSASHA256 -b 2048 -n ZONE . shell> dnssec-keygen -K /var/named/keys/ -a RSASHA256 -b 4096 -f KSK -n ZONE .
Now we copy the new DNSKEY records into the root-zone file2:
shell> cat ../keys/K.+008+*.key >> root.zone
The official Internet root-zone authoritative servers have names in
the root-servers.net
domain. As we do not have the private key for
that name, and we cannot enter a DS record (delegation signer) for our
internal root-server into the .net
zone, we need to change the NS
records and the SOA record. In my example, the hostname of the local
root-server is in the myroot-servers.loc
domain. That name is stored
inside the loc.
TLD, which we will also host on our authoritative
server.
With an text editor, we change the SOA record and the NS record(s) to point to our own authoritative servers. We also need to add proper glue records for every hostname used in the NS records:
. 86400 IN SOA a.myroot-servers.loc. hostmaster.loc. 2013122200 1800 900 604800 86400 . 518400 IN NS a.myroot-servers.loc. loc. 86400 IN NS a.myroot-servers.loc. a.myroot-servers.loc. 86400 IN A 192.0.2.53
Make sure that all other NS records for the root zone "." have been removed.
Now we can sign our zone:
shell> dnssec-signzone -o . -t -R -S -K /var/named/keys/ root.zone Fetching ZSK 15795/RSASHA256 from key repository. Fetching KSK 50434/RSASHA256 from key repository. Verifying the zone using the following algorithms: RSASHA256. Zone fully signed: Algorithm: RSASHA256: KSKs: 1 active, 0 stand-by, 0 revoked ZSKs: 1 active, 0 stand-by, 0 revoked root.zone.signed Signatures generated: 528 Signatures retained: 0 Signatures dropped: 3 Signatures successfully verified: 0 Signatures unsuccessfully verified: 0 Signing time in seconds: 85.686 Signatures per second: 6.161 Runtime in seconds: 89.025
The -R
switch removed all signatures created by the DNSSEC keys of
the real root zone. The -t
switch prints out some benchmark
information. Your signing process is probably faster, as I tested
this on a Rasberry Pi.
the augmented ".loc" TLD-Zone
Below is the content of the zone-file for the .loc
TLD zone. It
contains the same NS records as we have seen in the root-zone for the
delegation of the .loc
zone:
$TTL 86400 loc. 86400 IN SOA a.myroot-servers.loc. ( hostmaster 1 1d 2h 41d 1h ) loc. 86400 IN NS a.myroot-servers.loc. a.myroot-servers.loc. 86400 IN A 192.0.2.53
The .loc
TLD should also be DNSSEC secured, so we create a set of keys
for this zone as well:
shell> dnssec-keygen -K /var/named/keys/ -a rsasha256 -b 2048 \ -n ZONE loc shell> dnssec-keygen -K /var/named/keys/ -a rsasha256 -b 4096 \ -f KSK -n ZONE loc
The BIND configuration file
This is the BIND configuration file named.conf
for the authoritative
server. It loads both the root-zone and the .loc
private TLD. Both
zones are configured as dynamic zones. After loading the zones into
the BIND DNS Server, you cannot change the zone content with an text
editor anymore. You need to use nsupdate
instead. I highly recommend
nsupdate
, as it catches a number of errors that can occur when
editing zone file manually:
options { directory "/var/named"; key-directory "keys"; dnssec-enable yes; dnssec-validation auto; dnssec-lookaside auto; recursion no; }; zone "." { type master; file "root/root.zone.signed"; update-policy local; auto-dnssec maintain; }; zone "loc" { type master; file "master/loc.zone"; update-policy local; auto-dnssec maintain; };
This is the time to check the BIND configuration and all zone-files:
shell> named-checkconf -z zone ./IN: loaded serial 2013122201 (DNSSEC signed) zone loc/IN: loaded serial 1
If named-checkconf
does not report any errors, we start our DNS
Server (from the command-line or using the start-script):
shell> named shell> tailf /var/log/syslog 22-Dec-2013 14:39:27.405 starting BIND 9.9.4-P1 -g 22-Dec-2013 14:39:27.407 built with '--libdir=/usr/local/lib' 22-Dec-2013 14:39:27.409 ---------------------------------------------------- 22-Dec-2013 14:39:27.411 BIND 9 is maintained by Internet Systems Consortium, 22-Dec-2013 14:39:27.412 Inc. (ISC), a non-profit 501(c)(3) public-benefit 22-Dec-2013 14:39:27.412 corporation. Support and training for BIND 9 are 22-Dec-2013 14:39:27.412 available at https://www.isc.org/support 22-Dec-2013 14:39:27.412 ---------------------------------------------------- 22-Dec-2013 14:39:27.414 using 1 UDP listener per interface 22-Dec-2013 14:39:27.418 using up to 4096 sockets 22-Dec-2013 14:39:27.471 loading configuration from '/etc/named.conf' [...] 22-Dec-2013 14:39:28.143 zone ./IN: loaded serial 2013122200 (DNSSEC signed) 22-Dec-2013 14:39:28.148 all zones loaded 22-Dec-2013 14:39:28.150 running 22-Dec-2013 14:39:28.152 zone ./IN: sending notifies (serial 2013122200) 22-Dec-2013 14:39:28.159 zone ./IN: reconfiguring zone keys 22-Dec-2013 14:39:28.172 zone ./IN: next key event: 22-Dec-2013 15:39:28.159
The .loc
TLD needs to be signed, we do that using rndc
:
shell> rndc sign loc
As rndc sign
does not report some types of errors, we check if the
.log
zone is now really signed:
shell> dig -t soa loc +dnssec +m @localhost ; <<>> DiG 9.9.4-P1 <<>> -t soa loc +dnssec +m @localhost ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 31525 ;; flags: qr aa rd; QUERY: 1, ANSWER: 2, AUTHORITY: 2, ADDITIONAL: 3 ;; WARNING: recursion requested but not available ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags: do; udp: 4096 ;; QUESTION SECTION: ;loc. IN SOA ;; ANSWER SECTION: loc. 86400 IN SOA a.myroot-servers.loc. hostmaster.loc. ( 3 ; serial 86400 ; refresh (1 day) 7200 ; retry (2 hours) 3542400 ; expire (5 weeks 6 days) 3600 ; minimum (1 hour) ) loc. 86400 IN RRSIG SOA 8 1 86400 ( 20140121142448 20131222132448 38865 loc. cDBUly3QhZ0dm6HJboA/UQPYkMLFK3pWGOt8x98pzY+W oD3cBult7trdxAqNgMuyl5nTZbciEU0o0HNKZlr9cJ75 sxqY2PbKjPyZ63t9837LRIoWQtce62M+uP9KLnbaBXrw x9eeyUofy8hNFRstGwoTuDqT5s0GgcUBGjpOWS3vyQB2 zAj1cNvy8Mr9sTdxw84VdM7np30dn6z/9IrGurVD3Etz YXo94QgZrNMExqV2u9vYE+tJWldeo+swbwctWH6f/oqC jvOArSiR378h0XiHq11IzM4cjx0IwW0rsVYmD+dhwsEm svLsY66mZRKqyskaXXbbIOUoXWrydwiv+Q== ) ;; AUTHORITY SECTION: [...]
Yes, we see a RRSIG signature for the SOA record, so the signing process was successful.
To create a full DNSSEC chain-of-trust from the .loc
TLD to the
local root-zone, we need to add the DS record (delegation signer) into
out private root-zone. We create the DS-Record from the KSK of .loc
…
shell> dnssec-dsfromkey /var/named/keys/Kloc.+008+38611.key loc. IN DS 38611 8 1 FBBBF41938C6CA0E286675F9BBD38C7719217B89 loc. IN DS 38611 8 2 52F2B009BCD019B9AE2470EAE321DAE74AE77B58F094778BCCD9A53AF38A30F8
… and use nsupdate
to add the DS-Records to the root-zone:
# nsupdate -l > ttl 86400 > add loc. IN DS 38611 8 1 FBBBF41938C6CA0E286675F9BBD38C7719217B89 > add loc. IN DS 38611 8 2 52F2B009BCD019B9AE2470EAE321DAE74AE77B58F094778BCCD9A53AF38A30F8 > send > quit
Our authoritative server is now ready. Next is the caching server(s).
the Caching Server configuration
We start with a basic BIND named.conf
configuration file for a
caching, validating DNS Server:
acl myclients { 192.0.2.0/24; localhost; }; options { directory "/var/named"; allow-recursion { myclients; }; dnssec-enable yes; dnssec-validation yes; dnssec-lookaside auto; }; logging { channel syslog { syslog daemon; severity info; }; channel security { file "security.log" versions 10 size 50M; print-time yes; }; channel query_log { file "query.log" versions 10 size 50M; severity debug; print-time yes; }; category general { syslog; }; category security { security; }; category queries { query_log; }; category dnssec { security; }; category default { syslog; }; category resolver { syslog; }; category client { syslog; }; category query-errors { query_log; }; category edns-disabled { syslog; }; }; managed-keys { "." initial-key 257 3 8 "AwEAAcFN/moqnq1SxdGnZW9JigGYgmFx5WN68RKJ90Je 61LJVXi8pKFRz+rajcAu7g7hb0o56RGShWkIWJAosOGr O4onzJ5t+h+rwRNe7EX++KI9XJobzHp+LQiOi/eo2cze 91oik9+9Tr+NzyJsssOEq9X/mm9hP44a8YgqSzR+rwCb 8jeB9WPhQk25Sp7qN3o8WLCfDxFy6ioOFa7MFirUa1dK 30B92X10JN0KA3d0UmOJ3GU50T0WSbWq15k28qX0et/M W3Wl3T5CclG0goM19ET0iQPJh4mN3Gdw9bHqDyiPsqeP MRJNzjb+EgK+MOp3eAqbQ1hlnr/ruwOMKz6oEIYviec8 QWvJShmeY4+rRCL5lzZuQ7AYOQq8QO4jUnqTe2/t/Oqy o6yqOtiwsLjmgnsK11qZojJ9RSzjb1r5D5Icl5UHGNxG O6/CQYJMHFsyksYzc1Brtg1PdyHc1zy/GFNI6QEQzlhQ H1nTa+F2MnoZp3k1Z6PTh+GU1796jNtUhMgy4pN7dOqC 35hP3GzaP3/XkXtzWbpZRgcXNahbiKso8eqt3r45MCsK AsW3r3hg+CzujTOjVBveKhGfb3nQvx702IdW6Jy62HWX TvipTuG1Kqw+zWHofR2P0ugAszYQBL7G3Zo45VtEScwP XaWavSbGNnHJH5OQQT6HnDvF8hZP"; }; zone "." { type hint; file "root.hint"; };
The managed-key
is the DNSKEY record of our private root-zone. We
get that record with the query:
shell> dig @192.0.2.53 . DNSKEY | grep 257
This will request the DNSKEY record set from the private root-server,
the grep
will filter out the key-signing-key (KSK) with the flag
field of "257"3.
The file root.hint
will contain the root-hints (NS records and
A/AAAA records of our authoritative DNS servers for the private
root-zone):
shell> dig @192.0.2.53 ns . ; <<>> DiG 9.9.4-P1 <<>> @192.0.2.53 ns . ; (1 server found) ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 3958 ;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 2 ;; WARNING: recursion requested but not available ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 4096 ;; QUESTION SECTION: ;. IN NS ;; ANSWER SECTION: . 518400 IN NS a.myroot-servers.loc. ;; ADDITIONAL SECTION: a.myroot-servers.loc. 86400 IN A 192.0.2.53 ;; Query time: 15 msec ;; SERVER: 192.0.2.53#53(192.0.2.53) ;; WHEN: Sun Dec 22 15:06:50 CET 2013 ;; MSG SIZE rcvd: 77
This data can be redirected into the file:
shell> dig @192.0.2.53 ns . > root.hint
Next, we check that the BIND configuration is error free …
shell> named-checkconf -z
and if no error is shown, we start the BIND DNS Server:
shell> named shell> tail /var/log/syslog Dec 22 18:19:13 raspidev named[2384]: automatic empty zone: 9.E.F.IP6.ARPA Dec 22 18:19:13 raspidev named[2384]: automatic empty zone: A.E.F.IP6.ARPA Dec 22 18:19:13 raspidev named[2384]: automatic empty zone: B.E.F.IP6.ARPA Dec 22 18:19:13 raspidev named[2384]: automatic empty zone: 8.B.D.0.1.0.0.2.IP6.ARPA Dec 22 18:19:13 raspidev named[2384]: command channel listening on 127.0.0.1#953 Dec 22 18:19:13 raspidev named[2384]: command channel listening on ::1#953 Dec 22 18:19:13 raspidev named[2384]: managed-keys-zone: loaded serial 9 Dec 22 18:19:13 raspidev named[2384]: all zones loaded Dec 22 18:19:13 raspidev named[2384]: running
Now, on the caching server, we should be able to validate out own root zone (watch for the AD-Flag):
; <<>> DiG 9.9.4-P1 <<>> @localhost soa . +adflag +m ; (2 servers found) ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 13316 ;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 4096 ;; QUESTION SECTION: ;. IN SOA ;; ANSWER SECTION: . 86327 IN SOA a.myroot-servers.loc. nstld.verisign-grs.com. ( 2013122200 ; serial 1800 ; refresh (30 minutes) 900 ; retry (15 minutes) 604800 ; expire (1 week) 86400 ; minimum (1 day) ;; AUTHORITY SECTION: . 518274 IN NS a.myroot-servers.loc. ;; Query time: 3 msec ;; SERVER: 127.0.0.1#53(127.0.0.1) ;; WHEN: Sun Dec 22 15:13:02 CET 2013 ;; MSG SIZE rcvd: 118
And we should also be able to validate other data out in the Internet,
like the domain name for this blog (dnsworkshop.de
or
dnsworkshop.org
):
; <<>> DiG 9.9.4-P1 <<>> dnsworkshop.de @localhost ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 60851 ;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 2, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 4096 ;; QUESTION SECTION: ;dnsworkshop.de. IN A ;; ANSWER SECTION: dnsworkshop.de. 7200 IN A 91.190.147.212 ;; AUTHORITY SECTION: dnsworkshop.de. 7200 IN NS ns2.myinfrastructure.org. dnsworkshop.de. 7200 IN NS ns1.myinfrastructure.org. ;; Query time: 1230 msec ;; SERVER: 127.0.0.1#53(127.0.0.1) ;; WHEN: Sun Dec 22 15:14:16 CET 2013 ;; MSG SIZE rcvd: 115
And we can also validate our augmented .loc
TLD that does not exist
in the public Internet:
; <<>> DiG 9.9.4-P1 <<>> @localhost a.myroot-servers.loc +adflag ; (2 servers found) ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 15859 ;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 4096 ;; QUESTION SECTION: ;a.myroot-servers.loc. IN A ;; ANSWER SECTION: a.myroot-servers.loc. 86400 IN A 192.0.2.53 ;; AUTHORITY SECTION: loc. 86399 IN NS a.myroot-servers.loc. ;; Query time: 94 msec ;; SERVER: 127.0.0.1#53(127.0.0.1) ;; WHEN: Sun Dec 22 18:22:33 CET 2013 ;; MSG SIZE rcvd: 79
Voila, a DNSSEC signed local root-zone.
The root-zone is a busy place right now. New TLDs are added all the
time. Make sure you follow these changes. A good way to get notice of
updates in the root zone is to follow @diffroot on Twitter. Please
remember, if you followed this tutorial, you need to add the changes
using nsupdate
, do not use a text editor on the zone files!
Footnotes:
1]
I do not recomment hosting a private TLD, it is much easier and less
error-prone to run a private DNS delegation on the second or third level
of the DNS hierachy, such as private.example.com
.
2]
Be sure to use the double >>
to append to the file, a single >
will
override the zonefile with the keys. Not what we want.
3] Always double check the key data, there is a slight chance that the sequence "257" will appear in the key material of the ZSK as well!