There are different methods to get a sandbox environment. Here, we use the Firejail Security Sandbox, which allows to assign a private sealed scope to a service and all associated processes; this includes resources like network access, process table or filesystem. Therewith, the service only sees its own processes and can only access the part of the filesystem that has been assigned tio it.
A very helpful source for this howto was the articlel How To Use Firejail to Set Up a WordPress Installation in a Jailed Environment, which describes a WordPress installation including Firejail, Nginx and MySQL. In contrast to that article we restrict to the installation of the basic web and database services, especially Apache and PostgreSQL; on this basis, the reader may set up content-management systems (like Drupal or after all WordPress) or completely different services as usual. A further difference to the mentioned article is, that all scripts work with a specified target IP address, i.e., it is possible to run other services on the same machine, independent from the services described here, as long as they respond to other IP addresses.
We will set up the services so that they communicate with each other and the outer world over a bridge. The IP addresses involved are shown in the following table:
|Service||Public IP Address||Bridge IP Address|
This howto applies to Debian systems.
In case of – as happens to be for Debian 7 ("Wheezy") – Firejail is not available as a system package, it has to be downloaded from Firejail/Download and be installed.
Furthermore, we need a package to set up a minimal filesystem
apt-get install debootstrap
and tools to set up the bridge:
apt-get install bridge-utils
In /etc/network/interfaces the public IP address has to be configured, e.g.,
iface eth0 inet static address yyy.yyy.yyy.yyy netmask 255.255.255.0 gateway yyy.yyy.yyy.1 up ip addr add xxx.xxx.xxx.xxx/32 dev eth0 label eth0:0
where yyy.yyy.yyy.yyy is the "main" IP addresse of our machine and xxx.xxx.xxx.xxx is the public address of the host to be configured by this howto.
If still an Apache is running on this system that shall listen to IP addresses different from the one set up here, it has to be configured to listen to these addresses exactly. To achieve this, in its configuration files any _default_ entries have to be changed to the actual IP address, respectively. As a template, you can use the entries for ports.conf and sites-available/* of the table in the Webserver section.
We start with some definitions.
Of course, the public target address of our services has to be accommodated individually:
Further definitions – as a general rule – can be taken over as they are:
INTERFACE="eth0+" TCP_SERVICES="80 443" #web browsing BRIDGE_HOST_IP="10.10.20.1" BRIDGE_HOST_IP_RANGE=$BRIDGE_HOST_IP"/24" BRIDGE_WEBSERVER_IP="10.10.20.10" BRIDGE_DBSERVER_IP="10.10.20.20"
We create and configure our bridge:
brctl addbr br0 ifconfig br0 $BRIDGE_HOST_IP_RANGE
The operating systems allows forwarding of IP addresses by:
echo "1" > /proc/sys/net/ipv4/ip_forward
We allow traffic that is dedicated to our web server to be forwarded through our bridge:
for PORT in $TCP_SERVICES; do iptables -t nat -A PREROUTING -d $DESTINATION_IP -p tcp --dport $PORT -j DNAT --to $BRIDGE_WEBSERVER_IP:$PORT done
Vice versa, traffic that is going to the outside world shall be masqueraded by the public address:
iptables -t nat -A POSTROUTING -o $INTERFACE -j MASQUERADE
Traffic coming from the dedicated ports is allowed:
for PORT in $TCP_SERVICES; do iptables -A FORWARD -i $INTERFACE -o br0 -p tcp -m tcp --dport $PORT -j ACCEPT done
Also, incoming traffic of already established connections is allowed:
iptables -A FORWARD -i $INTERFACE -o br0 -m state --state RELATED,ESTABLISHED -j ACCEPT
We allow all traffic that comes from our bridge to put the jails in a position to communicate with each other and the outside world:
iptables -A FORWARD -i br0 -j ACCEPT
All other forwarding will be dropped:
iptables -P FORWARD DROP
Optionally, we may allow traffic originating from our host to access the database server:
iptables -I OUTPUT -p tcp -s $BRIDGE_HOST_IP -d $BRIDGE_DBSERVER_IP -j ACCEPT
This is convenient if we want, let's say, to administrate the database with a service like phpPgAdmin over a separate IP address.
A template for an /etc/init.d script is attached below. Before running it, at least DESTINATION_IP has to be set to the public IP address of the host.
We create a directory for the filesystems that later will be caught in a andbox and change to this:
mkdir /jail cd /jail
Then, we create a minimal filesystem for the web server
debootstrap --arch=amd64 stable www
and make a copy for the database server
rsync -a www/ db
The latter is less expensive than a new debootstrap which would be an alternative .
By the way, the sandbox operating system does not necessarily need to be equal to to the host operating system. At the time of writing this document, my Debian-7 host system already was "oldstable"; regardless, it worked with "debootstrap ... stable ..." and it was possible to install and operate software from the stable branch within the jails.
4.1 Web Server
To set up the web server, we start firejail within the filesystem created before
firejail --chroot=/jail/www/ --name=www
and install the web-server software (in this case Apache):
apt-get update apt-get install apache2
Next we have to configure Apache for the environment in which it shall run. We can do this within the enviroment started by firejail (then we are working within the directory /etc/apache2) or from outside (then the configuration directory is /jail/www/etc/apache2); in both cases we are working on the same directory, only the root of the filesystem is different.
The following table shows the important entries to be made:
|ports.conf||Set Listen directive to 10.10.20.10:80 resp. 10.10.20.10:443|
|conf-available/servername.conf (create file if necessary)||One entry only: ServerName 10.10.20.10|
|sites-available/000-default.conf||Set VirtualHost and ServerName to 10.10.20.10:80|
|sites-available/default-ssl.conf||Set VirtualHost and ServerName to 10.10.20.10:443|
|envvars (optional)||export APACHE_LOG_DIR=/var/local/log/apache2$SUFFIX (see below)|
On starting of the jail the directory /var/log will be moved to a temporary location. To get the Apache log files permanently, we can change the log directory in the configuration file envvars as described above (here to /var/local/log/apache2), but have to create the corresponding directory structure manually: mkdir -p /var/local/log/apache2 (within the jail).
To enable the server name. we call the command (within the jail)
Besides, all "normal" configurations have to be done that are required or desired for the opration of the web site.
If the web server is already running, we should stop it by
service apache2 stop
before leaving the firejail environment by
Regarding to the database server, we proceed accordingly, i.e., first
firejail --chroot=/jail/db/ --name=db
(now specifying db as chroot and name) and then (for PostgreSQL)
apt-get install postgresql
In the file /etc/postgresql/9.4/main/postgresql.conf (within the jail, adjust the version number accordingly) the following configurations have to be made:
|listen_addresses||'10.10.20.20'||Listen to our bridge IP.|
|logging_collector||on||Optional for logging.|
|log_directory||'/var/local/log/postgresql'||Optional, to be consistent with www logging.|
When enabling the alternative log_directory, we have to create it and set appropriate permissions (within the jail):
mkdir -p /var/local/log/postgresql chmod -R g-rwx,o-rwx /var/local/log/postgresql chown -R postgres:postgres /var/local/log/postgresql
Of course, all other "normal" operation configurations have to be done here too.
If the database server is already running, we should stop it by
service postgresql stop
before leaving the firejail environment by
Note Despite intensive attempts using diverse configurations and doing exhaustive investigations over several days I failed to set up a MySQL server with Firejail. Although the server started, it always immediately was shutting down without leaving useful error or log messages. For this reason, I finally chose PostgreSQL.
For set-up purposes we could start the jails as described above. To provide a network connection and to start the servers automatically in the background these commands are needed:
firejail --name=www --chroot=/jail/www --private --net=br0 --ip=10.10.20.10 sh -c "service apache2 start; sleep inf" & firejail --name=db --chroot=/jail/db --private --net=br0 --ip=10.10.20.20 sh -c "service postgresql start; sleep inf" &
The option --private ensures that certain directories of the original filesystem like /root or /home are not visible, --net and --ip establishes the routing and the succeeding command is executed in the jail using those parameters: web and database server are started and sleep inf ensures that the sandbox is still running even when the Apache or PostgreSQL process ends or crashes (the latter might be of interest for administrative ends). Note: sh -c is emphasized because in contrast to other descriptions it didn't work for me without this.
The running firejail processes can be listed by:
To join a running sandbox we call
Stopping of the servers can be done by
firejail --shutdown=www firejail --shutdown=db
Scripts for init.d are attatched below.
Using the described procedure one gets a web server and a database server, both running in a sealed jail, on the same machine within an internal IP range.
Within the web-server jail one can install a CMS like Drupal or WordPress as usual now, merely paying attention to the fact that the database has to be accessed by an internal IP address (10.10.20.20).
By the sealing it is achieved that, e.g., an attacker that could intrude by SQL injection only can cause damage within the database jail, but not in the rest of the system. Although such security measures never can be perfect, at least we have made the attacker a very hard work.