So, we’ve seen how to create a native jail using FreeBSD’s toolset, and we’ve fine-tuned a few of its settings, including mounting select directories from the host into the jail.

Is that really enough though? 🙃

ZFS inside

We want zfs inside our jail, period!

But why?

Since we use a dedicated zfs dataset per jail, isn’t that enough? Well, dataset management (and anything disk-related) is handled on the host.

Practically speaking, this means that the root user inside the jail cannot alter dataset properties, nor create new ones.

Under specific circumstances, we may want to delegate dataset management & creation to the jail administrator.

How about a backup server scenario, for instance, where each user gets their own jail, and may want or need to manage their own encrypted datasets? 🤓

Let’s!

So, we’re going to delegate a test dataset to a jail appropriately named testjail.

Host config

Using ZFS inside a jail requires a few additionnal jail properties in /etc/jail.conf or /etc/jail.conf.d/. Let’s add the following lines to the jail’s config:

# jail-specific zfs
allow.mount;
allow.mount.zfs;
enforce_statfs = "1";

Note: we need to set enforce_statfs from 2 to 1, in order to lift the restriction preventing dataset creation within the jail (see bastille docs).

Create…

On the host, let’s start by creating a dataset:

zfs create zstorage/test

Set…

To assign the test dataset to any jail, we first have to enable the jailed flag on the specific dataset, from the host:

zfs set jailed="on" zstorage/test

Delegate!

This article assumes we have a jail called testjail running and properly configured. Now that we’ve set its jailed property to on, let’s push the dataset into the jail (from the host):

zfs jail testjail zstorage/test

And voilà!

By the power of zfs

Let there be light

Once delegation is active, the assigned dataset(s) appear inside the jail with a simple zfs list:

# zfs list
NAME                  USED  AVAIL  REFER  MOUNTPOINT
zstorage             2.68T  19.0T   128K  none
zstorage/test         762K  19.0T   139K  none

It should not be mounted by default unless the dataset mountpoint property has been set, but this is a good opportunity to try and set dataset flags from within the jail and see what’s up:

zfs set mountpoint="/srv/" zstorage/test

Success! zfs mount shows zstorage/test, how cool is that?

How far can we see?

Alongside the delegated test dataset, we can see see the parent dataset belonging to the host! Eww, does that mean we can mess with it? Let’s try!

# zfs set atime=on zstorage
cannot set property for 'zstorage': permission denied

Looks like the parent dataset is safe, and that its properties cannot be modified outside of the host. Hint: in addition to zfs-jail, zfs-zone might be involved.

Similarly, attempts at creating another child dataset directly under the host’s parent dataset from the jail also fail:

# zfs create zstorage/hacktheplanet
cannot create 'zstorage/hacktheplanet': permission denied

For the record, zstorage may well have many other child datasets on the host or within other jails, but their existence does not leak into our jail!

More autonomous children

What we can do, however, is create child datasets underneath the delegated dataset, from within the jail itself, possibly with encryption properties set outside of the host’s perimeter, as suggested earlier:

zfs create  -o encryption="on" \
            -o keyformat="passphrase" \
               zstorage/test/child

Aw yeah!

Config consolidation

On the jail

To ensure datasets are mounted when the jail starts, we need to enable ZFS in the jail’s /etc/rc.conf:

sysrc zfs_enable="yes"
sysrc zfskeys_enable="yes"

Note: If you want to ensure the host cannot access encrypted datasets at rest, unmount them and remove their key from memory. Shutting down the jail doesn’t affect this (since the kernel is shared among all jails):

zfs umount zstorage/test/child
zfs unload-key zstorage/test/child

On the host

To detach a dataset from the jail from the host, the jail must still be running:

zfs unjail testjail zstorage/test

Now, we want these zfs jail & zfs unjail commands to be automatically issued when starting or stopping the jail, so our settings survive a jail restart.

To do so, let’s add the following to the jail’s configuration on the host in /etc/jail.conf or /etc/jail.conf.d/:

exec.created += '/sbin/zfs jail testjail zstorage/test';
exec.prestop += '/sbin/zfs unjail testjail zstorage/test';

There you go!