Secure your boot process part 2: Fedora and Unified Kernel images made easy with Dracut

As you may notice, this is almost a part 2 of my Secure your boot process: UEFI + Secureboot + EFISTUB + Luks2 + lvm + ArchLinux. Except that here i’ll not talk about all the secureboot stuff that i’ve already ran into on my last blogpost. This one is specifically focused on how to achieve the same setup using Fedora.

This blogpost compiles my personal opinions around using bootloaders on EFI environments so, the classic “opinions expressed here are solely my own and do not express the views or opinions of my employer” applies.

What is this blogpost about

  • Fedora manual installation process.
  • Luks2+lvm setup for encrypted partitions at boot time.
  • /home disk setup with some dracut tweaks.
  • EFISTUB to make Linux “it’s own bootloader” avoiding the entire /boot to be mounted on your ESP.
  • Unified kernel image and Secureboot. Means, one file that has initrd, boot parameters and kernel in a “all in one” package.

Requirements

Fedora Drama

Fedora heavly relies on grub2 and it has it’s own dracut scripts for auto generating initrds. While this is a nice approach to create a distribution that is reliable to those who do not want to thinker with bootloaders, i still find outdated this need of use grub while we have more lightweight counterparts like systemd-boot, or we could just be using the EFI menu entry system on our motherboards.

Bootloaders are not needed anymore. Why insist on using them?

Installing Fedora without using Anaconda

Step 01: Boot the Live DVD Environment, go to “Region and Language” on Gnome and set up your Keyboard correctly. Connect to the internet.

Step 02: Open Gnome Terminal. Type sudo -i to become root during all the rest of this tutorial.

Step 03: We will create luks2 containers with lvm2 volumes on top of the first disk. On my laptop i have 2 disks: sda is my first disk while sdb is my second disk. Use cgdisk /dev/sda and create a 256MB partition for EFI (ESP) code ef00 and the rest of your disk space create a partition with code 8309(Linux Luks). For the disk 2 (if any) that will be dedicated to /home create only one partition code 8309. Disk layout:

+-----------------------------+ +---------+
| SDA                         | |SDB      |
+------+ +------------------+ | +---------+
||SDA1 | |SDA2 LUKS         | | ||SDB1   ||
||     | +------------------+ | ||       ||
|| ESP | ||LV-ROOT||LV-SWAP|| | ||LUKS   ||
||     | ||       ||       || | ||HOME   ||
+------+ ||       ||       || | ||       ||
|        |------------------| | +---------+
|        |------------------| | |         |
+-----------------------------+ +---------+

You could also use gdisk or parted if you are more confortable with those tools.

Note to reader: Disks are named differently depending on your system (vdX on vms if you are just testing this material, nvmeX for nvme disks) so change it accordingly. Also, the second disk is totally optional and you could have just one disk containing your root, home and swap partitions.

Step 04: Create your luks container and open it. Default block cipher and block encyption mode should be good enough so there is no need of changing it with -c parameter:

cryptsetup -y -v --use-random luksFormat /dev/sda2   # type uppercase YES when asked, feed your password when asked
cryptsetup luksOpen /dev/sda2 crypt                  # type your password

Create your lvm infraesturucture on top of this luks container with swap and root logical volumes. If you have only one disk, create a specific volume to home if you want. Sizes defined here are totally arbitrary.

pvcreate /dev/mapper/crypt
vgcreate vg0 /dev/mapper/crypt
lvcreate --size 4G vg0 --name swap
lvcreate --size 30G vg0 --name root

Format your ESP, root and swap partitions/volumes. I’m using ext4 here but you could use xfs(remember to install xfsprogs later).

mkfs.vfat -F32 /dev/sda1
mkfs.ext4 /dev/mapper/vg0-root
mkswap /dev/mapper/vg0-swap

Step 04.01 - OPTIONAL: If you have a dedicated disk for your /home mountpoint and it is already partitioned accordingly, let’s create a luks container and format it.

cryptsetup -y -v --use-random luksFormat /dev/sdb1      # type uppercase YES when asked, feed your password when asked
cryptsetup luksOpen /dev/sdb1 crypthome                 # type your password
mkfs.ext4 /dev/mapper/crypthome

Step 05: Mount all partitions on /mnt subdir and bootstrap Fedora:

mount /dev/mapper/vg0-root /mnt
mkdir /mnt/efi
mount /dev/sda1 /mnt/efi
mkdir /mnt/home           #OPTIONAL
mount /dev/sdb1 /mnt/home #OPTIONAL
yum --releasever=33 --installroot=/mnt groupinstall core

