blog/articles/arch-uki-secure-boot/arch-uki-secure-boot.asciidoc

199 lines
14 KiB
Plaintext

= Arch Linux with UKI & Secure Boot
:docdate: 2024-06-12
:description: UEFI and UKI allows to boot a Linux Kernel without bootloader. See how to set it up under Arch Linux.
:keywords: Linux, Arch Linux, UEFI, UKI, Secure Boot
The Linux boot sequence can be seen as a bit convoluted, as multiple options exist, and the more recent advent of UEFI has shuffled this space again. While GRUB is still a very popular option, especially since it is compatible with both traditional BIOS boot and more recent UEFI firmwares, it is not the only one.
But the step of going through a third party software to boot your Linux kernel is not a compulsory step anymore. Linux kernels have been able to be booted as standalone UEFI images for a while now, and so as long as you had a firmware capable of providing your boot arguments, you could bypass the bootloader stage. Even more recently, Linux now has something called *UKI*, or "`Unified Kernel Image`". It is a way of taking a Linux kernel, its initramfs, and its command line, and make it a truly standalone bootable EFI image that can be simply booted from anything capable of booting EFI images.
Arch Linux is a generic distribution geared towards tinkerers. It notably has no defaults in regard to its bootloader. And it fully supports (albeit with a bit of tinkering) booting from an UKI image.
== Prerequisites
We are going to make an Arch Linux installation boot from UEFI. This can be done either from an existing installation, or during the initial installation process. The only true requirement is to be on an UEFI-compatible machine and https://wiki.archlinux.org/title/Installation_guide#Verify_the_boot_mode[booting into this mode]. Of course, this also means having an https://wiki.archlinux.org/title/EFI_system_partition[UEFI boot partition]. In this article, I have mounted mine at `+/boot/efi+`, although some installs mount it directly into `+/boot+`. It does not matter much, but you might have to correct some paths if it is the case for you.
This guide will also show how to make it compatible with https://wiki.archlinux.org/title/Unified_Extensible_Firmware_Interface/Secure_Boot[Secure Boot]. Secure Boot is a bit of a problematic feature in the Linux community, and some people prefer to leave it out. I am not going to argue for or against it, I am just presenting how to make it compatible, but this part is completely optional. However, if you want to go with Secure Boot, you need to boot your machine with Secure Boot enabled and in Setup mode.
[.note]
Arch Linux now provides multiple choices for the https://wiki.archlinux.org/title/Arch_boot_process#initramfs[initramfs]. https://wiki.archlinux.org/title/Mkinitcpio[mkinitcpio] has been the historical and still default choice today, but it is also possible to use https://wiki.archlinux.org/title/Dracut[dracut] or https://wiki.archlinux.org/title/Booster[booster]. dracut supports making an UKI image directly, and there is `+systemd-ukify+` that can make an UKI image from separate kernel and initramfs. However, this article will focus on mkinitcpio.
== Making the install
=== mkinitcpio
On your existing system, or on a system at the boot loader phase of the install, we will need to configure mkinitcpio to write to an UKI image. Ensure the `+mkinitcpio+` package is installed. Then, we will edit the preset file for mkinitcpio, located at `+/etc/mkinitcpio.d/linux.preset+` (or a similar name depending on your kernel flavour of choice like `+linux-lts.preset+`). Disable the classical initramfs generation by commenting the `+default_image+` and `+fallback_image+` options, then enable UKI generation by uncommenting the `+default_uki+` and `+fallback_uki+` options. Ensure the paths are correct. For a UEFI boot partition mounted at `+/boot/efi+`:
[source,bash]
----
default_uki="/boot/efi/EFI/Linux/arch-linux.efi"
fallback_uki="/boot/efi/EFI/Linux/arch-linux-fallback.efi"
----
Finally, mkinitcpio expects the parent folder to already exist which is not by default the case. Use `+mkdir+` to ensure the folder is created.
=== Command Line
In a traditional Linux setup, the kernel options (or _command line_) is stored and passed by the bootloader, and usually configured there. Here, we have no bootloader, but still need to provide the command line options.
The UKI build process assemble the command line from files in `+/etc/cmdline.d+`. You can create `+.conf+` files in this folder containing command line arguments (which also allows you to group related arguments together). Simplest example is to create a `+/etc/cmdline.d/root.conf+` with the root configuration:
....
root="/dev/vda2" rw
....
Of course, adapt it to your configuration and needs.
=== Building the image
Now that things are configured, you can just build the image like you do traditionally:
....
mkinitcpio -P
....
The process should end without errors, and you should find the new files at the location indicated above.
=== Registering
UEFI boot programs are normally registered into the firmware settings as boot entries. If we want to boot our kernel, we need to register our kernel in the firmware with `+efibootmgr+`. First, ensure the `+efibootmgr+` package is installed.
....
efibootmgr --create --disk=/dev/vda --part=1 --label="Arch Linux" --loader='\EFI\Linux\arch-linux.efi'
efibootmgr --create-only --disk=/dev/vda --part=1 --label="Arch Linux Fallback" --loader='\EFI\Linux\arch-linux-fallback.efi'
....
Note that the second command for the fallback image is created with `+--create-only+` instead of `+--create+`. This way, it will not be booted automatically, but still be available as a boot option.
=== Rebooting
Everything is set up! Now, you can finalise the installation steps (if applicable) and reboot your machine, then check that your system boots directly into Arch Linux. You can also trigger the UEFI boot menu to check for the additional entry for the fallback image.
Congratulations! You now boot entirely through your UKI image.
== Secure Boot
[.note]
As said above, this step is optional, if you do not want to use Secure Boot.
Secure Boot is an addition to UEFI that ensures that your boot sequence has not been tampered with, by requiring all boot images to be signed with a valid certificate. By default, machines have two Microsoft-provided certificates, one for Windows, one for third party systems, the latter being used by some distributions to provide some level of support to Secure Boot. You can also register your own certificate into the machine to sign your own binaries, which is what we are going to set up here. Once setup correctly, the signing process is automatic.
Install the `+sbctl+` package. Ensure that your machine has Secure Boot is in setup mode by issuing `+sbctl status+`:
....
Installed: ✓ sbctl is installed
Owner GUID: 00000000-0000-4000-0000-000000000000
Setup Mode: ✗ Enabled
Secure Boot: ✗ Disabled
Vendor Keys: none
....
[.note]
`+sbctl+` might also indicate that it is _not_ installed, but this will correct itself in the next steps.
If it is not, you will have to reboot your machine into the firmware interface to enable it. Some firmware will go into Setup mode by choosing to erase all certificates.
`+sbctl+` is a nice command to simplify the process of dealing with Secure Boot. To create a certificate and record it into our firmware, we issue the following commands:
....
sbctl create-keys
sbctl enroll-keys -m
....
[.warning]
`+-m+` tells `+sbctl+` to also insert the standard Microsoft certificates, which allows to boot Windows. Even if you plan on never booting Windows, some systems might have UEFI-drivers to load on boot that use one of these certificates. Not including them could break your boot sequence. You can technically do without, but be sure of what you are doing there.
Now that we have our certificates, we need to sign our UKI images. Fortunately, `+sbctl+` also includes a hook for `+mkinitcpio+` to do it for you. You should just have to regenerate images with `+mkinitcpio -P+`. Once this done, ensure it is all good with `+sbctl verify+`:
....
Verifying file database and EFI images in /boot/efi...
✗ /boot/efi/EFI/Linux/arch-linux.efi is not signed
✗ /boot/efi/EFI/Linux/arch-linux-fallback.efi is not signed
....
If you have static UEFI binaries, like the UEFI Shell or a third party bootloader like rEFInd, you will have to sign those too. They should be given to you by `+sbctl verify+`. For each binary that is to be signed, issue the following command:
....
sbctl sign -s /boot/efi/EFI/…
....
The `+-s+` flag tells `+sbctl+` to remember this path. In the future, if you modify any of those files, you can just issue `+sbctl sign-all+` to check and resign every known file.
[.warning]
Ensure that `+sbctl verify+` validates your boot files before rebooting! If you reboot without signing your files, your system will refuse to boot, now that the certificates are registered.
You can now reboot your machine to check that it still boots. Once you reboot, `+sbctl status+` should tell you that all is well:
....
Installed: ✓ sbctl is installed
Owner GUID: 00000000-0000-4000-0000-000000000000
Setup Mode: ✓ Disabled
Secure Boot: ✓ Enabled
Vendor Keys: microsoft
....
== Bonuses
While this is enough for a functional setup, I have some personal tweaks to this that may be useful to you.
=== Automatic root decryption
[.note]
Requires Secure Boot, a https://wiki.archlinux.org/title/Trusted_Platform_Module[TPM], and a LUKS 2 encrypted root.
If your root partition is encrypted, you have to enter the passphrase at every boot. If you have a TPM, you can use it to automatically provide this passphrase to the system. While this seem like a downgrade in security, your system is normally still password protected at the login prompt, so your data should be safe, but it is your choice between more security and ease of use. However, it becomes dangerous if coupled with auto-login!
The boot process and the TPM will ensure that your system has not been tempered with. It means that it will compare Secure Boot certificates at boot and will refuse to use the saved credentials if they are modified, which is even stronger than the base Secure Boot check.
[.note]
This does not save your passphrase in the firmware. It uses a different LUKS slot with its own data saved into the TPM. The safety of the passphrase itself is not affected.
systemd provides the `+systemd-cryptenroll+` command to make the process easy. First, check that your TPM device is detected:
....
systemd-cryptenroll --tpm2-device=list
....
If it is visible, you can then use it with your root partition:
....
systemd-cryptenroll --tpm2-device=auto /dev/vda2
....
Replace `+/dev/vda2+` by your root partition physical device (the encrypted layer, not the `+/dev/mapper+` mapping). It should ask you for the current encryption passphrase before registering the new TPM slot.
Reboot, and you should not have to input your passphrase anymore.
[.note]
Security of this depends on what is allowed to boot on your machine. I would advise to lock down your UEFI settings with a password, including the boot menu if possible, making changing the default boot device impossible for an external person. Of course, they can reset the firmware with a physical access, but that would also reset the boot certificates and make the TPM decryption impossible. If you still keep a bootloader, be careful of what it allows to load.
=== rEFInd
If you have an other system you often use besides your Arch Linux, you may want to still keep a bootloader, depending on how tedious your UEFI firmware is to override the default boot setting. rEFInd is a generic UEFI boot loader, but a very capable one, as well as providing a graphical user interface to choose where to boot from.
While rEFInd will find and boot your Linux configuration in the current state, a few tweaks will improve the experience a bit.
First step is to install the `+refind+` package, then run its built-in script to make the install automatically:
....
refind-install
....
Now, we need to tweak mkinitcpio configuration a bit. If you already have your UKI images generated at this point, I would advise you to remove completely the `+/boot/efi/EFI/Linux+` folder.
rEFInd has some automatic heuristics to try to guess what kind of system is an image for to display an appropriate icon. However, it seems to not be good with UKI images (yet), but we can help it. If it cannot determine the type of an image, it will use the name of the parent folder as a hint. In the current configuration, it will see the `+Linux+` folder, and will display a generic Tux icon. If you want to have Arch Linux branding, we need to rename the folder to `+arch+`. Go back to the `+linux.preset+` file from earlier, and change the two lines to replace `+Linux+` with `+arch+`. Do not close the file yet, we have another change to make.
For distributions that may install different kernel versions at the same time, rEFInd will try to determine which version is the latest to use it as default. However, it gets a bit lost with Arch Linux and believes that the default and fallback images are of the same system, but since no kernel version is given in the file name, it will fallback to the file creation date to determine the newer (and default) kernel. By default, mkinitcpio creates the default image first, and the fallback one in second, which, in our case, would make the fallback image the default.
There is a little trick to solve this: make mkinitcpio build the images the other way around. In the `+.preset+` file, simply switch the presets around:
[source,bash]
----
PRESETS=('fallback' 'default')
----
You can now close the preset file. Recreate the parent folder with `+mkdir+`, then run `+mkinitcpio -P+` to re-create the images in the right order.
[.note]
If you have Secure Boot set up, do not forget to sign its image with `+sbctl+`.