Setting Up A Safe Nameserver On Red Hat Linux 6.x

A Twelve-Step Program

Jefferson Ogata <Jefferson.Ogata@noaa.gov>

These instructions will help you set up a chrooted nameserver running as a non-root user under Red Hat Linux 6.x. This configuration is quite safe even if a new remote vulnerability is discovered in BIND, because the named process is running in a minimal environment with no access to external programs, and without superuser privileges that could be used to escape. Successful compromise via a nameserver configured this way is extremely difficult if not altogether impossible.

See the ISC site for documentation on named's configuration file named.conf, and the special directives for your zone files, and other general information about BIND.

Preliminary Steps

Please make sure that you have a clean machine in the first place. Start from a fresh install of Red Hat done with the machine disconnected from the network. Apply all current Red Hat patches to the system before you connect it to the Internet. It is pointless to set up a secure nameserver on a system that can be compromised by other means.

Once all patches are applied, turn off all unnecessary services, and eliminate all unencrypted means of remote shell access to the machine. Disable inetd, sendmail, and all other network services. Disable the window system.

Install sshd if you need remote access to the machine.

Setting up xntpd is also a good idea. Be aware that xntpd will not start up at boot time if the machine time is an hour or more in error. You may want to read up on the setclock command for suggested remedies. An alternative to running xntpd as a daemon is to execute ntpdate periodically from a cron job.

I also recommend that you install autorpm and perl-libnet, which you can get here. You will probably want to tweak the /etc/autorpm.d/pools/redhat-updates file.

When you are done, reboot the machine. After reboot, the results of running

    netstat -an -A inet
    
should look more or less like this (TCP port 22 is sshd):

    Active Internet connections (servers and established)
    Proto Recv-Q Send-Q Local Address           Foreign Address         State
    tcp        0      0 *:22                    *:*                     LISTEN
    raw        0      0 *:1                     *:*                     7
    raw        0      0 *:6                     *:*                     7
    

If you have set up xntpd you should also see UDP port 123 active on each interface.

Now you may connect the machine to the Internet.

Setting Up The Nameserver

Please note: these instructions are for an initial setup. If you have already performed these steps and are upgrading BIND from a Red Hat RPM, please see the upgrade instructions below.

Please make sure you understand each step as we go. I will not be held responsible for errors in this document. If you don't understand a step, please don't blame me when something unexpected happens.

You should perform all the following steps as the root user. Make sure your umask is set to a suitable value, such as 022.

