Encrypted Boot Without Nix
In the past I’ve done an encrypted boot setup with NixOS for my server, but now I’d like to also harden my laptop since, out of the two computers, that’s the more vulnerable one traveling with me and being out in public spaces. But my laptop needs to work with audio and visual media applications, hardware interfaces, webcams, etc. I’m still more comfortable debugging/setting up that stuff on Arch than NixOS, so now I get to dive into how to do all that LVM-on-LUKS configuration manually. It’s actually not that complicated, just a few moving parts that need to be coordinated, but the NixOS config is definitely way more convenient.
Also, beware of being led astray by troubleshooting threads for LUKS-on-LVM or LUKS-only systems, which somehow seem more common than my setup in this article. I was using root=/dev/mapper/vg-root
in the kernel parameters with different combinations of cryptkey
paths and cryptdevice
names wondering why it wasn’t working.
Justification
The main features I want from this setup are the following:
- Boot as encrypted as possible (so the images, but not the boot entries)
- Password input only once
- A semblance of rollback-ability akin to NixOS
As far as I can tell, the simplest way (or one of the simplest ways) of doing this is installing to BTRFS subvolumes for root and home, inside an LVM volume peer with one for swap, on top of a LUKS encrypted partition matched with an unencrypted partition for EFI. systemd-boot can’t deal with initramfs images and boot entries on separate partitions, so I’m using GRUB, which also forces me to use LUKS1. I think those are the only caveats to accomplishing the first two points. BTRFS will accomplish the third point its snapshots feature. This can be done just before a pacman -Syu
to provide a point to revert back to if anything goes wrong, in the worst case by a live USB. This is strictly worse than NixOS’ system generations, but it seems like an OK trade-off for the flexibility of modifying the system on the fly.
The Process
Booting the Arch Linux live image, the first thing I did was connect to the internet. Arch provides iwd
, but I always forget how to use it since I usually prefer Network Manager, so just a reminder:
# iwctl
[iwd]# station <name like wlan0> scan
[iwd]# station <name> get-networks
[iwd]# station <name> connect <ssid>
scan
and get-networks
can be skipped by tab-completing station <name> connect
.
Partition and Format the Disks
Next, I partitioned the hard drive with gdisk
. EFI partition first, with default start, size 550Mb
, and type ef00
. Then for the main partition, it can take up the rest of the drive, which is the default, and should be type 8300
, also default.
I generated the key file in the same way as before. The name can be anything, though Arch has a naming convention and a particular spot, /etc/cryptsetup-keys.d/
, to store the key. Actually, a lot of this is the same as last time, but I’ll briefly reproduce it here.
# dd if=/dev/urandom of=./root.key bs=1024 count=4
# cryptsetup luksFormat --type luks1 -c aes-xts-plain64 -s 256 -h sha512 /dev/${DISK}2
# cryptsetup luksAddKey /dev/${DISK}2 root.key
# cryptsetup luksOpen /dev/${DISK}2 root --key-file root.key
# pvcreate /dev/mapper/root
# vgcreate vg /dev/mapper/root
# lvcreate -L 8G -n swap vg
# lvcreate -l '100%FREE' -n root vg
# mkswap /dev/mapper/vg-swap
# swapon /dev/mapper/vg-swap
# mkfs.vfat -n boot /dev/${DISK}1
# mkfs.btrfs -L root /dev/mapper/vg-root
# mount /dev/mapper/vg-root /mnt
Then I created the subvolumes and re-mounted so the new rootfs
subvolume is the root of the eventual Arch system.
# cd /mnt
# btrfs subvolume create rootfs
# btrfs subvolume create homefs
# cd ..
# umount /mnt
# mount -o subvol=rootfs /dev/mapper/root /mnt
Normal Setup
After that I proceeded as normal to assemble the other mountpoints and followed the installation guide, picking up from the pacstrap
step.
# mkdir /mnt/home
# mount -o subvol=homefs /dev/mapper/root /mnt/home
# mkdir /mnt/efi
# mount /dev/sda1 /mnt/efi
# mkdir /mnt/boot
# pacstrap -K /mnt base base-devel linux linux-firmware intel-ucode less man-db man-pages lvm2 btrfs-progs grub efibootmgr vim networkmanager
# cp root.key /mnt/etc/cryptsetup-keys.d
I called genfstab
as normal in the guide, but I forgot to mount /efi
with the proper permissions, so I edited the fstab
at this stage to add the options uid=0,gid=0,
and change fmask=0022,dmask=0022
to 0070
each.
The rest of the configuration and setup was the same as in the guide, until the end with the initramfs and boot loader configs.
Initramfs and Boot Loader
arch-chroot
‘d into /mnt
, I changed the FILES
and HOOKS
lines in /etc/mkinitcpio.conf
to the following below. For this setup, LVM-on-LUKS, encrypt
should come before lvm2
to decrypt the disk.
FILES=(/etc/cryptsetup-keys.d/root.key)
HOOKS=(base udev autodetect microcode modconf kms keyboard keymap consolefont block encrypt lvm2 filesystems fsck)
Next, I changed the following lines in /etc/default/grub
:
GRUB_CMDLINE_LINUX="cryptdevice=UUID=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX:root:allow-discards cryptkey=rootfs:/etc/cryptsetup-keys.d/root.key root=/dev/vg/root rootflags=subvol=rootfs rw intel-ucode.img"
GRUB_ENABLE_CRYPTODISK=y
cryptdevice
: colon-separated list of arguments; first the disk partition (use a persistent name like UUID), then the name of the decrypted partition, then disk options (allow-discards
enables TRIM)cryptkey
: path to the key file of thecryptdevice
in the format<disk>:<path>
;rootfs
refers to the initramfs image, so in this case the path should be the same as in theFILES
line in/etc/mkinitcpio.conf
root
: the path of the mapped volume after LVM is activated; since I used a volume group it must be specified in the pathrootflags
: any flags that would be passed tomount
manually; in this case I specified the subvolumerootfs
rw
: kernel-default read-write mode for the root deviceintel-ucode.img
: additional cpu microcode image
When in doubt, the wiki pages for device encryption and encrypted system configuration are authoritative.
Then I generated the initramfs image, configured GRUB, and set the root password:
# mkinitcpio -P
# grub-install --target=x86_64-efi --efi-directory=/efi --bootloader-id=GRUB
# grub-mkconfig -o /boot/grub/grub.cfg
# passwd
Finish and Reboot
With the installation complete, I rebooted into the new system and finished setting it up. I’ll leave that for another time, as many people have different preferences, but if you’re totally new I’d start with the general recommendations.