Hack #59. Set Up a Minimal and Secure DNS Server

Use Djbdns, a more secure alternative to BIND, to serve your DNS records.

For many years BIND has been the workhorse of the Internet, serving up DNS records. But with that history comes a long track record of security vulnerabilities. While the rate at which vulnerabilities are being discovered has decreased over the years (it’s gotten “more secure”), you might want to err on the side of caution and use a different software package that doesn’t have such a colorful history. One such package is Djbdns (http://cr.yp.to/djbdns.html), written by Daniel J. Bernstein, for which no security vulnerabilities have been disclosed to date.

One of the things that makes Djbdns so secure is its architecture. BIND uses one big monolithic program to perform all DNS-related duties. On the other hand, Djbdns uses many separate specialized programs to serve authoritative zones, do recursive queries, perform zone transfers, and carry out logging, amongst other things. Each of these subprograms is smaller and easier to audit for vulnerabilities.

This hack focuses primarily on the tinydns program, which Djbdns uses to serve authoritative DNS zones.

Before you can get Djbdns up and running, you’ll first need to install daemontools (http://cr.yp.to/daemontools.html), another package used to manage server processes on Unix systems. Download the tarball from http://cr.yp.to/daemontools/daemontools-0.76.tar.gz and create a /package directory:

# mkdir /package
# chmod 1755 /package
            

Then, change into /package and unpack the tarball. You might be wondering why we’re using this /package directory. The reason is because daemontools’s installation process makes symbolic links from the binaries that were compiled during the build process to other locations within the system’s filesystems. So, you need a good permanent place to keep the source tree; any directory that satisfies that requirement will do.

Once the source code has been unpacked into /package, change the directory to admin/daemontools-0.76 and run the installation script:

# cd admin/daemontools-0.76
# package/install
            

You might encounter an error like this:

/usr/bin/ld: errno: TLS definition in /lib/libc.so.6 section .tbss mismatches non-TLS reference in envdir.o
/lib/libc.so.6: could not read symbols: Bad value
collect2: ld returned 1 exit status
make: *** [envdir] Error 1
Copying commands into ./command...
cp: cannot stat \Qcompile/svscan': No such file or directory

If you do, download the patch available at http://www.qmailrocks.org/downloads/patches/daemontools-0.76.errno.patch and apply it:

# cd /package/admin/daemontools-0.76/src
# patch < 
               
                  <path to patch file>
               
            

Now, run package/install again.

If the system uses a SysV init process (i.e., if an /etc/inittab file and an /etc/rc.d directory are used), an entry for daemontools will be added to /etc/ininttab. This will cause daemontools to automatically start now and with each system boot. Otherwise, the install process will add a line to /etc/rc.local to start it at boot time. Instead of rebooting to start daemontools, locate this line and run it manually:

# csh -cf '/command/svscanboot &'
            

Now it’s time to install Djbdns.

Once you’ve installed daemontools, download and unpack the Djbns tarball (available at http://cr.yp.to/djbdns/djbdns-1.05.tar.gz) and change to the directory that is created (e.g., djbdns-1.05). On Linux systems, run the following before starting the build:

$ echo gcc -O2 -include /usr/include/errno.h > conf-cc
            

Build Djbdns by simply running make. After compilation finishes, run make setup check to install it. You’ll need to create two user accounts under which to run tinydns and its logging process:

# adduser 
               
                  _tinydns
               
# adduser 
               
                  _dnslog
               
            

Now, set up tinydns to use these accounts and tell daemontools to start it (replace 192.168.0.40 with your system’s IP address):

# tinydns-conf 
               
                  _tinydns _dnslog
               
                /etc/tinydns 
               
                  192.168.0.40
               
# ln -s /etc/tinydns /service
            

After a few seconds, daemontools will start tinydns, and you should see it and its logger process:

# ps -aux | egrep '
               
                  _tinydns
               
               |
               
                  _dnslog
               
               '
_tinydns 49485  0.0  0.2  1328   552  p1  I    12:53AM   0:00.12 /usr/local/bin/tinydns
_dnslog  49486  0.0  0.2  1208   480  p1  I    12:53AM   0:00.07 multilog t ./main

Now it’s time to add some DNS records.

Setting up an authoritative zone with Djbdns is much less complex than doing so with BIND. In fact, it’s surprisingly easy. Simply put authoritative DNS records into a plain-text file and then compile them into a binary database format that tinydns can read.

Now, you’ll need to create some records. Some programs for adding records are included, but you’ll probably want to add NS and MX records by hand, because the included tools enforce a certain DNS- and mail-server-naming scheme that you might not want to use.

First, create NS and SOA records for the domain and an A record for your name server:

# cd /service/tinydns/root
# echo "
               
                  .example.com:192.168.0.40:ns1.example.com
               
               " > data
# echo "
               
                  .0.168.192.in-addr.arpa:192.168.0.40:ns1.example.com
               
               " >> data
            

The first character of each entry signifies the record type. A dot (.) causes tinydns to create an NS record for example.com pointing to ns1.example.com, an A record for ns1.example.com, and an SOA record for example.com. The second entry delegates the reverse zone for 192.168.0.0/24 to the name server. After adding these entries, run make to create the database that tinydns reads its data from, data.cdb. Remember to do this after every change you make to data.

Now, take a look at the records that were created:

# dig -t any 
               
                  example.com @192.168.0.40
               

; <<>> DiG 9.3.1 <<>> -t any example.com @192.168.0.40
; (1 server found)
;; global options:  printcmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 18791
;; flags: qr aa rd; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

;; QUESTION SECTION:
;example.com.                   IN      ANY

;; ANSWER SECTION:
example.com.            2560    IN      SOA     ns1.example.com. hostmaster.example.com. 1151133345 16384 2048 1048576 2560
example.com.            259200  IN      NS      ns1.example.com.

;; ADDITIONAL SECTION:
ns1.example.com.        259200  IN      A       192.168.0.40

;; Query time: 6 msec
;; SERVER: 192.168.0.40#53(192.168.0.40)
;; WHEN: Sat Jun 24 01:15:48 2006
;; MSG SIZE  rcvd: 110

Try adding an MX record. This time, use the add-mx helper program:

# ./add-mx 
               
                  example.com 192.168.0.42
               
This will create the following entry in the data file:
@example.com:192.168.0.42:a::86400

This results in an A record and an MX entry pointing to a.mx.example.com. This is an example of the helper programs enforcing their own naming scheme. Change the entry to look like the following to create an A and MX record for mail.example.com instead:

@example.com:192.168.0.42:mail.example.com:10:86400

The helper programs aren’t good for everything, but they are good for adding generic hosts. To add a host, use the appropriately named add-host program:

# ./add-host 
               
                  caligula.example.com 192.168.0.41
               
            

This creates an = entry, which causes tinydns to serve up an A record for caligula.example.com and its corresponding reverse DNS PTR record:

=caligula.example.com:192.168.0.41:86400

To add additional A (but not PTR) records for a host, use the add-alias command. Entries created with this command start with a +:

# ./add-alias 
               
                  www.example.com 192.168.0.41
               
# cat data
.example.com:192.168.0.40:ns1.example.com
.0.168.192.in-addr.arpa:192.168.0.40:ns1.example.com
@example.com:192.168.0.42:mail.example.com:10:86400
=caligula.example.com:192.168.0.41:86400
+www.example.com:192.168.0.41:86400

The types of entries discussed here should satisfy most situations. However, there are several other types, including a generic type that allows you to specify entries that generate any type of DNS record. For more information on these, consult http://cr.yp.to/djbdns/tinydns-data.html.