Change releasever with the stable version of the time you are reading this material. Wait for the bootstrap to finish.

Step 06: Copy your current resolver settings, mount sys, proc and efivarfs filesystems, bind mount your /dev and chroot to your installation environment.

cp /etc/resolv.conf /mnt/etc
mount -t sysfs none /mnt/sys
mount -t proc none /mnt/proc
mount -t efivarfs none /mnt/sys/firmware/efi/efivars
mount -o bind /dev /mnt/dev
chroot /mnt /bin/bash

Step 07: Install some additional packages on the chroot environment:

yum install vim lvm2 openssl lz4 efibootmgr cryptsetup kernel sbsigntools gdisk efitools

This is the minimal set of packages to operate after the first reboot. If you want, install any other packages that you might deem necessary. If you want to use xfs partitions, just install xfs_progs here too.

During the time I was experimenting with this Fedora setup on a vm, efitools was not already available on Fedora repos and i had to install it manually. This is not needed anymore :)

Step 08: Set a password for root user and, add and set a password for yourself:

passwd
useradd -m -G wheel,users myuser
passwd myuser

Step 09: Create your fstab with the following content, replacing your ESP partition with the corresponding UUID. UUIDs are unique identifiers on disks and you can discover by issuing blkid(will show all disks) or lsblk --output=UUID /dev/sda1 for example.

# EFI System Partition(ESP)
UUID=D39A-6EAF /efi vfat rw,relatime,fmask=0022,codepage=437,iocharset=ascii,shortnaremount-ro 0 2

#Root Partition
/dev/mapper/vg0-root / ext4 rw,relatime,defaults 0 1

# SWAP
/dev/mapper/vg0-swap none swap sw 0 0

Step 09.01 - OPTIONAL: if you’ve setup your home on the second disk, get the UUID of /dev/mapper/crypthome and add the following line to your fstab. Since devicemapper will rename /dev/mapper/crypthome to a autogenerated /dev/mapper/luks-uuid-of-sdb1 during next boot, take care to use /dev/mapper/crypthome UUID(ext4 device mapping), and not /dev/sdb1 UUID(partition with luks container). blkid should diplay to you the device type TYPE="ext4"

# /home
UUID=xxxxxxxx-xxxxxxxxxx-x-xxxxxxxxxx	/home     	ext4      	rw,relatime	0 2

Step 09.02 - OPTIONAL: Create a luks key to auto-unlock your home partition. This key will be stored inside your root partition, that is alrady secured with luks.

dd bs=512 count=4 if=/dev/urandom of=/root/secret.bin
chmod 000 /root/secret.bin
cryptsetup luksAddKey /dev/vdb1 /root/secret.bin         #type this container password. no output means success

Step 10: Create a basic directory structure for efi images and copy keytool to the ESP

mkdir -vp /efi/BOOT/Fedora
mkdir -vp /efi/EFI/BOOT
cp /usr/share/efitools/efi/KeyTool.efi /efi/BOOT/KeyTool.efi

No, i didn’t mistype /efi/BOOT/Fedora neither /efi/EFI/BOOT. We will use those on a near future ;)

Some motherboards come with firmwares that are able to deploy custom secureboot keys and using KeyTool may not be needed.

Step 12: Create a cmdline.txt file for the very first boot with your Unified EFI Image:

root=/dev/mapper/vg0-root luks.crypttab=no ro rd.luks.timeout=20 rd.lvm.vg=vg0 luks.uuid=11ed1a3a-22c0-4fab-beeb-5362dcfc67a4 luks.uuid=83ddd1d9-98da-47a0-9d25-e20ea9f6d27f rd.luks.key=83ddd1d9-98da-47a0-9d25-e20ea9f6d27f=/root/secret.bin:/dev/mapper/vg0-root quiet

