Skip to main content

An encrypted-boot gentoo installation story

How I got to gentoo

Like many people, I have gone through a few linux distros in my lifetime. Over the span of two decades, I ended up going through OpenSuse → Ubuntu → Debian (Stable) → Debian (Testing) → Gentoo. I keep an eye on distributions quite often, but the motivation for moving distributions crosses a threshold when a major workflow or technology disruption occurs within the distribution. It also correlates with my (Slowly) increasing knowledge and comfort with delving into how systems are put together.

Southern Gentoo Penguin

I moved from Ubuntu → Debian (Stable) when Ubuntu decided to develop Mir rather than developing for Wayland. I am always enthusiastic about new software being developed, if only to see what could have been. The vision of convergence between devices was a cool one and given the right circumstances, may have succeeded. However, any large scale surgery of this sort which veers off and does its own thing risks failure.

Another thing that philosophically did not sit right with me was the usage of snap packages. To me this felt like a way to reduce the pressure of maintenance in the short-term at the expense of long-term fragmentation. Desktop *nix distributions are not yet (it is now decade no.3 of trying to conquer the desktop) popular enough. I wish it was different (sigh)! Like any demand-supply equation, any let-up in the pressure to maintain library/API compatibility, in my opinion, would just lead to fragmentation and end-user frustration in the long-run.

So I decided to do the Ubuntu → Debian (Stable) switch. As I was using a slightly older laptop, and it had all the drivers and packages I needed, I wonder why I did not do this earlier (Doh)! After two of my upgrades between major Debian revisions ended up requiring re-installations, I heard of this amazing new term called rolling-distributions. (Please excuse the naïvete and yes, I really am that naïve)!

The next trigger to move was Debian's embrace of Systemd. I feel more comfortable with openRC which I think keeps better to the overall *nix philosophy. I had by now done enough systems level software development and debugging to no longer be afraid of doing silly things that break stuff (a little knowledge being dangerous and all that). Building a new system from ground up would be something that would allow me to explore and understand the guts of my own system.

Enter Gentoo

Keeping with the theme of going further upstream and closer to the source (wipe that smug Icarus image from your mind), the choice was between Linux from Scratch, Slackware and Gentoo. In the end, I went with Gentoo due to the package manager and the sheer amount of support and documentation on the website. It should help reduce the amount of debugging and maintenance work I have to do, while still making me feel like a proper computer scientist. Vanity and naïvete -- what could possibly go wrong?

Gentoo Linux Logo

Why ?!

This leads us onto the purpose of this blog. Installing gentoo is relatively easy but time consuming if we go through the relatively straightforward instructions in the marvellous Gentoo AMD64 handbook. However, I wanted to do a fully encrypted /boot drive as well and had to search around for various instructions. This is the command log (My Gentoo installation story) for my own personal notes. Someone else finding it useful is just a bonus. For more comprehensive information and various options, Each link in the subsections below are linked to the much more comprehensive information in the gentoo wiki.

Gentoo AMD64 installation with encrypted /boot

Obtaining and preparing the installation media

Very detailed instructions are already provided in the handbook and I don't have anything new to add. Switch off secure-boot in the BIOS and choose to boot from the USB drive that was just prepared. Once the laptop has been booted into the linux kernel and shows a root prompt, we will need to set up networking.

Partitioning the storage disks

I have a 16 GiB Laptop with 1TB of space on the SSD. I wanted to partition it with the following schema:

NAME                     MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINTS
nvme0n1                  259:0    0 953.9G  0 disk
├─nvme0n1p1              259:2    0     2M  0 part
├─nvme0n1p2              259:4    0   512M  0 part
│ └─luks_boot            253:0    0   496M  0 crypt
├─nvme0n1p3              259:6    0   128M  0 part
└─nvme0n1p4              259:8    0 953.2G  0 part
  └─luks_root            253:1    0 953.2G  0 crypt
    ├─osvg-swap          253:2    0    8G  0 lvm   [SWAP]
    ├─osvg-gentoo--root  253:3    0    64G  0 lvm   /
    ├─osvg-gentoo--home  253:4    0    16G  0 lvm   /home
    └─osvg-data          253:5    0 865.2G  0 lvm   /media/data

