How to configure a FreeBSD Jail on a Digital Ocean Droplet

  |   Source

One of the great things about FreeBSD is its long standing support for jails. A jail is a way to run a process or set of processes in an environment that is isolated from the host system. Processes created inside a jail cannot access files outside of that jail.

There are a host of reasons why you might want to run your services in jails, but the primary reason is that it allows you to run disparate services without having to worry about a flaw in one service allowing access to another service. For example, jails will allow you to run a mail server and a web server on the same Droplet without having to be overly concerned that a vulnerability in your web site could expose the data in your mail server.

Over the course of this article, you will take a newly minted FreeBSD Droplet, do some initial configuration, set up a jail, and install a simple web server inside the jail.

In the end, you will be setting up a firewall to protect the host system. This tutorial will be using the PF firewall that is included in FreeBSD. Aside from configuring a firewall, you will also be making some tweaks to the default shell as well as making some changes to the configuration of some of the default services.


The default editor and shell prompt

By default, FreeBSD uses tcsh as it's default shell, and vi as the default editor. Both are pretty spartan, so in this optional section, you'll be updating the shell prompt and the default editor. Since you'll probably want a better editor in order to fix the shell prompt, we'll start with the editor.

A better editor

The following command will install a version of vim that does not require X11.


sudo pkg install vim-lite

Since it is the first time that the pkg command has been run, it is going to do some initial setup & checks. If you've run pkg before, then your output may look different.


> sudo pkg install vim-lite
Updating FreeBSD repository catalogue...
Fetching meta.txz: 100%   944 B   0.9k/s    00:01
Fetching packagesite.txz: 100%    5 MB   1.8M/s    00:03
Processing entries: 100%
FreeBSD repository update completed. 23992 packages processed
New version of pkg detected; it needs to be installed first.
The following 1 packages will be affected (of 0 checked):

Installed packages to be UPGRADED:
    pkg: 1.4.4 -> 1.4.12

The process will require 23 KB more space.
2 MB to be downloaded.

Proceed with this action? [y/N]: y
Fetching pkg-1.4.12.txz: 100%    2 MB   1.2M/s    00:02
Checking integrity... done (0 conflicting)
[1/1] Upgrading pkg from 1.4.4 to 1.4.12...
[1/1] Extracting pkg-1.4.12: 100%
Message for pkg-1.4.12:
 If you are upgrading from the old package format, first run:

  # pkg2ng
Updating FreeBSD repository catalogue...
FreeBSD repository is up-to-date.
All repositories are up-to-date.
The following 1 packages will be affected (of 0 checked):

New packages to be INSTALLED:
    vim-lite: 7.4.591

The process will require 20 MiB more space.
5 MiB to be downloaded.

Proceed with this action? [y/N]: y
Fetching vim-lite-7.4.591.txz: 100%    5 MiB   1.6MB/s    00:03
Checking integrity... done (0 conflicting)
[1/1] Installing vim-lite-7.4.591...
[1/1] Extracting vim-lite-7.4.591: 100%

In order to get tcsh to see the newly installed package, you'll need to rehash the path:


rehash

The rehash command should exit with no output.

A more helpful shell prompt

FreeBSD include a sample csh configuration file that is an excellent starting off point. You'll need to grab a copy of it in order to get started:


cp /usr/share/skel/dot.cshrc ~/.cshrc

Even though the default shell is tcsh, it will read ~/.cshrc, since tcsh inherits a lot from the csh shell. Go ahead and open this file for editing:


vim ~/.cshrc

You'll probably want to change a couple of the defaults in this file. The default editor is set to vi and the default pager is set to more. Go ahead and change these to vim and less respectively. In order to do this, change


. . .

setenv  EDITOR  vi
setenv  PAGER   more

. . .

to


. . .

setenv  EDITOR  vim
setenv  PAGER   less

. . .

You may not have noticed, but some of your keys may not work as expected -- the delete is a great example. If you add the following chunk of code to the end of the file, that problem will be fixed. This code will check the terminal type that is connected and, if the terminal type is one that is expected, it will fix the whacky key bindings:


if ($term == "xterm" || $term == "vt100" \
            || $term == "vt102" || $term !~ "con*") then
          # bind keypad keys for console, vt100, vt102, xterm
          bindkey "\e[1~" beginning-of-line  # Home
          bindkey "\e[7~" beginning-of-line  # Home rxvt
          bindkey "\e[2~" overwrite-mode     # Ins
          bindkey "\e[3~" delete-char        # Delete
          bindkey "\e[4~" end-of-line        # End
          bindkey "\e[8~" end-of-line        # End rxvt
