Proxmox with encrypted ZFS via Tang & Clevis
Table of Contents
Running a Proxmox VE server with encrypted ZFS for data requires decrypting and mounting the ZFS datasets manually after boot.
This can be improved by using a file with random binary data as keyfile. While this approach solves entering password it breaks the intention of encryption as the keyfile will be stored on an unencrypted system. Thus everyone with access to the system can use the keyfile and decrypt the datasets.
This is solved by an approach that uses cryptsetup to store the keyfile on an encrypted volume which is decrypted upon boot by entering a password. As Grub and systemd-boot support cryptsetup the decryption can be initiated directly during boot process and request entering the decryption password. That way the ZFS datasets and their keyfile are stored encrypted while still decrypting everything automatically.
The only problem left with this approach is the manual step required for booting the system by entering the password via IPMI or boot-time-SSH. As I wanted the system to boot without any manual step I developed the setup described on this page. It uses Clevis together with Tang to decrypt the encrypte keyfiles used by ZFS automatically upon boot.
Tang is a server for binding data to network presence. Clevis is a pluggable framework for automated decryption.
The Tang-Clevis decryption process is based on McCallum-Relyea key exchange.
Implementation #
I’m running Tang server on my Turris Omnia router. The following steps describe the client side implementation on my Proxmox VE server.
-
install clevis
apt install clevis
-
export environment variables for simplicity (:!: change to meet your needs :!:)
export keystore="/etc/keystore" # path to store encrypted ZFS keyfiles export tmpdir="/tmp/zfs_clevis" # path to temporary store unencrypted ZFS keyfiles export keyfile="home-user.key" # name of ZFS keyfile
-
prepare helper vairables
export keypath="${keystore}/${keyfile}" export tmp_keypath="${tmpdir}/$(systemd-escape "${keypath}")" export tmpdir_esc="$(systemd-escape --path "${tmpdir}")"
-
create keyfile
mkdir "${keystore}" "${tmpdir}" curl "http://tang:8888/adv" > "${keystore}/tang-adv.jws" mount -t tmpfs -o noatime,nosuid,nodev,noexec,mode=0700 none "${tmpdir}" dd if=/dev/urandom of="${tmp_keypath}" bs=32 count=1 clevis encrypt tang '{"url":"http://tang:8888", "adv": "'"${keystore}"'/tang-adv.jws"}' < "${tmp_keypath}" > "${keypath}"
-
create encrypted dataset
zfs create -o encryption=aes-256-gcm -o keyformat=raw -o keylocation="file://${tmp_keypath}" rpool/home/user
-
create systemd-service to mount ZFS
cat > /etc/systemd/system/zfs-mount-network.service <<EOF [Unit] Description=Mount ZFS filesystems after network Before=pve-guests.service After=network.target ConditionPathIsDirectory=/sys/module/zfs #OnFailure=notify-email@%i.service [Service] Type=oneshot RemainAfterExit=yes ExecStart=/sbin/zfs mount -a [Install] WantedBy=zfs.target RequiredBy=pve-guests.service EOF
(OnFailure can call a service to notify you about errors)
-
create systemd-service to load ZFS keys
cat > /etc/systemd/system/zfs-load-key.service <<EOF [Unit] Description=Load encryption keys DefaultDependencies=false Before=zfs-mount-network.service After=zfs-import.target [Service] Type=oneshot ExecStart=/usr/bin/zfs load-key -a [Install] RequiredBy=zfs-mount-network.service EOF
-
create tmpfs mount unit for decrypted keys
cat > "/etc/systemd/system/${tmpdir_esc}.mount" <<EOF [Unit] PartOf=zfs-load-key.service StopWhenUnneeded=true [Mount] What=tmpfs Where=${tmpdir} Type=tmpfs Options=noatime,nosuid,nodev,noexec,mode=0700 DirectoryMode=0700 [Install] RequiredBy=zfs-load-key.service EOF
-
create systemd-service for keyfile decryption
cat > /etc/systemd/system/clevis-decrypt@.service <<EOF [Unit] Description=Decrypt keyfiles using clevis Before=zfs-load-key.service After=network.target ${tmpdir_esc}.mount Requires=${tmpdir_esc}.mount ConditionPathExists=%I ConditionFileNotEmpty=%I [Service] Type=oneshot RemainAfterExit=no ExecStart=/bin/bash -c "/usr/bin/clevis decrypt tang '{\"url\": \"http://tang:8888\", \"adv\": \"${keystore}/tang-adv.jws\"}' < %I > ${tmpdir}/%J" [Install] RequiredBy=zfs-load-key.service EOF
-
create systemd-unit to start pve-guests manually
cat > /etc/systemd/system/pve-guests-restart.service <<EOF [Unit] Description=Force start of pve-guests Requires=pve-guests.service [Service] Type=oneshot ExecStart=/usr/bin/sleep 1 EOF
-
activate systemd units
systemctl daemon-reload systemctl enable zfs-load-key.service "clevis-decrypt@${tmp_keypath}.service" zfs-mount-network.service
Backup #
To always be able to decrypt your ZFS volumes you need to backup the following information:
- content of
"${keystore}/${keyfile}"
which is encrypted by clevis (example path/etc/keystore/home-user.key
- content of all files in
/var/db/tang/
on tang server
All these information must be backuped synchroniously, meaning that those files need to be from the same time as you cannot decrypt your ZFS otherwise and will loose the data.