A compilation of documentation   { en , fr }

Configure bind9 in a chroot jail on Debian GNU/Linux

Created on:
Last edited on:
Xavier Béguin

Nowadays, security software like AppArmor, which restrict program accesses to the operating system, are by default distributed and integrated on most GNU/Linux systems.

When correctly configured, their use make the configuration of a chroot jail, as described on this page, unnecessary. This solution will however still be of interest in case you do not wish to use a software such as AppArmor.

Package installation

If you don't use BIND yet, on Debian you will simply need to install the package bind9 (which will lead to the automatic installation of required packages such as bind9utils).

apt-get install bind9

Its configuration is outside the scope of this document, but note that after installation, the package default configuration will run the named daemon as the user bind, which is a member of the group bind.

All commands given in this document are meant to be run as the system superuser (usually root). This usually means you should either prefix each command by the command sudo, or run them in a root shell created using, for example, the command sudo -i.

Setup of the chroot jail

Creation of the chroot jail directories

The choice of the directory where the chroot jail will be installed (and to which the daemon accesses will be restricted) is free, but in this document, we will choose the directory /var/lib/bind which was created to this effect by the Debian package.

We can recreate the files and directory structure required by BIND in this directory using the following commands :

mkdir -p /var/lib/bind/dev
mkdir -p /var/lib/bind/etc
mkdir -p /var/lib/bind/var
mkdir -p /var/lib/bind/var/run/named
mkdir -p /var/lib/bind/var/cache/bind

We also need to create the two character devices files required by BIND:

mknod /var/lib/bind/dev/null c 1 3
mknod /var/lib/bind/dev/random c 1 8
chmod 666 /var/lib/bind/dev/{null,random}

Copy of the configuration files

Before moving files, it can be worth backing them up somewhere. I'm used to keep these kinds of safety backups in the directory /var/local/backups that I create if it does not exist :

test -d /var/local/backups || mkdir /var/local/backups
cp -a /etc/bind /var/local/backups/etc_bind.orig

Note: if your BIND server manages dynamic DNS zones, it is advised to stop it now (using the command service bind9 stop), or you would risk losing zone updates that would occur between the copy of the files below and the restart of BIND in its new chroot jail. If the server does not manage dynamic zone, you can keep it running while setting up the chroot jail and only restart it once it is ready.

We now can copy configuration files to the chroot directory (we do a copy instead of a move to not prevent BIND from operating normally during our chroot setup; we will remove the old files at the end of the procedure):

cp -a /etc/bind /var/lib/bind/etc/

Copy of the cache

This operation is mainly useful if the server is configured as a slave for at least one DNS zone, as this will avoid it to transfer the zone(s) from the master server again:

