This is a step-by-step guide to set up an Ubuntu server with the root partition on btrfs raid1, encrypted with luks, and automatically unlocking on boot via a Mandos server running locally.
I wanted to do this on a home server because raid and encryption makes me sleep better, but neither do I want to have to type the LUKS password every time I need to reboot the machine. There are guides out there for each part of this, but not together, which unfortunately, made setting this up more of a pain than I anticipated. Hopefully, after reading this, it should be a breeze for others.
What we want and prerequisites
We have a server with two disks in it (or two SSD-s) that are the same size
(/dev/sda
and /dev/sdb
later on) and UEFI boot. We want to encrypt both
disks, put a btrfs
raid1 filesystem on them and then install an Ubuntu server
on this. We also want to be able to reboot this server without needing to type
our password anywhere, so we will install a mandos
client on it. We will also
need another machine that will act as our mandos server (I used a rasberry
running Ubuntu for this). We will also need an Ubuntu Live USB (for the record,
I used one with jammy).
Theory
I’m by no means an expert, and what I’m writing here might be wrong in many ways, but this was in my head by the time I finished, and thinking about it this way helped me troubleshoot.
btrfs raid1 with encryption
Btrfs doesn’t know how to do this on it’s own. So we need to encrypt
a partition on each disk with luks first, unlock the encryption on both and
then use btrfs to install a raid1 on the unlocked partitions. The raid1
“partition” will receive a UUID and we need to mount root (/
) on this UUID
and not either of the underlying partitions. Also, the Ubuntu installer doesn’t
know how to install itself on this raid1, which will bring some fun.
boot order and partitioning
First thing to turn on is the BIOS, which looks for a FAT32 partition marked as
EFI. In our setup this partition needs to have grub
installed on it, which
will take over from the BIOS. This EFI partition doesn’t have anything to do
really with how we mount things on Linux, but often it is mounted under
/boot/efi
and when we update-grub
this is where we are putting files. The
actual efi stuff is placed in a folder called EFI
(so when this FAT32
partitions is mounted under /boot/efi
the efi stuff will be in
/boot/efi/EFI
). In my experience if the partition marked as EFI has other
things in it or EFI is in a subfolder on the partition BIOS still finds it. On
the other hand, do-release-upgrade
in Ubuntu will complain for some reason,
so the best is to stick to the above.
Next the initial ram filesystem is loaded into memory (the initramfs
). This
actually feels like a mini-linux, with a busybox
shell if you ever happen to
get stuck here. This is also what can bring up initial networking and where we
can install the mandos client. Since we need to contact the mandos server via
the client before we can unlock the encrypted disks, the initramfs
images
cannot be encrypted. This is what is mounted under /boot
. Technically,
/boot
and /boot/efi
could all be on the same FAT32 partition, but as
I mentioned do-release-upgrade
does not like this for some reason, and also
FAT32 can’t do symlinks and update-initramfs
by default makes some, so it’s
better to create a separate ext4
or similars partition for /boot
.
Setup
Boot the machine from the live USB. I will assume the other machine that will be the mandos server is already up and running on the local network with an Ubuntu server on it.
Partitioning
Since we want to do raid1, both disks should be partitioned the same way (using
gparted
for this should be the easiest):
/dev/sda1
: 512 MB FAT32/dev/sda2
: 2 GB ext4/dev/sda3
: optional swap partition/dev/sda4
: leave the rest for encryption and raid
Repeat for /dev/sdb
.
The first 3 partitions will NOT go into raid and the boot process will only be
reading from one of the disks and two separate swap partitions will be
available to mount. Everthing under /boot
can easily be regenerate, but rsync
could be used to keep the other disk in sync, making a switch easier if it
would be needed. The swap can also be encrypted, but I didn’t bother (yet) and
there should be ample guides for doing that anyway.
Encryption and raid
We create two encrypted partitions. Use the same password for both. Also, if something goes wrong, you’ll be unlocking the disks in initramfs which loads an English keyboard layout, so make sure you know how to type it if your usual layout is different:
cryptsetup luksFormat --type=luks1 /dev/sda4
cryptsetup luksFormat --type=luks1 /dev/sdb4
Let’s unlock the encrypted disks and name them:
cryptsetup open /dev/sda4 cryptroot1
cryptsetup open /dev/sdb4 cryptroot2
Finally, we create the btrfs raid:
mkfs.btrfs -m raid1 -d raid1 /dev/mapper/cryptroot1 /dev/mapper/cryptroot2 -L btrfsroot
Note the UUID for the raid, but you can always get it with sudo btrfs filesystem show
:
❯ sudo btrfs filesystem show
Label: 'btrfsroot' uuid: a490e9ca-2ceb-48eb-8656-e4c311495ace
Total devices 2 FS bytes used 93.09GiB
devid 1 size 229.97GiB used 96.01GiB path /dev/mapper/cryptroot1
devid 2 size 229.97GiB used 96.01GiB path /dev/mapper/cryptroot2
Installing Ubuntu
Since the live ubuntu’s installer won’t be able to do this for us, we’ll be doing the install with debootstrap
and chroot
.
First, let’s do some mounting. We will reproduce the partition mountings we
discussed above under /mnt
and we also need to mount a special efivarfs
that seems to explain to linux how to configure the EFI partition properly.
(Note, I’m writing this from notes and memory, you may or may not need sudo here):
# change the UUID to what your btrfs raid is
sudo mount UUID=a490e9ca-2ceb-48eb-8656-e4c311495ace /mnt
sudo mount /dev/sda2 /mnt/boot
sudo mount /dev/sda1 /mnt/boot/efi
sudo mount -t efivarfs efivarfs /mnt/sys/firmware/efi/efivars
Now we install a very minimal Ubuntu. Unfortunately, there was no noble
in
deboostrap for me, which might have been because of the older Ubuntu:
sudo debootstrap jammy /mnt
Setting up Ubuntu
Next we do some more mounting, so we can chroot
into our new install:
for dir in /dev /proc /sys /run; do sudo mount --bind $dir /mnt$dir; done
sudo chroot /mnt
Once in, we are going to finish the setup:
echo "mandosclient" > /etc/hostname
locale-gen en_US.UTF-8
apt update
apt install linux-image-generic linux-headers-generic grub-efi-amd64 btrfs-progs cryptsetup lvm2 initramfs-tools systemd-sysv network-manager keyutils
grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=ubuntu --recheck
update-grub
apt install ubuntu-server # debootstrap install a very minimal system
We also make sure that our crypt setup sticks, so we edit /etc/crypttab
:
# <target name> <source device> <key file> <options>
cryptroot1 UUID=e89263d6-aa19-4f45-b16c-5f08186aa70d none luks,discard,keyscript=/usr/lib/cryptsetup/scripts/decrypt_keyctl
cryptroot2 UUID=94281c39-5172-41fc-92e0-ec7bc47b9b83 none luks,discard,keyscript=/usr/lib/cryptsetup/scripts/decrypt_keyctl
The above UUID-s should be the UUID of your /dev/sda4
and /dev/sdb4
respectively (use blkid
to get them if needed). The keyscript we added caches
the first decrypt password entered during boot and tries to apply to all other
encrypted partitions. This is useful already, but with mandos it is absolutely
necessary since mandos will only provide a single password, so the second disk
can only be unlocked if we cache the password (which is also why we needed to
use the same password for both disks).
As a reference this is how /boot
looked for me at this point:
❯ ls /boot
System.map-5.15.0-140-generic config-5.15.0-140-generic efi initrd.img-5.15.0-140-generic vmlinuz-5.15.0-140-generic
System.map-6.8.0-60-generic config-6.8.0-60-generic grub initrd.img-6.8.0-60-generic vmlinuz-6.8.0-60-generic
❯ tree /boot/efi
/boot/efi
└── EFI
├── BOOT
│ └── BOOTX64.EFI
└── ubuntu
├── grub.cfg
└── grubx64.efi
If we rebooted now, we would be prompted once for the luks password, and we should be booted into a functioning Ubuntu on encrypted btrfs raid. The next part will require us to produce a configuration file on this machine which will include a long encryption key and transfer it to the machine that will be the mandos server. This might be easier after a reboot, but I guess you can install sshd on the live Ubuntu or ssh from the live Ubuntu to the mandos server.
Set up mandos client
Install the mandos client:
sudo apt install mandos-client
Unfortunately, there’s a bug in the package, so we need to edit /usr/share/initramfs-tools/hooks/mandos
. Find the line
libgpgme11_version="`dpkg-query --showformat='${Version}' --show libgpgme11`"
and add the star at the end like so:
libgpgme11_version="`dpkg-query --showformat='${Version}' --show libgpgme11*`"
otherwise, you will see this error on booting:
Mandos plugin mandos-client: bad_gpgme_op_decrypt gnupg no secret key
We now generate the config, and copy the output to the mandos-server (put it in a file somewhere for the time being).
sudo mandos-keygen --password --type RSA --force
Finally, we regenerate the initramfs
:
sudo update-initramfs -u -k all
Configure the mandos server
Go over to the server that will act as the mandos-server and install mandos.
sudo apt install mandos
Now put the output we generated on the client at the end of /etc/mandos/clients.conf
.
It should look something like this (I deleted most of the values):
[mandosclient]
host = mandosclient
key_id =
fingerprint =
secret =
checker = ssh-keyscan -t ecdsa-sha2-nistp256 %%(host)s 2>/dev/null | grep --fixed-strings --line-regexp --quiet --regexp=%%(host)s" %(ssh_fingerprint)s"
ssh_fingerprint =
There’s only one thing you need to change: I found that the host was not found reliably this way, which is a problem for the checker (if that fails for long enough the server will refuse to give out the password), so I changed that value to the actuall IP of the client machine. Also note that although you can set up mandos differently, I’m assuming server and client are on the same local network.
After the setup, we enable all clients:
sudo mandos-ctl --enable --all
Then switch back to the client machine and test it:
/usr/lib/x86_64-linux-gnu/mandos/plugins.d/mandos-client --pubkey=/etc/keys/mandos/pubkey.txt --seckey=/etc/keys/mandos/seckey.txt --debug --tls-privkey=/etc/keys/mandos/tls-privkey.pem --tls-pubkey=/etc/keys/mandos/tls-pubkey.pem; echo
If things looks happy, reboot and rejoice as your server is now being unlocked automatically. Word of warning: although it’s possible to set it up, the initramfs by default does NOT have wifi, so the client machine should be hooked up to ethernet.
Set up dropbear for backup
dropbear is a small ssh server which can be installed in the initramfs and connected to over ssh to unlock the encrypted partitions over ssh should mandos fail:
sudo apt install dropbear-initramfs
sudo vim /etc/dropbear/initramfs/dropbear.conf # check the settings
sudo cp ~/.ssh/authorized_keys /etc/dropbear/initramfs/ # these keys will work now during boot as well
sudo update-initramfs -u -k all
Troubleshooting
If you are dropped in initramfs, just unlock the encrypted partitions, exit and boot should continue:
cryptsetup --verbose luksOpen /dev/sda4 /cryptroot1
cryptsetup --verbose luksOpen /dev/sdb4 /cryptroot2
exit
Some other notes
- I needed reinstall
keyutils
package at some point, when the keyscript wasn’t working - there’s no wifi during boot
- you can always
chroot
from the live USB again to fix things