Zap all pre-existing partitions on the disk.

livecd ~# sgdisk --zap-all /dev/nvme0n1

Ensure the first 1MiB is left for grub to be written into raw device head. So we create a 1MiB partition with offset=1MiB.

livecd ~# sgdisk  --new=1:1M:+2M /dev/nvme0n1
livecd ~# sgdisk  --new=2:0:+512M /dev/nvme0n1
livecd ~# sgdisk  --new=3:0:+128M /dev/nvme0n1    
livecd ~# sgdisk  --new=4:0:0 /dev/nvme0n1

Change the names of the partitions and their filesystem types in the GPT partition table. A list of partition types can be obtained with sgdisk -L.

livecd ~# sgdisk  --typecode=1:ef02 --typecode=2:8300 --typecode=3:ef00 --typecode=4:8300 /dev/nvme0n1
livecd ~# sgdisk --change-name=1:GRUB --change-name=2:/boot --change-name=3:EFI-SP --change-name=4:OS /dev/nvme0n1

Encrypt the /boot partition with a password. While it is certainly more robust to have a separate Keyfile stored on another USB flash drive, it is cumbersome to carry around. Also, if you forget it or lose it, then it can be a pain. I am just going to use a plain old password for this in this case. Additionally, grub can not yet decrypt keys in the default LUKS2 format (argon2id) and requires the key to be in the LUKS1 default format of PBKDF2. So the /boot partition is formatted with LUKS1. I will maybe write up a detached header version in a future post.

livecd ~# cryptsetup luksFormat --key-size=512 --type=luks1 /dev/nvme0n1p2

Format the /boot and efi-sp partitions

livecd ~# cryptsetup open /dev/nvme0n1p2 /dev/mapper/luks_boot
livecd ~# mkfs.ext4 -L boot /dev/mapper/luks_boot
livecd ~# mkfs.vfat -n EFI-SP -F 16 /dev/nvme0n1p3 

To obtain an encrypted /root, /home and swap partition, I decided to use Logical Volume Management (LVM) on a LUKS encrypted partition. With experience, I can say that if you intend on using the KDE desktop, the machine should ideally have 32GB of RAM. Some packages such as firefox, the qtwebkit renderer etc require greater than 16GB of RAM. This would then influence the amount of swap space that you should keep aside. Since I have 32GiB of RAM, and I do not want suspend, only 8GiB of swap space is allocated. The beauty of LVM is that this can be resized in the future if required.

livecd ~# cryptsetup luksFormat --key-size=512 --key-slot=1 /dev/nvme0n1p4 
livecd ~# cryptsetup open /dev/nvme0n1p4 luks_root    
livecd ~# pvcreate /dev/mapper/luks_root  # Create a Physical volume on the decrypted device
livecd ~# vgcreate osvg /dev/mapper/luks_root  # Create a volume group on the Physical volume
livecd ~# lvcreate -L 8G -n swap osvg  # Create swap space on the encrypted LVM 
livecd ~# lvcreate -L 64G -n gentoo-root osvg  # Create /root on the encrypted LVM 
livecd ~# lvcreate -L 16G -n gentoo-home osvg  # Create /home on the encrypted LVM 
livecd ~# lvcreate -l 100%FREE -n data osvg  # Create a separate data partition

Format the partitions created in the LVM.

livecd ~# mkswap -L swap /dev/mapper/osvg-swap 
livecd ~# mkfs.ext4 -L root /dev/mapper/osvg-gentoo--root
livecd ~# mkfs.ext4 -L home /dev/mapper/osvg-gentoo--home
livecd ~# mkfs.ext4 -L data /dev/mapper/osvg-data

Now mount the encrypted drives to various directories

mkdir -p /mnt/gentoo/{root,home,data}
mount /dev/mapper/osvg-gentoo--root /mnt/gentoo/root
mount /dev/mapper/osvg-gentoo--home /mnt/gentoo/home
mount /dev/mapper/osvg-data /mnt/gentoo/data
Configuring the network

I already have an ethernet cable to connect, so I did not require to set up WiFi. Gentoo already has the net-setup utility to help with setting up WiFi. The network interface names can be obtained using the ip link command. Set-up is through a fairly easy menu driven ncurses style interactive interface.