endif

After the file has been saved, you can exit, you can make your shell read the new configuration with:


source ~/.cshrc

Your prompt should change immediately:


[email protected]:~ %

Time and Time Zones

Time Zone

While not entirely necessary, keeping the time on your server correct is a recommended best practice. Start by setting the timezone of our server:


sudo tzsetup

Right off the bat, you should be prompted about the hardware clock on your machine:


/images/FreeBSD_tzsetup.png

You should choose "No" to set your clock to local time. Next you will be asked to identify where in the world your server is located:


/images/FreeBSD_regions.png

Next you'll need to be more specific about your region:


/images/FreeBSD_subregions.png

Finally, you'll be asked to choose a timezone:


/images/FreeBSD_TimeZones.png

NTP for accurate time keeping

With the time zone all configured, it's a good idea to configure NTP to keep the time accurate. FreeBSD comes by default with an implementation of the ISC NTP client, but for this tutorial, we'll be using OpenNTPD in order to keep the configuration as simple as possible:


sudo pkg install openntpd

OpenNTPd should install easily:


> sudo pkg install openntpd
Updating FreeBSD repository catalogue...
FreeBSD repository is up-to-date.
All repositories are up-to-date.
The following 1 packages will be affected (of 0 checked):

New packages to be INSTALLED:
openntpd: 5.7p3,2

The process will require 79 KiB more space.
36 KiB to be downloaded.

Proceed with this action? [y/N]: y
Fetching openntpd-5.7p3,2.txz: 100%   36 KiB  37.4kB/s    00:01
Checking integrity... done (0 conflicting)
[1/1] Installing openntpd-5.7p3,2...
===> Creating users and/or groups.
Creating group '_ntp' with gid '123'.
Creating user '_ntp' with uid '123'.
[1/1] Extracting openntpd-5.7p3,2: 100%

You can make OpenNTPD start by default, and start the service manually with the following commands:


sudo sh -c 'echo "openntpd_enable=\"YES\"" >> /etc/rc.conf'
sudo service openntpd start

By default, the OpenNTPD service will not listen for time requests, and will use the Tier-2 servers from ntp.org for keeping accurate time. If you want to use a different time server for synchronization, or use OpenNTPD as a time server, please see /usr/local/etc/ntpd.org.


Adding a jail

There are multiple ways to manage your jails, but ezjail is one of the more popular ones, probably beacuse it is just so EZ. You can install it from a binary package:


sudo pkg install ezjail

Ezjail works by creating a base jail, and then using that base as the model for all subsequent jails. Set up the base jail:


sudo ezjail-admin install -p

The -p flag should cause ezjail to include the FreeBSD ports tee in the base jail. The initial installation of the base jail may take a few minutes.

Setting up the network

You could go ahead and create your jail right off, but the networking will not work out of the box. Take a minute and do some basic network configuration before creating your first jail.

Virtual Interface

In order to keep all of the jails behind a single public IP address, you'll need to set up a new network interface. This new interface will be a clone of the loopback interface which will have an IP address assigned to it. You can you any RFC 1918 address space. In this tutorial, 172.16.1.1 will be used. Add the following to /etc/rc.conf to get the new interface set up:


# Setup the interface that all jails will use
cloned_interfaces="lo1"
ifconfig_lo1="inet 172.16.1.1 netmask 255.255.255.0"

# Future jails can use the following as a template.
# Be sure to use 255.255.255.255 as the netmask for all interface aliases
# ifconfig_lo1_alias0="inet 172.16.1.2 netmask 255.255.255.255"

# Enable port forwarding and packet filtering
pf_enable="YES"

# Enable EZJail at startup
ezjail_enable="YES"

With those entries in /etc/rc.conf the interfaces will all be created and configured at boot time. To get the interface set up without a reboot, you can use the following commands:


sudo ifconfig lo1 create
sudo ifconfig lo1 inet 172.16.1.1 netmask 255.255.255.0

An ifconfig should now show a new interface:


lo1: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> metric 0 mtu 16384
    options=600003<RXCSUM,TXCSUM,RXCSUM_IPV6,TXCSUM_IPV6>
    inet 172.16.1.1 netmask 0xffffff00
    nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL>

NAT for packet forwarding

Now that you have an interface to use with your jails, you'll need to get some packet forwarding set up. Edit /etc/pf.conf (which will be empty by default) according to the following