cp -a /var/cache/bind/* /var/lib/bind/var/cache/bind/

Modification of directory owner and permissions

The named daemon needs to be able to write in the cache directory (where it writes zones for which it is a slave, in particular) and in the directory where it writes its PID file (the file named.pid):

chgrp bind /var/lib/bind/var/cache/bind
chmod g+w /var/lib/bind/var/cache/bind
chgrp bind /var/lib/bind/var/run/named
chmod g+w /var/lib/bind/var/run/named

We then further restrict the permissions of the main chroot directory, for security purpose and to follow best practices:

chgrp bind /var/lib/bind
chmod 750 /var/lib/bind

Modification of the syslog configuration

On GNU/Linux, BIND by default communicates with the syslog daemon using the Unix socket file /dev/log. As, by definition, BIND will only be able to access files within the chroot jail, we need to configure the syslog daemon so that it creates another socket file inside the jail and read from it what BIND will write. In our example, we will use the socket /var/lib/bind/dev/log.

This configuration will depend on the syslog daemon you use. Using rsyslog (used by default on Debian), we can modify the configuration of the daemon by creating a file in /etc/rsyslog.d, for example using the following command:

cat <<'_EOD_' > /etc/rsyslog.d/bind-chroot.conf
$AddUnixListenSocket /var/lib/bind/dev/log

Then we simply need to restart rsyslog:

service rsyslog restart

Note: if you use the syslog daemon sysklogd (which was in particular the default syslog daemon on Debian Etch), edit /etc/default/syslogd and modify the variable definition SYSLOGD="" into SYSLOGD="-a /var/lib/bind/dev/log", for example using the following command:

sed -i -e 's|SYSLOGD=""|SYSLOGD="-a /var/lib/bind/dev/log"|' /etc/default/syslogd
service sysklogd restart

Adaptation of the startup configuration

We then need to adapt the startup configuration for BIND so that the named daemon is run using the -t option that requests the use of a chroot jail in the specified directory.

For this, we need to edit BIND startup configuration file /etc/default/bind9, to change the variable declaration OPTIONS="-u bind" into OPTIONS="-u bind -t /var/lib/bind". This can be achieved using this command:

sed -i -e 's|OPTIONS="-u bind"|OPTIONS="-u bind -t /var/lib/bind"|' /etc/default/bind9

Caution: on Debian 8 (Jessie), a bug in the bind9 package prevents the named daemon from taking into account the options provided by the file /etc/default/bind9 if you use systemd (which is the default). To work around this problem, we need to modify the systemd configuration of BIND by editing the file /lib/systemd/system/bind9.service. Replace the line starting by ExecStart= with these two lines:

ExecStart=/usr/sbin/named -f $OPTIONS

This operation can be achieved directly using the following command, before reloading the systemd configuration:

sed -i 's|^ExecStart=.*$|EnvironmentFile=-/etc/default/bind9\nExecStart=/usr/sbin/named -f $OPTIONS|' /lib/systemd/system/bind9.service
systemctl daemon-reload

Beware that changing this file means that each update of the bind9 package will reinstall the original file, overwriting your modifications, and named would then be restarted without using the chroot jail. To ask the package not to overwrite this file, a file diversion can be put in place with this command:

dpkg-divert --divert /lib/systemd/system/bind9.service.orig --rename /lib/systemd/system/bind9.service

After the bug is corrected, the file diversion can be removed using this command:

dpkg-divert --rename --remove /lib/systemd/system/bind9.service

For more information on these last commands, see the tip « File diversions on Debian »

Restart of the BIND daemon

The configuration of the BIND chroot is now finished. You can restart the bind9 service, which will restart the named daemon using the chroot:

service bind9 restart

Directory cleanup (optional)

After having made sure that everything works correctly after the restart, we can remove the original files from /etc/bind (as BIND now uses files from the chroot jail, /var/lib/bind/etc/bind in our example) and the cache files from /var/cache/bind.

Caution: before removing, make extra sure the files from /etc/bind were all correctly copied to /var/lib/bind/etc/bind!

rm -rf /etc/bind/* /var/cache/bind/*

If we want to make the chroot jail configuration transparent for the system administrators, we can replace /etc/bind by a link to /var/lib/bind/etc/bind:

rmdir /etc/bind
ln -s /var/lib/bind/etc/bind /etc/bind


Recap of the commands

Below is the recap of all commands given and described above, that need to be run to configure the chroot jail (we will suppose here that we use the rsyslog daemon).

Be cautious, as some configuration details certainly have changed in newer version of BIND on Debian since the writing of this documentation. I recommend to only execute these commands one by one to verify and correct any potential error as you go along.

# Stopping BIND
service bind9 stop

# Creation of the chroot jail under /var/lib/bind
mkdir -p /var/lib/bind/{dev,etc,var,var/run/named,var/cache/bind}
mknod /var/lib/bind/dev/null c 1 3
mknod /var/lib/bind/dev/random c 1 8
chmod 666 /var/lib/bind/dev/{null,random}

# Backup of the BIND files (optional)
[ -d /var/local/backups ] || mkdir /var/local/backups
cp -a /etc/bind /var/local/backups/etc_bind.orig

# Copy of the files to the chroot jail
cp /etc/localtime /var/lib/bind/etc/
cp -a /etc/bind /var/lib/bind/etc/
cp -a /var/cache/bind/* /var/lib/bind/var/cache/bind/

# Modification of file permissions and owners
chgrp bind /var/lib/bind/{var/cache/bind,var/run/named}
chmod g+w /var/lib/bind/{var/cache/bind,var/run/named}
chgrp bind /var/lib/bind
chmod 750 /var/lib/bind

# Configuration of rsyslog
cat <<'_EOD_' > /etc/rsyslog.d/bind-chroot.conf
$AddUnixListenSocket /var/lib/bind/dev/log
service rsyslog restart

# Adding the option -t to named to specify the chroot
sed -i \
  -e 's|OPTIONS="-u bind"|OPTIONS="-u bind -t /var/lib/bind"|' \
service bind9 restart

# Before removing these files, make sure everything works fine
# in the chroot and/or that you backed them up somewhere!
rm -rf /etc/bind/* /var/cache/bind/*

# Optional operation to ease the access to BIND files for admins
rmdir /etc/bind
ln -s /var/lib/bind/etc/bind /etc/bind

An example of the directory structure of a pristine configuration

As a reference, below are the files and directories you would find in the chroot jail (in our example, this is the content of the directory /var/lib/bind) after applying the chroot configuration on a pristine installation of the bind9 package on Debian 8 (Jessie) (this list was produced by the command tree /var/lib/bind):

├── bind9-default.md5sum
├── dev
│   ├── log
│   ├── null
│   └── random
├── etc
│   ├── bind
│   │   ├── bind.keys
│   │   ├── db.0
│   │   ├── db.127
│   │   ├── db.255
│   │   ├── db.empty
│   │   ├── db.local
│   │   ├── db.root
│   │   ├── named.conf
│   │   ├── named.conf.default-zones
│   │   ├── named.conf.local
│   │   ├── named.conf.options
│   │   ├── rndc.key
│   │   └── zones.rfc1918
│   └── localtime
└── var
    ├── cache
    │   └── bind
    │       ├── managed-keys.bind
    │       └── managed-keys.bind.jnl
    └── run
        └── named