livecd ~# net-setup 
  1. For ethernet, configure the wired ethernet interface (starts with enp...)
  2. In the case of WiFi, choose wireless WiFi interface (starts with wlp...)

Make sure the time of the system is accurate. I utilised a simple NTP client (chronyd) to correct the time.

livecd ~# chronyd -q
Obtaining the Stage-3 Installation files

I like openrc and chose the desktop-openrc profile for the stage-3 tarball. Use the livecd built-in ncurses browser to obtain the stage-3 tarball. Alternatively download it on another PC and transfer via another USB device.

Setup base root filesystem, configure portage and gentoo base

Assuming the stage-3 tarball is in /mnt/gentoo/data untar it to the target storage-device's /root directory. In our case, we have mounted it to /mnt/gentoo/root.

livecd ~# cd /mnt/gentoo/data
livecd ~# tar -Jxpvf stage3_tarball.tar.xz --xattrs-include='*.*' --numeric-owner -C /mnt/gentoo/root  
livecd ~# cd  /mnt/gentoo/root
livecd ~# cp --dereference  /etc/resolv.conf /mnt/gentoo/root/etc/resolv.conf 
livecd ~# echo MAKEOPTS=\"-j$(nproc)\" >> /mnt/gentoo/root/etc/portage/make.conf
livecd ~# echo ACCEPT_KEYWORDS=\"amd64\" >> /mnt/gentoo/root/etc/portage/make.conf
livecd ~# echo USE=\"udev lvm dbus X pulseaudio networkmanager clang\" >> /mnt/gentoo/root/etc/portage/make.conf

Add the CPU flags for your host to /mnt/gentoo/root/etc/portage/make.conf. Make sure you replace the newly added line to the format CPU_FLAGS_X86="aes .....".

livecd ~# echo $(cpuid2cpuflags) >> /mnt/gentoo/root/etc/portage/make.conf

Now add the video-cards depending on your machine. On this machine, I had an AMD video card.

livecd ~# echo VIDEO_CARDS=\"amdgpu radeonsi\" >> /mnt/gentoo/root/etc/portage/make.conf

Add some miscellaneous devices as well. libinput provides input handling for display servers.

livecd ~# echo INPUT_DEVICES=\"libinput\" >> /mnt/gentoo/root/etc/portage/make.conf
livecd ~# echo SANEBACKENDS=\"hp\" >> /mnt/gentoo/root/etc/portage/make.conf

Within the portage configuration file /mnt/gentoo/root/etc/portage/make.conf update the value of the variable COMMON_FLAGS to COMMON_FLAGS="-march=native -O2 -pipe" Also select from the worldwide mirrors to download software from.

livecd ~# mirrorselect -i -o >> /mnt/gentoo/root/etc/portage/make.conf

Mount the system files required to prepare the target computer's chroot environment.

livecd ~# mount --types proc /proc /mnt/gentoo/root/proc
livecd ~# mount --rbind /sys /mnt/gentoo/root/sys
livecd ~# mount --make-rslave /mnt/gentoo/root/sys
livecd ~# mount --rbind /dev /mnt/gentoo/root/dev
livecd ~# mount --make-rslave /mnt/gentoo/root/dev
livecd ~# mount --bind /run /mnt/gentoo/root/run
livecd ~# mount --make-slave /mnt/gentoo/root/run

Enter the chroot environment.

livecd ~# umount /mnt/gentoo/home /mnt/gentoo/data
livecd ~# chroot /mnt/gentoo/root /bin/bash
livecd ~# source /etc/profile
livecd ~# export PS1="(chroot) ${PS1}"
(chroot) livecd ~# export PS1="(chroot) ${PS1}"
(chroot) livecd ~# mkdir -p /home /media/data
(chroot) livecd ~# mount /dev/mapper/osvg-gentoo--home /home
(chroot) livecd ~# mount /dev/mapper/osvg-data /media/data
(chroot) livecd ~# swapon /dev/mapper/osvg-swap

Prepare the EFI System Partition.

