In order to keep all of the jails behind a single public IP address,
you’ll need to set up a loopback interface and direct the incoming and
outgoing traffic of your Jails with a proper firewall like pf
. Learn
how to get started on an amazing setup.
There are multiple ways to manage your jails, but iocage is one of the more popular ones. In terms of jail managers it’s a fairly new player in the game of jail management and is being very actively developed. It has a full rewrite in Python, and while the code in the background might be different, the actual user interface has stayed very similar.
Iocage makes use of ZFS clones in order to create base jails, which allow for sharing of one set of system packages between multiple jails, reducing the amount of resources necessary. Alternatively, jails can be completely independent of each other; however, using a base jail makes it easier to update multiple jails as well.
You can install it from a binary package:
pkg install -y py37-iocage
¶Setting up the zpool
It is a great idea to separate the jails storage device from the host system’s storage. Ideally in a separate physical device, but a separate partition will also do.
This means that you would be in a situation where your zroot
is
already setup, and you have a free device/partition where you can create
a second zpool
to be used specifically for the Jails.
Doing a gpart show
in my machine reveals the following setup:
=> 40 41942960 da0 GPT (20G) 40 1024 1 freebsd-boot (512K) 1064 984 - free - (492K) 2048 4194304 2 freebsd-swap (2.0G) 4196352 20971520 3 freebsd-zfs (10G) 25167872 16773120 4 freebsd-zfs (8.0G) 41940992 2008 - free - (1.0M)
This means I want to create a new pool on da0p4
with the name iocage
since my system has been installed on da0p3
.
This can be achieved very simply with the command:
zpool create iocage da0p4
With the pool created, you can now activate iocage
to use the wanted
pool:
iocage activate iocage
Then fetch the latest FreeBSD release (or the one you prefer) to be used to setup jails:
iocage fetch
Add the following new line to your /etc/fstab
file to improve the jail
performance:
fdescfs /dev/fd fdescfs rw 0 0
¶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
For our internal network, we create a cloned loopback device called
lo1
. Therefore we need to customize the /etc/rc.conf
file, adding
the following two lines:
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 use any RFC 1918 address space. In this
tutorial, 192.168.0.1
will be used. Add the following to
/etc/rc.conf
to get the new interface set up:
# /etc/rc.conf # Setup the interface that all jails will use cloned_interfaces="lo1" ipv4_addrs_lo1="192.168.0.1/24" gateway_enable="YES" # Enable port forwarding and packet filtering pf_enable="YES" # Enable iocage at startup iocage_enable="YES"
This defines a /24
network, offering IP addresses for a maximum of 254
jails:
joe@devjails > ipcalc 192.168.0.1/24 Address: 192.168.0.1 11000000.10101000.00000000. 00000001 Netmask: 255.255.255.0 = 24 11111111.11111111.11111111. 00000000 Wildcard: 0.0.0.255 00000000.00000000.00000000. 11111111 => Network: 192.168.0.0/24 11000000.10101000.00000000. 00000000 HostMin: 192.168.0.1 11000000.10101000.00000000. 00000001 HostMax: 192.168.0.254 11000000.10101000.00000000. 11111110 Broadcast: 192.168.0.255 11000000.10101000.00000000. 11111111 Hosts/Net: 254 Class C, Private Internet
Then we need to restart the network. Please be aware of currently active SSH sessions as they will be dropped during restart. It’s a good moment to ensure you have KVM or physical access to that server ;-)
With those entries in /etc/rc.conf
the interfaces will all be created
and configured at boot time, so give the machine a nice reboot
After booting, an ifconfig
should now show a new interface:
lo1: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> metric 0 mtu 16384 options=680003<RXCSUM,TXCSUM,LINKSTATE,RXCSUM_IPV6,TXCSUM_IPV6> inet 192.168.0.1 netmask 0xffffff00 groups: lo 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
ext_if="em0" jail_if="lo1" # Public IP address ip_pub="192.168.178.40" # Packet normalization scrub in all # Skip packet filtering on loopback interfaces set skip on lo0 set skip on lo1 # Allow outbound connections from within the jails nat pass on $ext_if from 192.168.0.0/24 to any -> $ip_pub
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 enable and start PF:
service pf enable service pf start
Before you load the firewall ruleset, test the config file to ensure that all is well:
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.
Your output should be very similar to:
ext_if = "em0" jail_if = "lo1" IP_PUB = "192.168.178.40" set skip on { lo0 } set skip on { lo1 } scrub in all fragment reassemble nat pass on em0 inet from 192.168.0.0/24 to any -> 192.168.178.40
Then reload the firewall rules with:
pfctl -F all -f /etc/pf.conf
¶Creating the first jail
It’s time to create & start a jail:
iocage create -n "mydb" -r 12.2-RELEASE ip4_addr="lo1|192.168.0.2" iocage start mydb
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. Assuming your zpool was setup to point to /iocage:
mkdir /iocage/iocage/jails/mydb/etc cp /etc/resolv.conf /iocage/iocage/jails/mydb/etc/resolv.conf
Then start the machine and enter the console:
iocage start mydb iocage console mydb
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.
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.digitalocean.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.
Trying 104.16.24.4...
Connected to www.digitalocean.com.
Escape character is '^]'.
^]
telnet> quit
Connection closed.
Now you are ready to install your desired software and set up the wanted
services in the jail. It is a good idea to get familiar with iocage
since it has many handy commands to manage the jails.
In my case, I installed MariaDB on this jail, and for that purpose,
opened up a port redirection in the firewall, so that traffic from the
public-facing interface will be redirected to the internal jail’s port.
You can do this by adding this line to the end of /etc/pf.conf
, with
192.168.0.4
being the internal IP I have assigned to this jail:
rdr on $ext_if proto tcp from any to $ip_pub port 3306 -> 192.168.0.4 port 3306
Then reload the firewall rules with:
pfctl -F all -f /etc/pf.conf
¶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 inbound traffic that is not destined for a jail
except for SSH.
Edit your file to look like:
ext_if="em0" jail_if="lo1" jail_net= $jail_if:network # Public IP address ip_pub="192.168.178.40" # Packet normalization scrub in all # Skip packet filtering on loopback interfaces set skip on lo0 set skip on lo1 # Allow outbound connections from within the jails nat pass on $ext_if from 192.168.0.0/24 to any -> $ip_pub # Port redirect for MariaDB rdr pass on $ext_if proto tcp to port 3306 -> 192.168.0.4 # 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
Now you have a secure machine, that only takes traffic from the wanted ports. This starts feeling production ready!
¶Jail tweaks
Jump back into your jail with iocage console -f mydb
and add the
following 2 lines to /etc/rc.conf
:
# Disable the RPC daemon rpcbind_enable="NO" # Clear /tmp at startup clear_tmp_enable="YES"
Go on now, create your universe of inter-connected systems, welcome to the world of FreeBSD jails!