Detailed explanation:

  • root=/dev/mapper/vg0-root - root logical volume
  • luks.crypttab=no - do not use /etc/crypttab to map devices. I’ve ran into a chicken’n egg situation where relying on crypttab would ask passwords twice even if i configure my home partition to be auto-unlocked the same way i did on Arch Linux, and if i use rd.luks.crypttab=no to ignore crypttab during the initrd phase, i would be asked my root password twice(init and kernel) and home one(during initrd since kernel would obey crypttab key auto-unlock config). Using crypttab(no parameter set) would also ask for root password twice and at this point i don’t know if it’s a bug. Using luks.crypttab=no and configuring it to auto-unlock my home partition using crypttab would completely ignore this config and i got asked for passwords for root. That’s why i’m not using crypttab and passing luks configuration directly during boot process.
    • This page contains good information about these luks related parameters, but it didn’t help much while investigating this luks+crypttab behavior.
  • rd.luks.timeout=20 - luks password timeout in seconds
  • rd.lvm.vg=vg0 - map vg0 volume group. You could map individual logical volumes here, but that is not the case.
  • luks.uuid=11ed1a3a-22c0-4fab-beeb-5362dcfc67a4 - UUID of the first luks container(/dev/sda2), that contains the main volume group
  • luks.uuid=83ddd1d9-98da-47a0-9d25-e20ea9f6d27f - UUID of the second luks container(/dev/sdb1)
  • rd.luks.key=83ddd1d9-98da-47a0-9d25-e20ea9f6d27f=/root/secret.bin:/dev/mapper/vg0-root - Key for 83ddd1d9-98da-47a0-9d25-e20ea9f6d27f(/dev/sdb1) is /root/secret.bin and stored at /dev/mapper/vg0-root mountpoint
    • While i’m not happy to have this information exposed on my boot process, i’m also not happy with the crypttab situation that i’ve got. But hey, the key is inside another luks container so, even knowing the location, the file had 000 permission(no read, write or execute to anyone) and our main luks partition needs to be decrypted first with my password for this to work.
  • quiet - less verbosity during boot

Again, if you are not using a second disk for home, the second UUID and it’s key boot entires are not relevant to you.

Step 13: Create your first unified Kernel image and place it at the fallback EFI location(/efi/EFI/BOOT/BOOX64.EFI). Take care while pointing to your kernel and initrd versions cause they can be different than mine due to Fedora updates and/or releases.

