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.
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?
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
- For ethernet, configure the wired ethernet interface (starts with enp...)
- 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.