In all steps, we are making an effort to preserve modification times on copied files, using the cp -p command or using tar. This is important because it will help you tell which files in the chroot hierarchy need to be replaced after applying future patches to system libraries or the BIND subsystem.

  1. Stop named if it is already running. (If you followed the preliminary steps listed above, it isn't.)
      /etc/rc.d/init.d/named stop
      
  2. Make sure there is a named user on your system. If there is not, create one with suitable non-zero uid and gid, an invalid shell, and an unusable password.
      useradd -u 530 -d /var/named -s /dev/null -c 'Named User' -p '*' -M named
      
  3. Set up a chroot hierarchy for named. When we are done, named will run with /var/roots/named as its root directory. This will make all files outside of /var/roots/named inaccessible to the named process. If compromised, named will be extremely limited in what actions it can perform on an attacker's behalf.
      mkdir /var/roots
      mkdir /var/roots/named
      mkdir /var/roots/named/{dev,etc,lib,usr,var}
      mkdir /var/roots/named/usr/sbin
      mkdir /var/roots/named/var/run
      
  4. The following steps are necessary if you are acting as a slave server. If you are acting only as a master, you may skip this section. On the other hand, there's no harm in performing these steps, and you will then be able to act as a slave later on without further modifications.

    1. Copy the named-xfer program into the hierarchy. named invokes this program to transfer slave zones from other nameservers.
        cp -p /usr/sbin/named-xfer /var/roots/named/usr/sbin/named-xfer
        
    2. Copy necessary libraries for named-xfer. We use cp's -d flag to preserve the fact that some of these objects are symbolic links.
        cp -pd /lib/ld-2.1.3.so /lib/ld-linux.so.2 /lib/libc-2.1.3.so /lib/libc.so.6 /var/roots/named/lib/
        

      You may need to adjust the above command for the versions of libc and ld that are current for Red Hat Linux. The ldd command is useful for determining what versions of which libraries are used to link and run a given binary.

    3. Make a directory for slave zone files -- it must belong to the named user so that the named and named-xfer processes can write to it.
        mkdir /var/roots/named/var/named
        mkdir /var/roots/named/var/named/s
        chown named.named /var/roots/named/var/named/s
        
  5. Copy /etc/localtime. Log messages will appear in UTC without it.
      cp -p /etc/localtime /var/roots/named/etc/localtime
      
  6. Copy configuration and database files. Important: do not make soft links to the chrooted configuration file and zone file directory. linuxconf likes to "clean up" your zone files and it makes a royal mess out of them, actually making incorrect modifications.
      cd /
      tar cf - var/named etc/named.conf | (cd /var/roots/named ; tar xBpf -)
      

    You might want to drop a README in /var/named and some comments in /etc/named.conf pointing your colleagues to the appropriate locations under /var/roots/named.

  7. Now we have to fix up named and ndc in /usr/sbin. We will replace the original versions with shell scripts to set a few extra options during invocation. ndc, by the way, is the name daemon control program, which lets you do a lot of things to the running nameserver, such as reloading a specific zone, checking status, restarting the daemon, etc.

    1. Move original binaries to a new spot.
        mkdir /usr/sbin/bind-orig
        mv /usr/sbin/named /usr/sbin/ndc /usr/sbin/bind-orig/
        
    2. Create a stub for ndc so it can find the control channel in the chroot hierarchy. Normally, ndc would look in /var/run/ndc, but this path will be inaccessible to named when it creates its control channel. Instead, the control channel will end up in /var/roots/named/var/run/ndc.
        echo '#!/bin/sh' > /usr/sbin/ndc
        echo >> /usr/sbin/ndc
        echo 'exec /usr/sbin/bind-orig/ndc -c /var/roots/named/var/run/ndc "$@"' >> /usr/sbin/ndc
        echo >> /usr/sbin/ndc
        chmod +x /usr/sbin/ndc
        
    3. Create a stub for named that will chroot to the new hierarchy and run as the named user.
        echo '#!/bin/sh' > /usr/sbin/named
        echo >> /usr/sbin/named
        echo 'exec /usr/sbin/bind-orig/named -t /var/roots/named -u named "$@" </dev/null >/dev/null 2>&1' >> /usr/sbin/named
        echo >> /usr/sbin/named
        chmod +x /usr/sbin/named
        
  8. Set up logging: named wants to log to /dev/log, but there is no /dev/log in the chroot hierarchy. We address this problem by telling syslogd to bind to an additional socket in an appropriate place in the chroot hierarchy, using the -a flag. This requires tweaking the syslogd startup script.

    1. First backup the old script:
        cp -p /etc/rc.d/init.d/syslog /etc/rc.d/init.d/syslog.orig
        
    2. To be consistent with the rest of Red Hat's configuration structure, create /etc/sysconfig/syslogd with the following:
        cat > /etc/sysconfig/syslogd <<EOT
        SYSLOGD_OPTS="-m 0 -a /var/roots/named/dev/log"
        EOT
        
    3. And make the following changes to /etc/rc.d/init.d/syslog:

      1. After:
          # Source function library.
          . /etc/rc.d/init.d/functions
          
        add the following:
          # Source local customizations.
          . /etc/sysconfig/syslogd
          
      2. Change the line:
          	daemon syslogd -m 0
          
        to:
          	daemon syslogd $SYSLOGD_OPTS
          
    4. Restart syslogd. After restart you should find a UNIX-domain socket at /var/roots/named/dev/log.
        /etc/rc.d/init.d/syslog restart
        ls -l /var/roots/named/dev/log
        
  9. Customize /var/roots/named/etc/named.conf now. Any place you see an IP address below, make a suitable change. Good things to have in your named.conf:
      options {
      
      	directory "/var/named";
      
      	// Enumerate hosts to whom we allow zone transfers.
      	allow-transfer {
      		192.168.0.1;	// Add all slave server addresses here.
      		192.168.0.2;
      	};
      
      	// Make up something here. It is preferable not to reveal the
      	// version of BIND you are using.
      	version "over 8 billion served";
      
      	// You don't need to write a pid file. ndc talks to the UNIX
      	// socket. This will cause an error message at startup, but that
      	// is okay; it is harmless.
      	pid-file "/no/such/path/named.pid";
      
      	// Set the source address you will use for outgoing queries.
      	// If you want to specify the IP address as well, replace the
      	// asterisk in the third field.
      	query-source address * port 53;
      
      	// Set the source address we use for outgoing zone transfer
      	// requests. If you are acting as a slave and have multiple
      	// network interfaces, it is important to do this so that the
      	// master servers can be configured to allow transfers from a
      	// consistent address. If you don't do this, you may end up
      	// expiring a valid zone.
      	transfer-source 192.168.0.53;
      };
      
      // You probably don't need to log statistics. They make a lot of noise.
      logging {
      	category statistics {
      		null;
      	};
      };
      
  10. You can now start named.
      /etc/rc.d/init.d/named start
      
    You should see messages similar to the following appear in /var/log/messages:
      named[999]: starting.  named 8.2.2-P7 Thu Nov 11 00:04:50 EST 1999 root@porky.devel.redhat.com:/usr/src/bs/BUILD/bind-8.2.2_P7/src/bin/named
      named[999]: hint zone "" (IN) loaded (serial 0)
      named[999]: master zone "localhost" (IN) loaded (serial 1)
      named[999]: master zone "foo.com" (IN) loaded (serial 2000010101)
      named[999]: master zone "0.0.127.in-addr.arpa" (IN) loaded (serial 1)
      named[999]: master zone "0.168.192.in-addr.arpa" (IN) loaded (serial 2000010101)
      named[999]: couldn't create pid file '/no/such/path/named.pid'
      named[999]: listening on [127.0.0.1].53 (lo)
      named[999]: listening on [192.168.0.53].53 (eth0)
      named[999]: Forwarding source address is [0.0.0.0].53
      named[999]: couldn't create pid file '/no/such/path/named.pid'
      named[999]: chrooted to /var/roots/named
      named[999]: group = 530
      named[999]: user = named
      named[999]: Ready to answer queries.
      

    You should also be able to retrieve your dummy version string with the following command:

      dig @localhost version.bind txt chaos
      
  11. Make sure named is enabled in the configuration for runlevels 2 through 5:
      chkconfig --level 2345 named on
      

And you're done. You might want to reboot at this point to make sure everything starts up okay.

Upgrading The Nameserver

Periodically Red Hat issues an RPM containing a new version of BIND. This RPM may replace the named, ndc, named-xfer, and various other binaries, as well as various library files. Since some of these files have been copied to the chroot hierarchy, while certain other files in /usr/sbin have been replaced with stub scripts, you must perform additional steps to make everything work properly after a BIND upgrade. This is also the case if you upgrade the C library (glibc package) or the syslog daemon (sysklogd package).

  1. Stop named.
      /etc/rc.d/init.d/named stop
      
  2. Install the new RPMs using the "freshen" option (-F):
      rpm -Fvh bind*.rpm
      
  3. If you set up your nameserver originally as a slave server, make sure your named-xfer binary and any required libraries in the chroot hierarchy are up to date. If your nameserver does not already have a copy of named-xfer in the chroot hierarchy, skip this step.

    1. Re-copy the named-xfer program into the hierarchy.
        cp -p /usr/sbin/named-xfer /var/roots/named/usr/sbin/named-xfer
        
    2. Re-copy necessary libraries for named-xfer.
        cp -pdf /lib/ld-2.1.3.so /lib/ld-linux.so.2 /lib/libc-2.1.3.so /lib/libc.so.6 /var/roots/named/lib/
        

      As in the initial setup, you may need to adjust the above command for the versions of libc and ld that are current for Red Hat Linux.

  4. Now we have to make sure our modified versions of named and ndc in /usr/sbin are correct. We had replaced the original versions with shell scripts as part of the initial setup. The new RPM may have overwritten one or both of our scripts with new binaries. We'll start by examining ndc.
      file /usr/sbin/ndc
      

    If file describes ndc as Bourne shell script text, then the shell script is intact, and you should skip the remainder of this step. If, however, ndc is a dynamically linked executable, then the RPM has replaced our shell script with a new binary, and we need to perform the following steps:

    1. Move the new ndc binary to our bind-orig directory. In the process, we will overwrite the old version of ndc which we had moved there during the initial setup.
        mv /usr/sbin/ndc /usr/sbin/bind-orig/ndc
        
    2. Re-create the stub for ndc so it can find the control channel in the chroot hierarchy.
        echo '#!/bin/sh' > /usr/sbin/ndc
        echo >> /usr/sbin/ndc
        echo 'exec /usr/sbin/bind-orig/ndc -c /var/roots/named/var/run/ndc "$@"' >> /usr/sbin/ndc
        echo >> /usr/sbin/ndc
        chmod +x /usr/sbin/ndc
        
  5. Examine the named program in /usr/sbin. This step is independent of the previous step. Regardless of whether we had to re-create the ndc stub, we must still examine named, as Red Hat may distribute an RPM that updates named but not ndc, or vice versa.
      file /usr/sbin/named
      

    If file describes named as Bourne shell script text, then the shell script is intact, and you should skip the remainder of this step. If, however, named is a dynamically linked executable, then the RPM has replaced our shell script with a new binary, and we need to perform the following steps:

    1. Move the new named binary to our bind-orig directory. In the process, we will overwrite the old version of named which we had moved there during the initial setup.
        mv /usr/sbin/named /usr/sbin/bind-orig/named
        
    2. Re-create the stub for named that will chroot to the chroot hierarchy and run as the named user.
        echo '#!/bin/sh' > /usr/sbin/named
        echo >> /usr/sbin/named
        echo 'exec /usr/sbin/bind-orig/named -t /var/roots/named -u named "$@" </dev/null >/dev/null 2>&1' >> /usr/sbin/named
        echo >> /usr/sbin/named
        chmod +x /usr/sbin/named
        
  6. Make sure syslogd has a logging socket in the chroot hierarchy. Updates to the sysklogd package may overwrite the init.d startup script, which we modified to append options to the syslogd command line. Check that our modifications are intact using the following command:
      grep SYSLOGD_OPTS /etc/rc.d/init.d/syslog
      

    If grep reports the line

      	daemon syslogd $SYSLOGD_OPTS
      
    then our changes are still intact, and you should skip the remainder of this step. If, however, grep reports no occurrence of SYSLOGD_OPTS, then we must redo our changes to the /etc/rc.d/init.d/syslog script as follows:

    1. First backup the old script:
        cp -p /etc/rc.d/init.d/syslog /etc/rc.d/init.d/syslog.orig
        
    2. Make the following changes to /etc/rc.d/init.d/syslog:

      1. After:
          # Source function library.
          . /etc/rc.d/init.d/functions
          
        add the following:
          # Source local customizations.
          . /etc/sysconfig/syslogd
          
      2. Change the line:
          	daemon syslogd -m 0
          
        to:
          	daemon syslogd $SYSLOGD_OPTS
          
    3. Restart syslogd. After restart you should find a UNIX-domain socket at /var/roots/named/dev/log.
        /etc/rc.d/init.d/syslog restart
        ls -l /var/roots/named/dev/log
        
  7. You can now start named.
      /etc/rc.d/init.d/named start
      
    You should see messages similar to the following appear in /var/log/messages:
      named[999]: starting.  named 8.2.2-P7 Thu Nov 11 00:04:50 EST 1999 root@porky.devel.redhat.com:/usr/src/bs/BUILD/bind-8.2.2_P7/src/bin/named
      named[999]: hint zone "" (IN) loaded (serial 0)
      named[999]: master zone "localhost" (IN) loaded (serial 1)
      named[999]: master zone "foo.com" (IN) loaded (serial 2000010101)
      named[999]: master zone "0.0.127.in-addr.arpa" (IN) loaded (serial 1)
      named[999]: master zone "0.168.192.in-addr.arpa" (IN) loaded (serial 2000010101)
      named[999]: couldn't create pid file '/no/such/path/named.pid'
      named[999]: listening on [127.0.0.1].53 (lo)
      named[999]: listening on [192.168.0.53].53 (eth0)
      named[999]: Forwarding source address is [0.0.0.0].53
      named[999]: couldn't create pid file '/no/such/path/named.pid'
      named[999]: chrooted to /var/roots/named
      named[999]: group = 530
      named[999]: user = named
      named[999]: Ready to answer queries.
      
And you are finished upgrading the nameserver. At this point you should enjoy a cold, refreshing beverage of your choice.