Proxmox Full Disk Encryption with SSH Remote Unlock
Although ZFS also offers encryption there is no support for full disk encryption yet so we will use LUKS instead. This is advanced and I assume you know your ways around Linux!
Note: verified to still work with Proxmox VE 9.0 (upgrades and fresh install)
Requirements: ZFS mirror (raid1) install
In order to encrypt a running system rpool must be a ZFS mirror.
To verify you are running a ZFS mirror execute the following command.
zpool status rpool | grep mirror >/dev/null 2>&1 && echo "zfs mirror - continue with the encryption" || echo "not supported, reinstall as zfs (raid1) mirror"
Note: If you don’t want to use ZFS you can also install Debian with LUKS and install Proxmox on top.
LUKS Encryption
Install cryptsetup and run a benchmark to select the best encryption algorithm.
apt install cryptsetup cryptsetup-initramfs
cryptsetup benchmark
I’m going with aes-xts 512b for mixed write and read speed.
aes-cbc 256b could be better suited for read heavy workloads on nvme disks.
In order to start list the disks in your rpool via “zpool status”.
zpool status rpool
I will use /dev/sda3 as example for the first disk and /dev/sdb3 for the second disk.
You will have to use the correct disk printed by zpool which should be in the format /dev/disk/by-id/XXXXXXXX.
Write down both disk names and detach the first disk.
zpool detach rpool /dev/sda3
After that encrypt the disk with LUKS. I’m also hardening brute force attempts by increasing the iteration time to 10 seconds.
Make sure to use a good passphrase.
cryptsetup luksFormat -v -c aes-xts-plain64 -s 512 -h sha512 -i 10000 -y /dev/sda3
Unlock the disk and attach it back to rpool.
cryptsetup luksOpen /dev/sda3 cryptroot1
zpool attach rpool /dev/sdb3 cryptroot1
ZFS will start a resilver which will encrypt all data on cryptroot1.
watch zpool status rpool
Once resilver completes repeat with the second disk.
zpool detach rpool /dev/sdb3
Instead of creating a new LUKS header for the second disk we can simply clone it from the first disk and change the UUID.
dd if=/dev/sda3 of=/dev/sdb3 bs=2M count=1
UUID=$(cat /proc/sys/kernel/random/uuid)
cryptsetup luksUUID --uuid $UUID /dev/sdb3
cryptsetup luksOpen /dev/sdb3 cryptroot2
zpool attach rpool cryptroot1 cryptroot2
Wait again for the resilver to finish.
watch zpool status rpool
Now all that’s left is to create the crypttab mapping.
Print the UUID for both disks and add them to /etc/crypttab.
blkid | grep crypto
cat << 'EOF' > /etc/crypttab
cryptroot1 UUID=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX cryptroot luks,initramfs,keyscript=decrypt_keyctl
cryptroot2 UUID=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX cryptroot luks,initramfs,keyscript=decrypt_keyctl
EOF
Update initramfs to apply the changes.
update-initramfs -u
Note: You can ignore the following message
cryptsetup: ERROR: Couldn't resolve device rpool/ROOT/pve-1
cryptsetup: WARNING: Couldn't determine root device
If you reboot now you will have to enter the passphrase via keyboard.
SSH Remote Unlock
Copy your RSA key to the server for authentication.
If you use Linux you can simply use ssh-copy-id.
ssh-copy-id -i /root/.ssh/id_rsa root@192.168.1.10
The key should be listed in /root/.ssh/authorized_keys now.
Install Dropbear.
apt install dropbear-initramfs
Configure static IP for initramfs.
Make sure to adjust the network interface “enp2s0” and the IP “192.168.1.10” as well as gateway and netmask if needed. You can figure out your network interfaces with “ip a”, note: it’s not vmbr0!
cat << 'EOF' >> /etc/initramfs-tools/initramfs.conf
IP=192.168.1.10::192.168.1.1:255.255.255.0::enp2s0:off
EOF
Copy the RSA key to initramfs.
cp /root/.ssh/authorized_keys /etc/dropbear/initramfs/authorized_keys
chmod 600 /etc/dropbear/initramfs/authorized_keys
Apply changes and reboot.
update-initramfs -u
reboot
Note: You can ignore the following messages
cryptsetup: ERROR: Couldn't resolve device rpool/ROOT/pve-1
cryptsetup: WARNING: Couldn't determine root device
cryptsetup: ERROR: cryptroot1: Source mismatch
cryptsetup: ERROR: cryptroot2: Source mismatch
To decrypt the disks connect via SSH and execute “cryptroot-unlock”
cryptroot-unlock
Enter the passphrase and the server should continue to boot.
If you increased the iteration time you might get a timeout error message but the server will boot shortly after as long as the passphrase is correct.
Securely Delete Leftovers
There will still be unencrypted leftovers on the disks.
In order to securely delete any leftovers fill the disks with random data.
apt install pv
cat /dev/urandom | pv > /tmp/fill.tmp || rm /tmp/fill.tmp
SSD TRIM
If your rpool is installed on SSD’s you can enable TRIM for better performance.
First make sure TRIM is supported. “Disc-Gran” should not be zero.
lsblk --discard | grep sda3
Add “discard” to /etc/crypttab options.
cryptroot1 UUID=XXXX cryptroot luks,discard,...
cryptroot2 UUID=XXXX cryptroot luks,discard,...
Apply change and reboot.
update-initramfs -u
reboot
dmsetup should print “allow_discards” if it is enabled.
dmsetup table | grep allow_discards
Setup a cron job to trim rpool once a week.
cat << 'EOF' > /etc/cron.d/zfs-trim
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
# trim rpool every sunday at 4am
0 4 * * 7 root /usr/sbin/zpool trim rpool
EOF
chmod +x /etc/cron.d/zfs-trim
You can verify the trim status with.
watch zpool status -t
Header Backup
It’s recommended to create a LUKS header dump for disaster recovery.
With the header the whole disk can be decrypted without the passphrase!
Make sure to store it somewhere safe e.g. give it to a friend.
cryptsetup luksHeaderBackup /dev/sda3 --header-backup-file /mnt/usbstick/headerbackup
Disk Replacement
If you have a disk failing, replace it with a new one. The new disk must be bigger or exactly the same size as the old one. Note that different disks can have different byte sizes, so replacing a 4TB disk with a 4TB disk could still fail if it’s some bytes short, it’s best to replace the failing disk with an identical one.
You can check the status with “zpool status”, in this example a failing disk has already been replaced and the root pool is currently degraded e.g. booted from the single remaining disk
zpool status
NAME STATE READ WRITE CKSUM
rpool DEGRADED 0 0 0
mirror-0 DEGRADED 0 0 0
cryptroot1 ONLINE 0 0 0
1046765708608951588 UNAVAIL 0 0 0 was /dev/mapper/cryptroot2
note: if your pool becomes degraded due to read/write/cksum errors randomly you can clear them using “zpool clear rpool” and do a scrub using “zpool scrub rpool” to autoheal, if the problem does not go away replace the disk. The problem could also be related to a bad HBA, Motherboard fault or cabling issue.
The newly added disk is /dev/sdb and is not partitioned yet. lsblk shows that it has no partitions.
lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
sda 8:0 0 256G 0 disk
├─sda1 8:1 0 1007K 0 part
├─sda2 8:2 0 1G 0 part
└─sda3 8:3 0 255G 0 part
└─cryptroot1 252:0 0 255G 0 crypt
sdb 8:16 0 256G 0 disk
We need to setup /dev/sdb so that it’s bootable and then encrypt it and add it back to the zfs root pool.
If you had something on the disk clean it first, this will remove any existing partitions.
wipefs -a /dev/sdb
Now we copy the partition layout from the working drive /dev/sda on to it, randomize the uuids and add it to the proxmox esp boot sync tool.
sgdisk /dev/sda -R /dev/sdb
sgdisk -G /dev/sdb
proxmox-boot-tool format /dev/sdb2
proxmox-boot-tool init /dev/sdb2
proxmox-boot-tool refresh
proxmox-boot-tool will warn you about the removed faulty disk, e.g.
WARN: /dev/disk/by-uuid/B9A7-C968 does not exist - clean '/etc/kernel/proxmox-boot-uuids'! - skipping
remove the line B9A7-C968 from /etc/kernel/proxmox-boot-uuids
If you get the message
/dev/disk/by-uuid/E3A6-F728 contains no grub directory - skipping
you have a grub install and not systemd-boot, in that case you have to run this instead:
proxmox-boot-tool format /dev/sdb2
proxmox-boot-tool init /dev/sdb2 grub
proxmox-boot-tool refresh
You should not see any errors / warnings anymore, the boot loader is successfully setup.
Now it’s time to add the disk back to rpool and update crypttab, essentially we repeat the steps in the original setup from above but this time we replace the faulty disk instead of attaching it.
dd if=/dev/sda3 of=/dev/sdb3 bs=2M count=1
UUID=$(cat /proc/sys/kernel/random/uuid)
cryptsetup luksUUID --uuid $UUID /dev/sdb3
cryptsetup luksOpen /dev/sdb3 cryptroot2
zpool replace rpool 1046765708608951588 cryptroot2
Wait for the resilver to finish, since there is a lot of data this time it can take a couple of hours!
watch zpool status rpool
Now all that’s left is to update the UUID in crypttab, replace the old UUID for cryproot2 with the new one.
blkid | grep crypto
cat << 'EOF' > /etc/crypttab
cryptroot1 UUID=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX cryptroot luks,initramfs,keyscript=decrypt_keyctl
cryptroot2 UUID=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX cryptroot luks,initramfs,keyscript=decrypt_keyctl
EOF
Update initramfs to apply the changes.
update-initramfs -u
Note: You can ignore the following message
cryptsetup: ERROR: Couldn't resolve device rpool/ROOT/pve-1
cryptsetup: WARNING: Couldn't determine root device
That’s it, you can reboot now optionally to test if everything worked, removing any disk should still let the system boot.
Comments