(chroot) livecd ~# mount /dev/mapper/luks_boot /boot
(chroot) livecd ~# mkdir -p /boot/efi 
(chroot) livecd ~# mount /dev/nvme0n1p3 /boot/efi

Synchronise emerge's software package list with upstream mirrors

(chroot) livecd ~# emerge-webrsync

Setup Locale Details

(chroot) livecd ~# echo "Europe/London" > /etc/timezone
(chroot) livecd ~# echo "C.UTF8 UTF-8" >> /etc/locale.gen
(chroot) livecd ~# echo "en_GB ISO-8859-1" >> /etc/locale.gen
(chroot) livecd ~# echo "en_GB.UTF-8 UTF-8" >> /etc/locale.gen
(chroot) livecd ~# locale-gen
(chroot) livecd ~# env-update
Configuring the Linux Kernel

Obtain all kernel related gentoo packages.

(chroot) livecd ~# mkdir -p /etc/portage/package.license 
(chroot) livecd ~# echo "sys-kernel/linux-firmware linux-fw-redistributable" >> /etc/portage/package.license/kernel
(chroot) livecd ~# emerge --ask --quiet-build sys-kernel/linux-firmware sys-fs/cryptsetup sys-kernel/gentoo-sources sys-kernel/genkernel 
(chroot) livecd ~# eselect kernel set 1  # this should link /usr/src/linux to current kernel source
(chroot) livecd ~# echo "sys-boot/grub:2 device-mapper" > /etc/portage/package.use/grub2
(chroot) livecd ~# emerge --ask --quiet-build sys-boot/grub sys-fs/genfstab
## Ensure all the relvant drives (including swap are already mounted / turned on
(chroot) livecd ~# genfstab -Up / >> /etc/fstab 
## add noauto to the /boot and /boot/efi mount-points in /etc/fstab

Generate a LUKS key and add the generated key to the block device holding the encrypted partition. In my case, this was luks_boot (/dev/nvme0n1p2) and optionallyluks_root (/dev/nvme0n1p4). This will be the key that is used by the kernel-initramfs to decrypt and mount the encrypted LVM volume and (optionally) the /boot partition. There is a good argument to not automatically decrypt the /boot partition. This is why I have decided it is optional. Remember: We are adding this newly generated key to Key-slot:0 of luks_root -- this is why we carefully added the original-key during disk-partitioning in Key-slot:1. Using Key-slot:0 will make it faster during actual booting and each key is tried in sequence. link.

(chroot) livecd ~# mkdir -p /etc/luks/mnt/key
(chroot) livecd ~# dd if=/dev/urandom of=/etc/luks/mnt/key/boot_os.keyfile bs=4096 count=1
(chroot) livecd ~# chmod u=rx,go-rwx /etc/luks
(chroot) livecd ~# chmod u=r,go-rwx /etc/luks/mnt/key/boot_os.keyfile
(chroot) livecd ~# cryptsetup --key-slot=0 /dev/nvme0n1p4  /etc/luks/mnt/key/boot_os.keyfile  # luks_root
(chroot) livecd ~# cryptsetup --key-slot=1 /dev/nvme0n1p2  /etc/luks/mnt/key/boot_os.keyfile  # luks_boot
(chroot) livecd ~# echo "luks_boot UUID=$(blkid -s UUID -o value /dev/nvme0n1p2) /etc/luks/mnt/key/boot_os.keyfile luks,discard" >> /etc/crypttab
(chroot) livecd ~# echo "luks_root UUID=$(blkid -s UUID -o value /dev/nvme0n1p4) /etc/luks/mnt/key/boot_os.keyfile luks,discard" >> /etc/crypttab

NOTE: The key should be generated in a directory with the following pattern ${INITRAMFS_OVERLAY}/mnt/key. The genkernel tool when provided the INITRAMFS_OVERLAY variable will use this overlay within its filesystem. The kernel will then look for the internal key in /mnt/key.

While you could spend a long time configuring the kernel, I think it is easier to use genkernel to generate a kernel with a lot of options. We can always slim down the kernel afterwards. We can see the list of kernels with eselect kernel list.

(chroot) livecd ~# eselect kernel set 1  

