Alpine Linux with Full Disk Encryption
I recently built a file server. For the operating system, I decided to use Alpine Linux. As with most of my Linux systems, I wanted to utilize full disk encryption. The following guide is mostly based off the documentation on the Alpine Wiki, and goes through the installation of Alpine on a modern UEFI system, with LUKS full disk encryption.
- Michael Siegel has created a guide for Installing Alpine Linux with full disk encryption on BIOS/MBR systems with a custom partition layout.
- In the interest of helping people update other documentation, the text and code in this article (and only this article) are licensed under CC0.
After booting from the Alpine USB installation media, I used ifconfig eth0 up
and udhcpc eth0
to bring up a network connection. I used setup-apkrepos
to configure the package manager. I then installed the following tools, needed for setting up encrypted disks and logical volumes.
apk update apk add gptfdisk cryptsetup lvm2 e2fsprogs util-linux dosfstools
Next I partitioned the disk. If your system is using an NVME device, the primary device will look like /dev/nvme0n1
and the individual partitions will be /dev/nvme0n1p1
and /dev/nvme0n1p2
. For systems using SATA devices, your primary device will likely be /dev/sda
and the partitions will be /dev/sda1
and /dev/sda2
. For the rest of this guide, I assumed an NVME device as the primary boot drive. If you’re using SATA devices, adjust the device names appropriately. To create a GPT partition, I used the gdisk
command installed from the gptfdisk
package.
# gdisk /dev/nvme0n1 Command (? for help): p Disk /dev/nvme0n1: 1000215216 sectors, 476.9 GiB Model: SK hynix PC300 HFS512GD9MND-5510A Sector size (logical/physical): 512/512 bytes Disk identifier (GUID): 1C949023-866B-4764-B397-252D5125277C Partition table holds up to 128 entries Main partition table begins at sector 2 and ends at sector 33 First usable sector is 34, last usable sector is 1000215182 Partitions will be aligned on 2048-sector boundaries Total free space is 2014 sectors (1007.0 KiB) Number Start (sector) End (sector) Size Code Name 1 2048 104447 50.0 MiB EF00 EFI system partition 2 104448 1000215182 476.9 GiB 8300 Linux filesystem
I created a small EFI System Partition (ESP) of 50MB. This doesn’t need to be very large, as it will only hold the GRUB EFI executable used to boot the system (and possible other boot loaders as well if you choose to dual boot Windows or another operating system). Be careful not to make the partition too small or else you could get a WARNING: Not enough clusters for a 32 bit FAT!
error1 when attempting to format it.
Other than the GRUB boot loader, everything else on this system will be fully encrypted. GRUB2 allows for unlocking an LUKS encrypted system at boot, but only the LUKS 1 format. GRUB has added support for LUKS 22, but that support hasn’t made it into a stable release version yet. I used the following cryptsetup
commands to created the encrypted partition. To fully understand what these options mean, I suggest reading the Arch Wiki documentation on LUKS encryption modes and options. Learning what these options mean, and deciding which ones to use, is vital to ensuring the confidentiality of your system.
cryptsetup luksFormat --type luks1 -c aes-xts-plain64 -s 512 --iter-time 5000 --hash sha512 /dev/nvme0n1p2 cryptsetup luksOpen /dev/nvme0n1p2 enc_root
Next, I formatted my ESP partition as FAT32, and setup two logical volumes within the encrypted partition. One of the volumes was 16GB in size and formatted as swap, and the second filled the remaining space and was formated as ext4.
mkfs.fat -F32 /dev/nvme0n1p1 pvcreate /dev/mapper/enc_root vgcreate lvm /dev/mapper/enc_root lvcreate -L 16G lvm -n swap lvcreate -l 100%FREE lvm -n root mkswap /dev/lvm/swap mkfs.ext4 /dev/lvm/root
Next, I mounted the root and ESP file systems.
mount /dev/lvm/root /mnt -t ext4 mkdir /mnt/boot/efi -p mount /dev/nvme0n1p1 /mnt/boot/efi/
Take note of the UUID for the encrypted partition using the blkid
command. It will be needed for the GRUB configuration later.
blkid -s UUID -o value /dev/nvme0n1p2 /dev/nvme0n1p2: UUID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx" TYPE="crypto_LUKS"
I installed the Alpine base system using the setup-disk
command on the mounted volume.
setup-disk -m sys /mnt Installing system on /dev/lvm/root: /mnt/boot is device /dev/lvm/root 100% #########==> initramfs: creating /boot/initramfs-lts /boot is device /dev/lvm/root
To finish the install, I chrooted into the new Alpine environment. Before doing so, I needed to bind the virtual proc
, sys
and dev
file systems into the new environment.
mount -t proc /proc /mnt/proc mount --rbind /dev /mnt/dev mount --make-rslave /mnt/dev mount --rbind /sys /mnt/sys
Then I used chroot
on the environment, and changed my prompt as well to make it clear I was in the new root.
chroot /mnt source /etc/profile export PS1="(chroot) $PS1"
I needed to install grub-efi
and efibootmgr
for my boot loader. Since I’m using UEFI and not BIOS/legacy, I removed syslinux
.
apk add grub-efi efibootmgr apk del syslinux
To get grub to work correctly with full disk encryption and my logical volumes, I needed to edit /etc/default/grub
, and add in the following options. Be sure to set the UUID to the one returned by blkid
above (the UUID of your LUKS encrypted partition).
rootfstype
argument for the root filesystem to the kernel command line below. The root filesystem type may need to be configured explicitly on some systems. If you don't, you may get the error message "no such file or directory"
when the initrd attempts to mount the root volume to /sysroot
.(Updated: 2022-06-18)# /etc/default/grub GRUB_DISTRIBUTOR="Alpine" GRUB_TIMEOUT=2 GRUB_DISABLE_SUBMENU=y GRUB_DISABLE_RECOVERY=true GRUB_ENABLE_CRYPTODISK=y GRUB_CMDLINE_LINUX_DEFAULT="cryptroot=UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx cryptdm=lvmcrypt cryptkey rootfstype=ext4" GRUB_PRELOAD_MODULES="luks cryptodisk part_gpt lvm"
The arguments in the GRUB_CMDLINE_LINUX_DEFAULT
listed above are not for the kernel but for the initial ram disk (initrd) created using mkinitfs
. The cryptroot
argument specifies which device to unlock on boot and cryptkey
indicates that a key exists at /crypto_keyfile.bin
on the root file system that should be included in the initrd. The cryptdm
argument indicates the name of the encrypted disk mapping, which doesn’t really matter as the initrd will map the logical volume names for the rest of the boot process.
The GRUB_PRELOAD_MODULES
argument indicates which modules are baked into the GRUB unencrypted executable on the ESP. These modules allow GRUB to unlock an encrypted disk, read GPT partitions and load logical volumes, all of which are needed to boot this particular encrypted setup. A full list of modules can be found in /usr/lib/grub/x86_64-efi/
.
Next, I generated a random key and added it to my encrypted volume. The initial ram disk will be on the fully encrypted root volume, and GRUB will prompt me to unlock that volume on boot. Without adding this key to the LUKS volume and initrd, I would have to type in my password twice, once for GRUB to load the initrd and then the initrd would prompt me for it again.
dd bs=512 count=4 if=/dev/urandom of=/crypto_keyfile.bin chmod 000 /crypto_keyfile.bin cryptsetup luksAddKey /dev/nvme0n1p2 /crypto_keyfile.bin
I setup my initial ram disk. The cryptsetup
and cryptkey
features were added to /etc/mkinitfs/mkinitfs.conf
for unlocking the encrypted disk on boot. I’ve also added nvme
since I’ve booting from an NVME drive3, but it can be left out for systems with SATA system drives.
# /etc/mkinitfs/mkinitfs.conf features="ata base ide scsi usb virtio ext4 lvm cryptsetup cryptkey nvme"
Next, I made the initramfs and installed grub. I used $(ls /lib/modules/)
to specify the version of the kernel that is installed to the system disk, as it may be different from the kernel version used on the USB boot media. Some memory leak warnings may appear. These can be safely ignored.
mkinitfs -c /etc/mkinitfs/mkinitfs.conf $(ls /lib/modules/) grub-install --target=x86_64-efi --efi-directory=/boot/efi grub-mkconfig -o /boot/grub/grub.cfg
Before rebooting, I needed to run passwd
to set a password for the root user. Afterwards, I unmounted all the file systems and rebooted. If you get an error about -R
being an unrecognized option, it’s because you didn’t install util-linux
earlier on in this tutorial. The busybox umount
command doesn’t support recursive unmounting, which is needed for rbind
mounts.
passwd Changing password for root New password: Retype password: passwd: password for root changed by root exit cd / umount -R /mnt/proc /mnt/dev /mnt/sys /mnt/boot/efi umount /mnt reboot
Post Install
Since I skipped the typical setup-alpine
installation method, and called setup-disk
to install the system partition, there are many parts of the system that did not get configured. At this point you can run setup-alpine
and skip the disk installation step, or call the individual setup tools such as setup-timezone
, setup-keymap
, setup-hostname
, setup-ntp
, and others. I do like how Alpine separates out each individual setup step into self-contained commands.
You should also setup swap by adding adding the line /dev/mapper/lvm-swap none swap sw 0 0
to /etc/fstab
. You can also use the swap volume’s UUID instead of the lvm device node. After setting up swap, run /etc/init.d/swap start
to activate it and rc-update add swap boot
to ensure swap is activated when the system starts.
Troubleshooting
The Alpine initramfs isn’t very well documented. If you run into issues, the boot script for it can be located at /usr/share/mkinitfs/initramfs-init
. Reading through it is how I discovered where it expects the location for the crypto_keyfile.bin
file.
If GRUB fails to boot and you need to return to the installation USB to debug what’s happening, the command vgchange -a y
will load all the logical volumes once you’ve unlocked the encrypted LUKS volume. The efibootmgr
tool is also useful when trying to diagnose EFI boot order issues.
Sometimes it will take a few reboots to fully diagnose an issue, so I typically keep around all the commands I need to get me back into the chroot environment of the new install.
ifconfig eth0 up udhcpc eth0 setup-apkrepos -r apk update apk add gptfdisk cryptsetup lvm2 e2fsprogs util-linux cryptsetup luksOpen /dev/nvme0n1p2 enc_root vgchange -a y mount /dev/mapper/lvm-root /mnt mount /dev/nvme0n1p1 /mnt/boot/efi/ mount -t proc /proc /mnt/proc mount --rbind /dev /mnt/dev mount --make-rslave /mnt/dev mount --rbind /sys /mnt/sys chroot /mnt source /etc/profile export PS1="(chroot) $PS1"
Here are some common questions you should yourself in you run into issues:
- Does
efibootmgr -v
show the correct device and boot loader? - Did GRUB not install correctly? (rerun
grub-install
in the chroot environment) - Did you forget to configure GRUB with
grub-mkconfig -o /boot/grub/grub.cfg
? - Does the initial ram disk have all the modules/features it needs? (Run
mkinitfs -L
for a list) - Is the ESP partition not formatted in a way the UEFI/BIOS can recognize it (did you remember to use
-F32
? Maybe try FAT16 instead?)
Most Dell BIOS screens have a UI for configuring UEFI executables, which can be helpful for manually finding and loading the GRUB boot loader. Some motherboards have a UEFI diagnostic shell, but for the vast majority of systems I’ve seen, there are few if any diagnostic tools for UEFI and boot issues.
Final Thoughts
Installation with full disk encryption for Linux distributions such as Gentoo, Void and Alpine Linux, isn’t very straight forward. It typically involves going past the default installation instructions or setup tools, and requires knowledge about how Linux file systems and boot loaders are setup and configured. It does take some additional work, but having a core understanding of how Linux disk partitions, encryption, and boot loaders are setup, are valuable skills that carry over to other aspects of development, operations and Linux system administration.
-
WARNING: Not enough clusters for a 32 bit FAT! - SOLVED. Installation - Arch Linux Forums. Retrieved 13 October 2020. ↩
-
GNU GRUB - Bugs: bug #55093, Add LUKS2 support. 25 November 2018. dllud. GNU GRUB. Retrieved 14 October 2020. ↩
-
NVME - Alpine Linux. Alpine Linux wiki. Retrieved 13 October 2020. ↩