objcopy \
--add-section .osrel=/etc/os-release --change-section-vma .osrel=0x20000 \
--add-section .cmdline="cmdline.txt" --change-section-vma .cmdline=0x30000 \
--add-section .linux="vmlinuz-5.10.16-200.fc33.x86_64" --change-section-vma .linux=0x40000 \
--add-section .initrd="initramfs-5.10.16-200.fc33.x86_64.img" --change-section-vma .initrd=0x3000000 \
/usr/lib/systemd/boot/efi/linuxx64.efi.stub /efi/EFI/BOOT/BOOX64.EFI`

I’m suggesting you to use the EFI fallback location to boot first time cause almost certainly you will not have permissions to write a new boot entry on your motherboard using efibootmgr on a chroot environment. You could double-check by running bootctl status| grep -i "sets" (in my case i’ve got a red X).

Step 14: Exit the chroot environment and restart your Live system. Remember to eject the live disk.

From now you have a fully functional Fedora booting into Unified Kernel images. Now, it’s time to automate tasks related to secureboot like auto signing and auto image generation after a kernel update.

Creating your Root of Trust

Quoting my other article, Secure your boot process: UEFI + Secureboot + EFISTUB + Luks2 + lvm + ArchLinux:

There is no need to reinvent the wheel so, we will use a pretty good script from mr. Roderick W. Smith aka rodsbooks.com. Check out his page for tons of informations related to EFI and other Linux related stuff. Access http://www.rodsbooks.com/efi-bootloaders/controlling-sb.html#creatingkeys download the script directly:

su - root
cd /root
mkdir keys
cd keys
wget https://www.rodsbooks.com/efi-bootloaders/mkkeys.sh

Let’s explain the contents on this script:

  • openssl commands will basically create key and certificate pairs for PK, KEK and DB on X509 format
  • python line will randomly create a GUID for our secureboot assets (think it like a name to our keys)
  • cert-to-efi-sig-list is the software that will convert X509 certificates(openssl ones) to a format that EFI can understand
  • touch will crate an empty noPK file since we do not have a list of revoked keys
  • sign-efi-sig-list commands will sign PK with itself, sign noPK using PK, sign KEK using PK and finally sign DB using KEK creating all your root trust.

After taking a look at the script, just run it :)

chmod +x mkkeys.sh
./mkkeys.sh

Creating a dracut config file

Besides of what i did on Archlinux, here we will delagate all signing/unification of the kernel image to dracut. No need to use an exernal tool like sbupdate.

However, since dracut on Fedora has a ton of custom scripts we will not be able to use it’s native capabilities like uefi_*= and create a custom config inside /etc/dracut.d. This will heavly break initrd creation itself, trust me, i’ve spend 2 nights thinkering with it.

Instead, we will write our own script and read it from one outside module, after all the initrd creation is done.

Let’s prepare the key infrastructure:

cp /root/keys/DB.key /etc/efi-keys/db.key
cp /root/keys/DB.crt /etc/efi-keys/db.crt

Create /etc/dracut-sb.conf with the following content:

hostonly=yes
hostonly_cmdline=no
use_fstab=yes
compress=lz4
show_modules=yes
add_drivers+='lz4'

uefi=yes
early_microcode=yes
uefi_stub=/usr/lib/systemd/boot/efi/linuxx64.efi.stub
uefi_secureboot_cert=/etc/efi-keys/db.crt
uefi_secureboot_key=/etc/efi-keys/db.key
CMDLINE=(
    root=/dev/mapper/vg0-root 
    luks.crypttab=no
    ro 
    rd.luks.timeout=20
    rd.lvm.vg=vg0
    luks.uuid=11ed1a3a-22c0-4fab-beeb-5362dcfc67a4 
    luks.uuid=83ddd1d9-98da-47a0-9d25-e20ea9f6d27f 
    rd.luks.key=83ddd1d9-98da-47a0-9d25-e20ea9f6d27f=/root/secret.bin:/dev/mapper/vg0-root
    quiet
)
kernel_cmdline="${CMDLINE[*]}"
unset CMDLINE

Pretty simple eh? some additional compression modules for initrd, uefi parameters for kernel signing(key, cert, stub location), CMDLINE is basically the same block we’ve used to create our first unified image.

Now, the custom script that will be run after every kernel update. Create the /etc/kernel/install.d/99-make-n-sign-efiimg.install file.

#!/usr/bin/bash
#  "Life, uh... finds a way" -- Dr. Ian Malcolm, Jurassic Park
#
# Ex: bit of an overhead to run dracut twice with our dracut.d config file but...
# dracut -f -v --kver 5.10.16-200.fc33.x86_64 /efi/BOOT/Fedora/linux-signed.efi 
#
# The way dracut is tailored to work with Fedora, and its dependencies with grub
# i had to do this little hack, keeping my dracut configfile outside dracut.conf.d
# config drop-in dir, or weird things could happen
# And also, because im not mounting my ESP at /boot or /boot/efi, some weird 
# directory garbage is generated with my machine-id...
#
# Generate a new secureboot signed image
COMMAND="$1"
KERNEL_VERSION="$2"

DISTRO="Fedora"
SECBOOTIMG="/efi/BOOT/${DISTRO}/linux-signed.efi"
SECBOOTIMG_BKP="/efi/BOOT/${DISTRO}/linux-signed-bkp.efi"
SECBOOTCONF="/etc/dracut-sb.conf"

ret=0
case "$COMMAND" in
    add)
        if [[ -f ${SECBOOTIMG} ]]; then
		      # Backup the last efi image. crate an additional entry with efiboomgr
		      # to have a fallback just in case...
		      mv -f $SECBOOTIMG $SECBOOTIMG_BKP
        fi
      	dracut -c $SECBOOTCONF -f -v --kver $KERNEL_VERSION $SECBOOTIMG

        ret=$?
	      ;;
    remove)
        ret=$?
      	;;
esac
exit $ret

Set execution permission for owner only(root):

chmod 700 /etc/kernel/install.d/99-make-n-sign-efiimg.install

After each kernel update /efi/BOOT/Fedora/linux-signed.efi will be moved to /efi/BOOT/Fedora/linux-signed-bkp.efi and a new /efi/BOOT/Fedora/linux-signed.efi will be generated with the new kernel+initrd+microcode+cmdline glue.

Setting boot order

This part is crucial cause, you’ll need to check if your firmware will allow you to create menu entries. Most motherboards will allow but you know, we can’t test all hardware in existence so, use the following command to check if you are able to write to your motherboard efi boot menu:

bootctl status| grep -i "sets"
       ✓ Boot loader sets ESP partition information

If ou see a you are good to go. If not(red X is shown), you will have to change the SECBOOTIMG= variable of the last script and change it to point to the fallback EFI boot image: SECBOOTIMG="/efi/EFI/BOOT/BOOX64.EFI". You will also lose the ability to quickly boot into a recovery image if something goes wrong, but using a live usb you will be able to “revert” by copying it.

If this is your case, change the script and ignore all the steps inside this chapter and jump right into Deploying secureboot keys.

Else, lets move the fallback image to our desired place. efibootmg needs a valid image and it will check if the file exists before placing a new efi menu entry:

mkdir -p /efi/BOOT/Fedora
mv /efi/EFI/BOOT/BOOX64.EFI /efi/BOOT/Fedora/linux-signed.efi
cp /efi/BOOT/Fedora/linux-signed.efi /efi/BOOT/Fedora/linux-signed-bkp.efi

Setting menu entries is easy with efibootmgr:

efibootmgr -c -d /dev/sda -p 1 --label "Fedora" -l "BOOT\Fedora\linux-signed.efi" --verbose
efibootmgr -c -d /dev/sda -p 1 --label "Fedora BKP" -l "BOOT\Fedora\linux-signed-bkp.efi" --verbose

In plain english: “Create an efi entry, from sda device partition 1, with the following label, this is the path and please verbose :)”. Note that /efi is not needed here since we are already saying it’s on sda1(and it’s our ESP).

Double check boot order:

efibootmgr
BootCurrent: 0000
Timeout: 0 seconds
BootOrder: 0000,0001,2001,2002,2003
Boot0000* Fedora
Boot0001* Fedora BKP
Boot2001* EFI USB Device
Boot2002* EFI DVD/CDROM
Boot2003* EFI Network

Those values could change depending on your machine(entry numbers and order), and using efibootmg you could also reorder them or even delete boot entries to make your installation even more secure. For example, if we delete USB, DVD and network boot our menu will look like this:

efibootmgr -b 2001 -B
efibootmgr -b 2002 -B
efibootmgr -b 2003 -B

efibootmgr
BootCurrent: 0000
Timeout: 0 seconds
BootOrder: 0000,0001
Boot0000* Fedora
Boot0001* Fedora BKP

**IMPORTANT: **Test your new setup and sign your main kernel by reinstalling the kernel-core package:

yum -y reinstall kernel-core

You will notice that kernel install will take more time to finish in order to deliver a signed kernel to you.

Deploying secureboot keys

**NOTE: **If you are not able to create boot entries on your motherboard, you’ll need to rely on a live-usb efi of KeyTool, or move KeyTool to /efi/EFI/BOOT/BOOX64.EFI and after that move your kernel back to that location.

Firstly, create a boot entry to KeyTool:

efibootmgr -c -d /dev/sda -p 1 --label "Keytool" -l "BOOT\KeyTool.efi" --verbose

efibootmgr
BootCurrent: 0000
Timeout: 0 seconds
BootOrder: 0000,0001,0002
Boot0000* Fedora
Boot0001* Fedora BKP
Boot0002* Keytool

Remember that we’ve already copied KeyTool.efi to /efi/BOOT/KeyTool.efi in the first chapter.

Be sure Keytool will be the next thing to boot on your computer:

efibootmgr --bootorder 0002,0000,0001

You will need to be sure that Secure boot is in “setup mode” not “user mode”. I did this setup on 2 different laptop model/brands on my life: a Lenovo Ideapad 330 and an Acer Aspire VX(my current) and it was a guessing game on the Aspire.

With the Lenovo was just a matter of selecting “remove oem keys”, while with the Acer i had to not only do that but also cleanup TPM2 inside the firmware setup. The important thing here is that if you do something wrong, KeyTool will warn you that firmware is not in setup mode and you will not be able to install certs on your firmware.

To copy your keyst to your ESP partition to keep things simple:

copy /root/keys/*.esl /efi/BOOT
copy /root/keys/*.auth /efi/BOOT

Reboot:

Now, add your keys following this order:

  • Select the db entry, hit “Add new key”, and point to DB.esl
  • Select the kek entry, hit “Add new key” and point to KEK.esl
  • Finally, add the Platform Key(PK), select “Replace Keys” and point to PK.auth
  • Exit KeyTool and reboot
  • Enable Secure Boot(depending on your hardware, this step will be automatically done)

You will probably receive a secureboot message warning as soon as you reboot, and that is of course because your KeyTool.efi image isn’t signed. Enter your firmware and change your boot order.

As soon as you get a working environment, shred all those keys from your ESP:

shred /efi/BOOT/*.esl
shred /efi/BOOT/*.auth

Shred is a software that comes with coreutils and it’s used for safe file removal.

Also, remove your Keytool entry and efi file:

efibootmgr -b 0002 -B
rm /efi/BOOT/KeyTool.efi

Done. Now install all the packages you want to have a pretty (and secure) desktop :)

Now what?

Security isn’t a “one pill” medicine. There’s plenty of room for other tools to create a more secure environment:

  • Deploy tpm2 boot policies to create a Security Violation if boot options are changed
  • Remove all undeeded boot entries
  • Use usbguard to avoid tricky devices like those usb drivers that are actually HIDs under-the-hood
  • Setup a vpn client to use on public wifi hostpots you don’t trust much
  • Create a fake windows installation partition before your root luks partition

Be safe!