Set-up the following configurations in /etc/genkernel.conf. NOTE: Without the INITRAMFS_OVERLAY, the initramfs kernel cannot decrypt the enncrypted block device holding the LVMs for /root, /home etc.

NOCOLOR="false"
LVM="yes"
LUKS="yes"
GK_SHARE="${GK_SHARE:-/usr/share/genkernel}"
CACHE_DIR="/var/cache/genkernel"
DISTDIR="${GK_SHARE}/distfiles"
LOGFILE="/var/log/genkernel.log"
LOGLEVEL=1
ZFS="no"
BTRFS="no"
XFSPROGS="no"
DEFAULT_KERNEL_SOURCE="/usr/src/linux"
INITRAMFS_OVERLAY="/etc/luks"

Now execute genkernel and prune as much of the kernel config that you don't need before executing. (Ensure that /boot and /boot/efi are mounted)!

(chroot) livecd ~# genkernel --menuconfig --luks --lvm --no-zfs all
Configuring the GRUB bootloader

Ensure the following settings are inserted into the Grub configuration file in /etc/default/grub

GRUB_DISTRIBUTOR="Gentoo"
GRUB_TIMEOUT=3
GRUB_TIMEOUT_STYLE=menu
GRUB_DISABLE_LINUX_PARTUUID=false
GRUB_PRELOAD_MODULES="part_gpt part_msdos lvm"
GRUB_CMDLINE_LINUX_RECOVERY="recovery"
GRUB_ENABLE_CRYPTODISK=y

Also add the commandline for the linux kernel during boot before downloading and installing grub

(chroot) livecd ~# echo GRUB_CMDLINE_LINUX=\"keymap=uk dolvm crypt_root=UUID=$(blkid -s UUID -o value /dev/nvme0n1p4) root_key=boot_os.keyfile root_trim=yes resume=/dev/osvg/swap\" >> /etc/default/grub
(chroot) livecd ~# emerge --ask --quiet-build sys-boot/grub
(chroot) livecd ~# mkdir /boot/grub
(chroot) livecd ~# grub-mkconfig -o /boot/grub/grub.cfg 
(chroot) livecd ~# grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id="grub"

This should install various bootloader stages in their respective locations. Set keymap=uk in the file /etc/conf.d/keymaps. Otherwise, a recovery shell dropping you into a different keymap can be frustrating for passwords and debugging in a shell.

Preparing to reboot into our newly installed bare-bones system.

We are now ready to shutdown and reboot into our newly installed system. Unmount all the mount points, bind-mounts and dmcrypt. First set a root password for the new system. Then unmount all our devices.

(chroot) livecd ~# passwd     #Set new password
(chroot) livecd ~# umount /boot/efi
(chroot) livecd ~# umount /boot
(chroot) livecd ~# cryptsetup close luks_boot 
(chroot) livecd ~# swapoff /dev/mapper/osvg-swap
(chroot) livecd ~# umount /media/data /home
(chroot) livecd ~# exit
livecd ~# umount /mnt/gentoo/root/proc
livecd ~# umount --recursive /mnt/gentoo/root/dev /mnt/gentoo/root/sys /mnt/gentoo/root/run
livecd ~# shutdown -Ph now 

Now reboot into the newly installed system and put in the password for grub. This should then drop you into a prompt for the root password for the new system.

(Optional): It might be a good idea to automatically decrypt the encrypted /boot block device so that we can very simply just use a mount /boot command that was earalier set up in /etc/fstab. We add entries for the dmcrypt service to automatically decrypt /boot during bootup and start the dmcrypt service

hostname ~# echo "target=luks_boot" >> /etc/conf.d/dmcrypt
hostname ~# echo source=UUID=\"$(blkid -s UUID -o value /dev/nvme0n1p2)\" >> /etc/conf.d/dmcrypt
hostname ~# echo "key=/etc/luks/mnt/key/boot_os.keyfile" >> /etc/conf.d/dmcrypt
hostname ~# rc-update add dmcrypt boot
hostname ~# rc-service dmcrypt start

Conclusion

This will get gentoo booting into a shell. Modern desktop computing is however a lot more. I will chronicle my system setup in a further post. My gentoo desktop installation saga continues in Part-II.