#Define the interfaces
ext_if = "vtnet0"
int_if = "lo1"
jail_net = $int_if:network

#Define the NAT for the jails
nat on $ext_if from $jail_net to any -> ($ext_if)

The first 3 lines define a couple of useful variables -- called macros in PF parlance. The nat line instructs PF to mask outbound traffic from the jails (all of them) behind the IP address of the external interface. In short, all of your outbound jail traffic will come from the IP address of your droplet.

With that in place, you can start PF:


sudo service pf start

Before you load the firewall ruleset, test the config file to ensure that all is well:


sudo pfctl -nvf /etc/pf.conf

That command will cause PF to parse the rules in /etc/pf.conf, and print them back out to the console. If there are syntax errors, they will be called out.


[email protected]:~ % sudo pfctl -nvf /etc/pf.conf
ext_if = "vtnet0"
int_if = "lo1"
jail_net = "lo1:network"
nat on vtnet0 inet from 172.16.1.0/24 to any -> (vtnet0) round-robin

Your output should look very similar to what is above. Once you are getting output that inidicates no errors you can turn on the NAT:


sudo pfctl -f /etc/pf.conf

Creating the first jail

It's time to create & start a jail:


sudo ezjail-admin create WEBSERVER 172.16.1.1
sudo ezjail-admin start WEBSERVER

Those commands will have a lot of output, and may end with a warning. You can safely ignore the warnings. The jail needs to be told how to do DNS lookups. The simple way to solve this is to copy the hosts /etc/resolv.conf into the jail:


sudo cp /etc/resolv.conf /usr/jails/WEBSERVER/etc/

Go ahead and jump into the console of our jail:


sudo ezjail-admin console WEBSERVER

There will be a lot of stuff on the console -- very similar to what you should have seen when you first connected to your Droplet.


[email protected]:~ % sudo ezjail-admin console WEBSERVER
FreeBSD 10.1-RELEASE (GENERIC) #0 r274401: Tue Nov 11 21:02:49 UTC 2014

Welcome to FreeBSD!

Release Notes, Errata: https://www.FreeBSD.org/releases/
Security Advisories:   https://www.FreeBSD.org/security/
FreeBSD Handbook:      https://www.FreeBSD.org/handbook/
FreeBSD FAQ:           https://www.FreeBSD.org/faq/
Questions List: https://lists.FreeBSD.org/mailman/listinfo/freebsd-questions/
FreeBSD Forums:        https://forums.FreeBSD.org/

Documents installed with the system are in the /usr/local/share/doc/freebsd/
directory, or can be installed later with:  pkg install en-freebsd-doc
For other languages, replace "en" with a language code like de or fr.

Show the version of FreeBSD installed:  freebsd-version ; uname -a
Please include that output and any error messages when posting questions.
Introduction to manual pages:  man man
FreeBSD directory layout:      man hier

Edit /etc/motd to change this login announcement.
[email protected]:~ #

Notice that your prompt has changed. Congratulations, you are inside your jail! It's probably a good idea to test your connectivity to the outside world, but by default, and for security reasons, FreeBSD jails are not allowed to ping. To test connectivity, you can use telnet:


telnet www.digitaloean.com 80

That command will open up a very basic connection to a webserver at Digital Ocean. You can get out of it pressing Control-] to close the connection, followed by quit to close telnet.


[email protected]:~ # telnet www.digitalocean.com 80
Trying 104.16.24.4...
Connected to www.digitalocean.com.
Escape character is '^]'.
^]
telnet> quit
Connection closed.
[email protected]:~ #

Installing a webserver

With a jail up and connected to the internet, you can go ahead and install a webserver. You should be inside of your jail for this (remember that you can use sudo ezjail-admin console WEBSERVER to get into your jail):


pkg install nginx

Since pkg has not yet been run in your jail, it will prompt you to allow it to install and let it configure itself. Then you will be prompted about installing the webserver:


[email protected]:~ # pkg install nginx
The package management tool is not yet installed on your system.
Do you want to fetch and install it now? [y/N]: y
Bootstrapping pkg from pkg+http://pkg.FreeBSD.org/freebsd:10:x86:64/latest, please wait...
Verifying signature with trusted certificate pkg.freebsd.org.2013102301... done
[WEBSERVER] Installing pkg-1.4.12...
[WEBSERVER] Extracting pkg-1.4.12: 100%
Message for pkg-1.4.12:
 If you are upgrading from the old package format, first run:

  # pkg2ng
