User:Liassica/Secure Arch Setup
Acknowledgments
This guide is primarily a synthesis of information from these sources:
- https://wiki.archlinux.org/title/User:M0p/LUKS_Root_on_Btrfs
- https://wiki.archlinux.org/title/Dm-crypt/Encrypting_an_entire_system#Simple_encrypted_root_with_TPM2_and_Secure_Boot
- https://0pointer.net/blog/authenticated-boot-and-disk-encryption-on-linux.html
Overview
This guide makes use of secure boot, unified kernel images, dm-crypt, Btrfs, and systemd-homed to create a secure and robust system.
Pre-installation
Follow the installation guide and stop before partitioning the disks. Ensure that your system is booted in 64-bit UEFI mode, otherwise this guide will not work.
Partition the disks
Create the following GPT layout using your chosen partitioning tool. GPT fdisk is recommended.
Mount point | Partition | Partition type (GUID) | Partition label | Suggested size |
---|---|---|---|---|
/mnt/efi
|
/dev/efi_system_partition
|
EFI system partition (ef00) | ESP | 300M-1G, depending on the number of EFI binaries you plan to keep in it |
/mnt
|
/dev/root_partition
|
Linux x86-64 root (/) (8304) | CRYPTROOT | At least 10G, more depending on the amount of software you plan to install. 50G is a safe bet if you don't want to worry about it. |
/mnt/home
|
/dev/home_partition
|
Linux x86-64 home (/home) (8302) | HOME | Remainder of the device |
Format the partitions
Encrypted root
Since we are using unified kernel images, /boot
does not have to be accessible at startup, and therefore the root partition will be LUKS2 encrypted. The encryption unlocking can optionally be keyed to a TPM device, in which case the encryption password can be left blank.
Create the dm-crypt container:
# cryptsetup luksFormat /dev/disk/by-partlabel/CRYPTROOT
Next, open the encrypted container:
# cryptsetup open /dev/disk/by-partlabel/CRYPTROOT root
Finally, format the mapped device with Btrfs:
# mkfs.btrfs -L Root /dev/mapper/root
Home partition
The home partition itself will be left unencrypted as to avoid double encrypting user home directories created with systemd-homed.
Format the home partition as Btrfs:
# mkfs.btrfs -L Home /dev/disk/by-partlabel/HOME
EFI System Partition
As per the UEFI specification, the EFI System Partition must be FAT32:
# mkfs.fat -F 32 -n ESP /dev/disk/by-partlabel/ESP
Btrfs subvolume layout
We will use a Btrfs subvolume expanding upon Snapper#Suggested filesystem layout.
First, mount the whole root container:
# mount /dev/mapper/root /mnt # cd /mnt
Create the top-level subvolumes:
# btrfs subvolume create @ # for i in {snapshots,root,srv,usr,usr/local,swap,var,opt}; do btrfs subvolume create @$i; done # for i in {tmp,spool,log,cache,crash,opt}; do btrfs subvolume create @var/$i; done
The effect of this is we get a flat layout like this:
Subvolume | Moint point | Contains | Reason |
---|---|---|---|
@ |
/ |
/etc , /var , /usr , /boot |
Include in snapshots |
@snapshots |
/.snapshots |
Snapshots of /
|
Avoid rollbacks |
@root |
/root |
Root user files | |
@srv |
/srv |
Server files | |
@swap |
/swap |
Swap files | |
@usr/local |
/usr/local |
Locally installed software | |
@opt |
/opt |
Third-party software | |
@var/log |
/var/log |
System logs | |
@var/opt |
/var/opt |
Third-party data | |
@var/tmp |
/var/tmp |
Persistent temporary data | Exclude from snapshots |
@var/spool |
/var/spool |
Spool data | |
@var/cache |
/var/cache |
Cached data | |
@var/crash |
/var/crash |
Crash data |
You may consider adding other paths to the @var subvolume, such as lib/libvirt/images
(libvirt), lib/docker
(Docker), or lib/flatpak
(Flatpak):
Subvolume | Moint point | Contains | Reason |
---|---|---|---|
@var/lib/libvirt/images |
/var/lib/libvirt/images |
Virtual machine images | Avoid rollbacks |
@var/lib/docker |
/var/lib/docker |
Docker containers | |
@var/lib/flatpak |
/var/lib/flatpak |
Flatpak apps |
Finally, unmount the root container:
# cd ~ # umount /mnt
Mount the file systems
Mount the Btrfs subvolumes
Mount root subvolume:
# mount /dev/mapper/root /mnt -o subvol=@,compress-force=zstd,noatime
Mount snapshots subvolume:
# mount --mkdir /dev/mapper/root /mnt/.snapshots/ -o subvol=@snapshots,compress-force=zstd,noatime
Mount other top level subvolumes:
# for i in {root,srv,swap,usr/local,opt}; do mount --mkdir /dev/mapper/root /mnt/$i -o subvol=@$i,compress-force=zstd,noatime; done
Mount var subvolumes:
# for i in {tmp,spool,log,cache,crash,opt}; do mount --mkdir /dev/mapper/root /mnt/var/$i -o subvol=@var/$i,compress-force=zstd,noatime; done
For certain directories, you should disable copy-on-write:
# chattr +C /mnt/swap # chattr +C /mnt/var/lib/libvirt/images
Mount ESP and /home
Mount the ESP:
# mount --mkdir LABEL=ESP /mnt/efi
Mount the home partition:
# mount --mkdir LABEL=Home /mnt/home -o compress-force=zstd,noatime
Installation
Install essential packages
Install the packages necessary to get this setup working:
# pacstrap -K /mnt base linux linux-firmware nano cryptsetup btrfs-progs efibootmgr amd-ucode
Replace linux
, nano
, and amd-ucode
with your preferred kernel, text editor, and microcode, respectively.
Configuration
Fstab
Generate an fstab file:
# genfstab -t PARTUUID /mnt >> /mnt/etc/fstab
In the generated fstab file, remove the subvolid=id
options from every btrfs subvolume mount point. Additionally, remove the subvol=@
option from the subvolume mounted at /
so that it mounts the default subvolume set by snapper instead. Optionally, you may comment out or delete the entries for the ESP and home partition since systemd will automount them. However, keeping the home partition entry may be helpful for e.g. setting compression or permissions options.
subvolid=id
options at once:
# sed -i 's/subvolid=[0-9]*,//g' /mnt/etc/fstab
subvol=@
must still be explicitly removed from the /
mount point entry.
Chroot
Change root into the new system:
# arch-chroot /mnt
General configuration
Resume following the installation guide and stop before configuring the initramfs.
Initramfs
We will configure mkinitcpio to create unified kernel images.
First, edit the mkinicpio configuration to use a systemd based startup:
/etc/mkinitcpio.conf
... BINARIES=(/usr/bin/btrfs) ... HOOKS=(base systemd autodetect modconf kms keyboard sd-vconsole block sd-encrypt filesystems fsck) ...
If desired, kernel command line arguments can be added to .conf
files in /etc/cmdline.d
.
For example, this guide does not use a swap partition, so if you want to use zram instead, disable zswap:
/etc/cmdline.d/zswap.conf
zswap.enabled=0
Next, edit the preset file for your chosen kernel (e.g /etc/mkinitcpio.d/linux.preset
for the standard Linux kernel) and make the following changes:
- Un-comment the
[PRESET]_uki=
parameter for each item inPRESETS=
- Optionally, comment out
[PRESET]_image=
to avoid storing a redundantinitramfs-*.img
file - Optionally, un-comment or add the
--splash
parameter to each[PRESET]_options=
line for which you want to add a splash image
You can then remove the initramfs files from /boot
:
# rm /boot/initramfs-*.img
Root password
Set the root password:
# passwd
Boot loader
efibootmgr can be used to add UKIs directly the UEFI menu:
# efibootmgr --create --disk /dev/disk --part partnum --label "Arch Linux" --loader 'EFI\Linux\arch-linux.efi' --unicode
However, it may be advantageous to use systemd-boot instead for automatic detection of UKIs and an interactive boot menu. The downside is there is one more EFI binary to sign for secure boot, potentially increasing attack surface. It is worth noting, however, that unlike GRUB, systemd-boot will only boot other secure boot signed EFI binaries.
To install systemd-boot:
# bootctl install
Finally, generate the UKIs with mkinitcpio:
# mkinitcpio -P
Snapper
The Btrfs layout we set up earlier facilitates easy recovery of root when used in conjunction with Snapper.
First, install relevant snapper packages:
# pacman -S snapper snap-pac
Set up a snapper configuration for the root file system:
# umount /.snapshots/ # rmdir /.snapshots/ # snapper --no-dbus -c root create-config / # btrfs subvolume delete /.snapshots/ # mkdir /.snapshots/ # chmod 750 /.snapshots/ # mount /.snapshots/
Next, enable the various snapper timers:
# systemctl enable /lib/systemd/system/snapper-*
Finally, make sure to set the default subvolume so that root will properly mount
Find out the id of the @
subvolume (likely 256 since we created it first):
# btrfs subvolume list -p /
Set it as the default:
# btrfs subvolume set-default @_subvolid /
Reboot
Exit the chroot environment by typing exit
or pressing Ctrl+d
.
Unmount the mounted partitions and close the cryptsetup container:
# umount -R /mnt # cryptsetup close root
Finally, reboot into UEFI in preparation to set up secure boot:
# systemctl reboot --firmware-setup
Post-installation
Secure boot
Setup
In the UEFI, go into the secure boot settings and enter setup mode. See Unified Extensible Firmware Interface/Secure Boot#Putting firmware in "Setup Mode" for how to do so. After secure boot setup mode is enabled, continue into the new system.
Install sbctl, a tool for managing secure boot:
# pacman -S sbctl
Check secure boot status:
# sbctl status
You should see that setup mode is enabled, and secure boot is disabled.
Creating and enrolling keys
Create custom secure boot keys:
# sbctl create-keys
Enroll your keys and Microsoft's to the UEFI:
# sbctl enroll-keys -m
-m
.Signing
Check which files need to be signed:
# sbctl verify
This will include the UKIs and systemd-boot if you installed it. Sign each file with sbctl sign -s /path/to/file
.
Finally, reboot into the UEFI again and turn on secure boot (it may have been turned on automatically when the new keys were enrolled). One final sbctl status
should verify if secure boot is working.
sbctl includes a pacman hook to automatically sign new UKIs when they are generated, however it also generates the UKIs which we do not want.
Copy the hook to the local configuration:
# cp /usr/share/libalpm/hooks/zz-sbctl.hook /etc/pacman.d/hooks/
Edit the hook and remove -g
from the Exec =
line. This will let mkinitcpio handle UKI generation, as the sbctl documentation recommends. [2]
When manually generating UKIs, make sure to sign them before use. Alternatively, you can use a mkinitcpio post hook to automatically sign any new UKIs generated:
/etc/initcpio/post/uki-sign
#!/usr/bin/env bash sbctl sign -s "$3"
Make the file executable:
# chmod +x /etc/initcpio/post/uki-sign
TPM Enrollment
The unlocking of the root partition can be keyed to the TPM, potentially adding security and bypassing the need for entering the decryption password, if so desired.
Generate a recovery key:
# systemd-cryptenroll /dev/disk/by-partlabel/CRYPTROOT --recovery-key
Enroll the TPM:
# systemd-cryptenroll /dev/disk/by-partlabel/CRYPTROOT --wipe-slot=empty --tpm2-device=auto
You can use the --tpm2-with-pin=yes
option if you wish to retain a passphrase needed for unlocking.
Users
Normal users (i.e. non-root and non-system) will be managed with systemd-homed.
First, enable and start systemd-homed:
# systemctl enable --now systemd-homed
To create a user in the wheel group (for use with sudo, etc.):
# homectl create --storage=luks --member-of=wheel user
homectl will prompt for a password for the new user and create the user.home
loop file in /home
. The benefit of using systemd-homed for users is that no user, not even root, can access a user's home directory while they are logged out without knowing that user's password. Additionally, users can be moved between systems in certain cases, adding a degree of portability.
Other configuration
See General recommendations for other post-installation setup. Particularly, one may be interested in Sudo, Security, and Zram or Btrfs#Swap file.