ZFS encryption and notification service on OmniOS

       679 words, 4 minutes

I have a Dell server in a colo. It (will) hosts some virtual machines and (private) data. And because trust does not exclude control (on your data), I do encrypt (at rest) some of the storage.

Read the zpool-features(7) and zfs(8) manpages to learn about the encryption feature. Read the smf(7) manpage to learn about Solaris service management facility.

Encrypted ZFS dataset

Create a ZFS pool and enable the encryption feature.

# diskinfo
TYPE  DISK    VID   PID        SIZE         RMV SSD
SCSI  c1t0d0  DELL  PERC H710   136.12 GiB  no  no
SCSI  c1t1d0  DELL  PERC H710  1116.75 GiB  no  no

# zpool create -m /tank -o feature@encryption=enabled tank c1t1d0

Create an encrypted ZFS dataset.

# zfs create -o mountpoint=/zones          \
  -o encryption=on -o keyformat=passphrase \
  tank/zones
Enter passphrase:
Re-enter passphrase:

Note on keylocation: if unspecified, the default is prompt. Which is exactly what I want.

Note on pbkdf2iters: although I tried to understand what a “good value” would be, I couldn’t make up my mind. So I just used the default OmniOS (Illumos?) value ; which is 350000.

Let’s have a look at what’s been done.

# zfs get -rp encryption,keystatus,pbkdf2iters,keylocation tank
NAME        PROPERTY     VALUE        SOURCE
tank        encryption   off          default
tank        keystatus    -            -
tank        pbkdf2iters  0            default
tank        keylocation  none         default
tank/zones  encryption   aes-256-ccm  -
tank/zones  keystatus    available    -
tank/zones  pbkdf2iters  350000       -
tank/zones  keylocation  prompt       local

Notification service

As I used the passphrase keyformat and the default keylocation is prompt, the ZFS dataset is not automatically mounted when the system boots. It must be done manually ; which is exactly what I want. So I wrote a script that runs at boot time and notifies me, using the excellent Pushover notification system , that I have to log into the server and mount / unlock the dataset.

# vi /home/scripts/notif-boot
#!/usr/bin/env sh
PATH="/bin:/usr/bin:/sbin:/usr/sbin"
TOKEN='<changeme>'
USER='<changeme>'
TITLE="$(hostname -s) is ready for action"
MESSAGE="Connect and run /home/scripts/afterboot."
curl -s                                    \
  -F "token=$TOKEN"                        \
  -F "user=$USER"                          \
  -F "title=$TITLE"                        \
  -F "message=$MESSAGE"                    \
  https://api.pushover.net/1/messages.json \
  2>&1 1>/dev/null
exit 0

# chmod 0740 /home/scripts/notif-boot

There does not seem to be any @reboot special field available on OmniOS’ crontab , so I created a service that runs only once at boot time ; based on /lib/svc/manifest/system/zones.xml and /lib/svc/manifest/network/ssh.xml.

# vi /root/notif-boot.xml
<?xml version="1.0"?>
<!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1">
<service_bundle type='manifest' name='notif-boot'>
<!--
        Run a script at system startup.
-->
<service
        name='site/notif-boot'
        type='service'
        version='1'>

        <create_default_instance enabled='false' />

        <single_instance />

        <dependency
                name='multi-user-server'
                type='service'
                grouping='require_all'
                restart_on='none'>
                <service_fmri value='svc:/milestone/multi-user-server' />
        </dependency>

        <exec_method
                type='method'
                name='start'
                exec='/home/scripts/notif-boot'
                timeout_seconds='30'>
        </exec_method>

        <exec_method
                type='method'
                name='stop'
                exec=':true'
                timeout_seconds='30'>
        </exec_method>

        <property_group name='startd' type='framework'>
                <propval name='duration' type='astring' value='transient' />
        </property_group>

        <stability value='Unstable' />
</service>
</service_bundle>

# svccfg validate /root/notif-boot.xml

# svccfg import /root/notif-boot.xml

# svcadm enable svc:/site/notif-boot

# svcs | grep notif
online         18:37:04 svc:/site/notif-boot:default

The validate command works in the “No news is Good news” manner. In case of errors, they will be described. In case of success, the command ends silently.

Should the service fail, svcs -xv can be used to get more information.

Should you want to stop or remove the service, those commands may help:

# svcadm clear svc:/site/notif-boot
# svcadm disable svc:/site/notif-boot
# svccfg delete svc:/site/notif-boot

From there, I get a Pushover notification each time my server boots and I should mount the encrypted ZFS dataset(s).

Mount encrypted ZFS

Mouting the encrypted dataset(s) can be done manually. But I have services that depends on those datasets. So I also have to run a bunch of commands. So I wrote a script that ask for passphrase(s) to mount the encrypted ZFS dataset(s) and start services that rely on data stored there.

The relevant parts of the script that will decrypt the ZFS dataset is:

# vi /home/scripts/afterboot
#!/usr/bin/env sh
PATH="/bin:/usr/bin:/sbin:/usr/sbin"
echo "Loading ZFS encryption key..."
zfs load-key tank/zones
echo "Mounting ZFS dataset(s)..."
zfs mount tank/zones
(...)
exit 0

# chmod 0750 /home/scripts/afterboot

Running /home/scripts/afterboot loads the ZFS encryption key(s) and mounts the encrypted dataset(s).

Bibliography