Updating FreeBSD repository catalogue...
[WEBSERVER] Fetching meta.txz: 100%    944 B   0.9kB/s    00:01
[WEBSERVER] Fetching packagesite.txz: 100%    5 MiB   1.8MB/s    00:03
Processing entries: 100%
FreeBSD repository update completed. 23992 packages processed
Updating database digests format: 100%
The following 2 packages will be affected (of 0 checked):

New packages to be INSTALLED:
    nginx: 1.6.2_1,2
    pcre: 8.35_2

The process will require 6 MiB more space.
1 MiB to be downloaded.

Proceed with this action? [y/N]:

When you say "Yes", pkg will finish the installation of nginx. Next set nginx to start when the jail starts, and start it immediately:


echo 'nginx_enable="YES"' > /etc/rc.conf.d/nginx
service nginx start

Redirecting the web traffic to the webserver

Your web server should now be running, but you can't access it quite yet. Issue an exit command inside your jail to get back to your host system. Make a couple of edits to /etc/pf.conf to make it look like this:


# Define the interfaces
ext_if = "vtnet0"
int_if = "lo1"
jail_net = $int_if:network

# Define the IP address of jails
# as well as ports to be allowed redirected
WEBSERVER = "172.16.1.1"
WEBSERVER_TCP_PORTS = "{ 80, 443 }"

# Define the NAT for the jails
nat on $ext_if from $jail_net to any -> ($ext_if)

# Redirect traffic on ports 80 and 443 to the webserver jail
rdr pass on $ext_if inet proto tcp to port $WEBSERVER_TCP_PORTS -> $WEBSERVER

A quick sudo pfctl -nf /etc/pf.conf should return nothing, indicating that there are no syntax errors. You can flush the current rules and reload them:


sudo pfctl -F all -f /etc/pf.conf

To be perfectly clear, that command will flush all of the existing tables and load up the new rules.

At this point, you should be able to browse to the IP of your Droplet, and get the default Nginx page:


/images/FreeBSD_nginx_default.png

Finishing touches

With a fully functioning jail, there are just 2 things left to do.

Locking down the firewall

The way things stand right now, your jail is working, but the host system is pretty wide-open. If you edit your /etc/pf.conf once more, we can restrict all IB traffic that is not destined for a jail except for SSH.


# Define the interfaces
ext_if = "vtnet0"
int_if = "lo1"
jail_net = $int_if:network

# Define the IP address of jails
# as well as ports to be allowed redirected
WEBSERVER = "172.16.1.1"
WEBSERVER_TCP_PORTS = "{ 80, 443 }"

# Define the NAT for the jails
nat on $ext_if from $jail_net to any -> ($ext_if)

# Redirect traffic on ports 80 and 443 to the webserver jail
rdr pass on $ext_if inet proto tcp to port $WEBSERVER_TCP_PORTS -> $WEBSERVER

# Set the default: block everything
block all

# Allow the jail traffic to be translated
pass from { lo0, $jail_net } to any keep state

# Allow SSH in to the host
pass in inet proto tcp to $ext_if port ssh

# Allow OB traffic
pass out all keep state

As always, test your config changes:


sudo pfctl -nf /etc/pf.conf

If there is output, then fix the indicated errors, and keep running that same command until the command runs with no output. When you are ready, reload the firewall rules:


sudo pfctl -f /etc/pf.conf

After that command, you may get disconnected from the server. You should be able to reconnect without issue.


Tweaking the jail

Jump back into your jail:


sudo ezjail-admin console WEBSERVER

If you run the date command in your jail, you'll notice that the time zone might be different from the host's timezone. You can fix that by running:


tzsetup

from within the jail. It is the same process as you already went through earlier when you set up the timesone for the host.

While you are in the jail, you should probably add the following lines to /etc/rc.conf as well:


rpcbind_enable="NO"             # Disable the RPC daemon
cron_flags="$cron_flags -J 15"  # Prevent lots of jails running cron jobs at the same time
syslogd_flags="-ss"             # Disable syslogd listening for incoming connections
sendmail_enable="NONE"          # Completely disable sendmail
clear_tmp_enable="YES"          # Clear /tmp at startup

As you can see, they are basic commands for keeping your jail in order.

Moving Forward

With the first jail out of the way, you can continue to add jails as you need them. The high level steps would be along these lines:

  1. Create a new lo1 alias with a unique IP address in /etc/rc.conf
  2. Define the jail in /etc/pf.conf, including the IP, ports to be redirected, and the redirect rules.
  3. Create the jail

Welcome to the world of FreeBSD Jails.

Comments powered by Disqus