User:Screenager/install/encrypted detached header lvm unified gentoo kernel image customized dracut
Introduction
Why
Gentoo allows for extreme customization starting with the installation process. This guide is meant to explain an advanced encrypted installation using logical volume management underneath, aiming to extend the 'Preparing the disks' section in the official handbook. The idea is to detach the header of a rootfs encrypted gentoo install and put it inside a luks container which will allow for somewhat safe removal of the header from the device without the need to perform a secure wipe. Besides that dracut will be modified to allow full control of the boot process. This will allow for adjustments in security strategy, for example before traveling with a laptop.
Prerequisites
Block Devices
Without understanding block devices fully, it will be hard to follow this guide. There will be encapsulating of abstraction layers. If this sounds scary, careful reading of the following articles is advised.
It is important to understand block devices. The official handbook offers information for that: Link
dm-crypt
This software will be responsible for creating an encrypted container on your block device, which in itself will be treated as just another block device when mounted. More information here: Link
LVM - Logical Volume Manager
This will add more block device abstraction layers. Again it is advised to read the wiki article beforehand: Link
Secure Wipe
This guide will be written for installation on a SSD, therefore a secure wipe beforehand is advised: Link
Preparing the disks
This guide will always prefer PARTLABELS and PARTUUID due to the nature of luks partitions not having a filesystem. Using the same PARTLABEL it will be possible to have multiple boot USB sticks for a single device
By now Networking should be completed and all Prerequisites should be understood. First identify your block devices, going forward this guide will use
root #
lsblk -o NAME,PARTLABEL,SIZE,FSTYPE,TYPE,MOUNTPOINT
NAME PARTLABEL SIZE FSTYPE TYPE MOUNTPOINT sda 465,8G disk
If the device got erased just now, it might be necessary to run
root #
partprobe /dev/sda
Creating a partition scheme
It is possible to have bigger or smaller efi partitions. Also it is advised to leave about 10-15% of unformatted space when not using discard on a SSD
Invoke fdisk:
root #
fdisk /dev/sda
Welcome to fdisk (util-linux 2.38.1). Changes will remain in memory only, until you decide to write them. Be careful before using the write command. Device does not contain a recognized partition table. Created a new DOS disklabel with disk identifier 0xba70857e. Command (m for help):
Create a GPT partition table
Command (m for help):
g
Created a new GPT disklabel (GUID: 226B0C64-4F00-4A44-86B3-D897AB1F5009).
Then create a efi partition
Command (m for help):
n
Partition number (1-128, default 1): First sector (2048-976773134, default 2048): Last sector, +/-sectors or +/-size{K,M,G,T,P} (2048-976773134, default 976773119): +768M Created a new partition 1 of type 'Linux filesystem' and of size 768 MiB.
Command (m for help):
t
Selected partition 1 Partition type or alias (type L to list all): 1 Changed type of partition 'Linux filesystem' to 'EFI System'.
It is by no means necessary to create a helper partition
Create a helper Partition
Command (m for help):
n
Partition number (2-128, default 2): First sector (1574912-976773134, default 1574912): Last sector, +/-sectors or +/-size{K,M,G,T,P} (1574912-976773134, default 976773119): +256M Created a new partition 2 of type 'Linux filesystem' and of size 256 MiB.
and then create a partition for the luks container spanning the rest of the device
Command (m for help):
n
Partition number (3-128, default 3): First sector (2099200-976773134, default 2099200): Last sector, +/-sectors or +/-size{K,M,G,T,P} (2099200-976773134, default 976773119): Created a new partition 3 of type 'Linux filesystem' and of size 464.8 GiB.
Write the changes to disk
Command (m for help):
w
The partition table has been altered. Calling ioctl() to re-read partition table. Syncing disks.
Partitionlabels may be set with parted or Gparted when working from a GUI.
root #
parted
GNU Parted 3.6 Using /dev/sda Welcome to GNU Parted! Type 'help' to view a list of commands. (parted) name 1 'efi' (parted) name 2 'helper' (parted) name 3 'cryptContainer' (parted) q
Check if everything went right
root #
lsblk -o NAME,PARTLABEL,SIZE,FSTYPE,TYPE,MOUNTPOINT
NAME PARTLABEL SIZE FSTYPE TYPE MOUNTPOINT sda 465.8G disk ├─sda1 efi 768M part ├─sda2 helper 256M part └─sda3 cryptContainer 464.8G part
Preparing luks header storage
In order to be able to 'remove' the detached header in a somewhat secure way it needs to be encrypted first. There are again multiple ways to achieve this. This guide will use a file as a luks container stored on the helper partition. Start by formatting our helper partition:
root #
mkfs.ext4 -T small /dev/sda2
format the efi partition with fat32:
root #
mkfs.vfat -F 32 /dev/sda1
Mount the helper partition
root #
mount /dev/sda2 /mnt/tmp
create a 128MB file and format it
root #
dd if=/dev/zero bs=1M count=128 of=/mnt/tmp/secret.img
root #
cryptsetup -c aes-xts-plain64 -s 512 -y luksFormat /mnt/tmp/secret.img
now decrypt and mount this file
root #
cryptsetup luksOpen /mnt/tmp/secret.img headspace
this will create the headspace block device in /dev/mapper/
the block device is unformatted to change that format it with ext4
root #
mkfs.ext4 -T small /dev/mapper/headspace
mount this block device to the file system
root #
mount /dev/mapper/headspace /mnt/headspace/
now the system is prepared for creation of the 'real' luks container
Creating the main crypt container
Start off by formatting your luks partition as a luks container and store it's header inside the encrypted file container
root #
cryptsetup -c aes-xts-plain64 -s 512 -y luksFormat /dev/sda3 --header /mnt/headspace/header.img
It may not be unwise to use a rather simple password in the beginning. It is possible to destroy the first key-slot later and use a more secure password once everything is running. Take special care in not copying the header until it is fully secured.
If the header image file is lost all data will be unrecoverable
continue with opening and mapping the luks container:
root #
cryptsetup luksOpen /dev/sda3 lukscont --header /mnt/headspace/header.img
this will create the lukscont block device in /dev/mapper/
lsblk should now show the new logical block devices:
root #
lsblk -o NAME,PARTLABEL,SIZE,FSTYPE,TYPE,MOUNTPOINT
NAME PARTLABEL SIZE FSTYPE TYPE MOUNTPOINT loop0 128M crypto_LUKS loop └─headspace 112M ext4 crypt /mnt/headspace sda 465.8G disk ├─sda1 efi 768M vfat part ├─sda2 helper 256M ext4 part /mnt/tmp └─sda3 cryptContainer 464.8G part └─lukscont 464.8G crypt
LVM Setup
With LVM it will be easily possible to encrypt and decrypt multiple partitions with a single password/keyfile. LVM will also allow easy extending or shrinking of partitions as well as percentage based allocation. Start by creating a physical volume:
root #
pvcreate /dev/mapper/lukscont
follwing by creating a volume group within the just created physical volume
root #
vgcreate vg1 /dev/mapper/lukscont
this will create the volume group vg1. Next it is time to create logical volumes for a swap and root partition.
root #
lvcreate --size 16G --name swap vg1
and
root #
lvcreate --size 100G --name root vg1
It will be very easy to create more partitions later or extent the root partition to the full size of the volume group
Next, format and enable the swap partition:
root #
mkswap /dev/vg1/swap
root #
swapon /dev/mapper/vg1-swap
check with:
root #
swapon -d
next format the root partition with ext4
root #
mkfs.ext4 /dev/vg1/root
root #
mount /dev/vg1/root /mnt/gentoo/
root #
mkdir /mnt/gentoo/efi
root #
mount /dev/sda1 /mnt/gentoo/efi/
which will conclude the disk preparation and filesystem setup. Perform a final check with lsblk:
root #
lsblk -o NAME,PARTLABEL,SIZE,FSTYPE,TYPE,MOUNTPOINT
NAME PARTLABEL SIZE FSTYPE TYPE MOUNTPOINT loop0 128M crypto_LUKS loop └─headspace 112M ext4 crypt /mnt/headspace sda 465.8G disk ├─sda1 efi 768M vfat part /mnt/gentoo/efi ├─sda2 helper 256M ext4 part /mnt/tmp └─sda3 cryptContainer 464.8G part └─lukscont 464.8G LVM2_member crypt ├─vg1-swap 16G swap lvm [SWAP] └─vg1-root 100G ext4 lvm /mnt/gentoo
Continue with the next part of the handbook until 'Configuring the bootloader' make sure to skip the 'Configuring the kernel' section. The kernel will be installed later in this guide shortly before rebooting in the new system.
Continuing the installation
Installing additional software
After just completing the 'Installing tools' section continue with installing needed programs. First dm-crypt
root #
emerge --ask sys-fs/cryptsetup
then continue with lvm. First configure the USE variable for the package by creating the file
sys-fs/lvm2 lvm
It might be better to do this after installing the kernel, if using a reasonably new Gentoo install medium, there should be no problem.
root #
emerge --ask sys-fs/lvm2
and configure the service to start at boot time
root #
rc-update add lvm boot
now install dracut
root #
emerge --ask sys-kernel/dracut
proceed to configure dracut
Configuring and modifying dracut
It might be a good idea to specify a key map when facing a password input
add_dracutmodules+=" dm crypt lvm resume "
hostonly="yes"
uefi="yes"
kernel_cmdline="rd.vconsole.keymap=<your_keymap_here> rd.vconsole.unicode resume=UUID=19b6cb99-9f1a-4bb3-a302-191664b9e391"
i18n_vars="/etc/conf.d/keymaps:KEYMAP /etc/rc.conf:UNICODE"
i18n_install_all="yes"
The
resume
kernel command line points to the UUID of the swap partition to allow for hibernationRead dracuts wiki page and/or manual for more information about these options. Dracut should be able to define the system root as lvms /dev/vg1/root logical volume by itself. Because of the complex crypt container setup, the procedure for unlocking these needs to be scripted manually. Remember that the main point is to just replicate in a script what was done before chrooting to create an environment where dracut is able to locate the root partition and swap partition if resuming from hibernate. Once this is understood it will be no problem to change the initial partitioning scheme or even use remote authentication. Dracut allows the installation of additional programs therefore allowing unlimited customization of the boot process.
Start by creating a new folder:
root #
mkdir /usr/lib/dracut/modules.d/92mymod
inside there first create a file that will tell dracut how and what to install
/usr/lib/dracut/modules.d/92mymod/module-setup.sh which will have following content:
#!/bin/bash
check(){
return 0 #will always install this module when dracut gets called
}
install(){
inst_hook initqueue/settled 92 "$moddir/mount-luks.sh" #will run this script in dracuts initqueue repeatedly untill it finds root
}
next create the actual script
/usr/lib/dracut/modules.d/92mymod/mount-luks.sh
#!/bin/bash
#this function will get called repeatedly in dracuts initqueue so there is no need to put a loop inside
mount_local_luks(){
if [ ! -b /dev/vg1/root ] #check if dev/vg1/root is NOT a block device
then #then do
if [ ! -b /dev/mapper/headspace ] #check if /dev/mapper/headspace is NOT a block device
then #then do
mkdir /tmp/headspace
mkdir /tmp/mnt
mount /dev/sda2 /tmp/mnt
dmesg -D #deactivate kernel message console output in order to not clutter the console
read -s -p "Enter first Password:" pword #read the password from keyboard input and save it to variable pword
echo #hacky way to get a newline, puts the next password prompt on a newline
echo $pword | cryptsetup luksOpen /tmp/mnt/secret.img headspace #output the password into cryptsetup to unlock the header
unset pword #removes password from memory
else #continue here instead if /dev/mapper/headspace exists
mount /dev/mapper/headspace /tmp/headspace
read -s -p "Enter second Password:" pword
echo
echo $pword | cryptsetup luksOpen /dev/sda3 lukscont --header /tmp/headspace/header.img --allow-discards #discards get activated here
unset pword
if [ ! -b /dev/mapper/lukscont ] #check if /dev/mapper/lukscont is NOT a block device
then
echo "YOU DIDN'T SAY THE MAGIC WORD" #which would mean that you entered a wrong password
dmesg -E #enables kernel message console output again
umount /tmp/mnt
else #this part will only run when /dev/mapper/lukscont exists
lvm pvscan #lvm scan for physical volumes
lvm vgscan #lvm scan for volume groups
lvm lvscan #lvm scan for logical volumes
lvm vgchange -ay #lvm activate all volume groups
dmesg -E
umount /tmp/mnt #clean up and unmount everything
umount /tmp/headspace
cryptsetup luksClose headspace
fi
fi
fi
}
mount_local_luks
It is possible to hide this function inside the encrypted header container /tmp/headspace an attacker will only see that an additional script will be called inside an invisible block device. This creates a complete new set of oppurtunities; including the possibility to boot different installations with different 'passphrase' checks for ultimate plausible deniability
With that done it is time to install the kernel. Which will be the boot loader at the same time.
Configuring the kernel
First create following configuration file:
layout=uki
uki_generator=dracut
initrd_generator=dracut
continue with installing the firmware
root #
emerge --ask sys-kernel/linux-firmware
if running an Intel CPU you might want to install it's microcode, skip this if using an AMD CPU
root #
emerge --ask sys-firmware/intel-microcode
For the efi stub generation systemd-utils boot and installkernel will be needed. Create and edit following file
sys-apps/systemd-utils kernel-install boot -ukify
configure the installkernel package to not use systemds kernel-install:
sys-kernel/installkernel -systemd dracut
emerge the installkernel package
root #
emerge --ask sys-kernel/installkernel
install the systemd bootloader afterwards with
root #
emerge --ask sys-apps/systemd-utils
and finally proceed to build the kernel:
root #
emerge --ask sys-kernel/gentoo-kernel
after the build and installation is done there will be a vmlinuz-gentoo-dist executable located at /boot/vmlinuz-6.1.67-gentoo-dist.efi. Should the need ever arise, for example after changing the custom script, you can regenerate this file with
root #
emerge --config sys-kernel/gentoo-kernel
now copy the UKI to the efi partition
root #
cp /boot/vmlinuz-6.1.67-gentoo-dist.efi /efi/EFI/gentoo/vmlinuz-6.1.67-gentoo-dist.efi
and add a bootentry to the NVRAM. Install efibootmgr
root #
emerge --ask sys-boot/efibootmgr
then add a new boot entry pointing to your new unified kernel image
root #
efibootmgr --create --disk /dev/sda --part 1 --label "Gentoo" --loader 'EFI/gentoo/vmlinuz-6.1.67-gentoo-dist.efi' --unicode
the installation is finished and should be fully bootable.
Optional: Automating kernel updates
It is possible to create a script that will automate a kernel update and move the old kernel image to /efi/EFI/Boot/bootx64.efi as a failsafe.
Simply copy this script to
/etc/kernel/postinst.d/99kernel-update.sh
#!/bin/bash
if [ ! -d /efi/EFI/Boot ]
then
mkdir /efi/EFI/Boot
fi
string=$(efibootmgr | grep "Gentoo")
boot=${string:4:4}
bootnumi=$(expr $(expr index "$boot" [1-9]) - 1)
bootnum=${boot:$bootnumi}
ckfile=${string##*\\}
ckfile=${ckfile%\)*}
string=$(ls -t /boot | grep "vmlinuz" | grep -v "old")
stringarray=($string)
nkfile=${stringarray[0]}
if [ "$ckfile" == "$nkfile" ]
then
echo "No new kernel version found. No automatic actions taken $nkfile left in /boot"
else
echo "Installing new kernel image..."
mv /efi/EFI/gentoo/$ckfile /efi/EFI/Boot/bootx64.efi
cp /boot/$nkfile /efi/EFI/gentoo/$nkfile
efibootmgr -b $bootnum -B
efibootmgr -c -d /dev/sda -p 1 -L "Gentoo" -l 'EFI/gentoo/'"$nkfile" -u
echo "Kernel Update complete, remember to reboot"
fi
unset nkfile
unset ckfile
unset stringarray
unset string
unset boot
unset bootnumi
unset bootnum
Note that the script may not function when the /efi partition resides on a device other than /dev/sda, so the hardcoded efibootmgr parameter must be adjusted accordingly.
Remember to make it executable with
root #
chmod +x /etc/kernel/postinst.d/99kernel-update.sh
This script will not install a new kernel image if the kernel version does not change. So remember to do it manually if just changing the custom dracut module.
Optional: Plymouth
Plymouth is a program that will provide graphical rendering during initramfs staging; in short: it will add a customizable splash screen to your gentoo installation. This guide will focus on including kde-plasmas breeze theme into the initramfs. Start by emerging it:
root #
emerge --ask sys-boot/plymouth
unfortunately the plymouth module in dracut is currently bugged so the necessary files need to be included and will vary depending on the theme used. Emerge the breeze theme for plymouth
root #
emerge --ask kde-plasma/breeze-plymouth
installed themes are located at /usr/share/plymouth/themes
Next set up the plymouth config
root #
plymouth-set-default-theme breeze
Now the dracut config needs some modification, note that the extra files installed are specifically needed by the breeze theme
add_dracutmodules+=" dm crypt lvm resume plymouth "
hostonly="yes"
uefi="yes"
kernel_cmdline="rd.vconsole.keymap=<your_keymap_here> rd.vconsole.unicode resume=UUID=19b6cb99-9f1a-4bb3-a302-191664b9e391 splash quiet"
i18n_vars="/etc/conf.d/keymaps:KEYMAP /etc/rc.conf:UNICODE"
i18n_install_all="yes"
install_items+=" /usr/share/fonts/noto/NotoSans-Regular.ttf /usr/share/fonts/noto/NotoSerif-Regular.ttf /usr/lib64/plymouth/label.so "
The custom dracut module script will need some upgrading, the password prompts need to interface with plymouth. Plymouth has good fallback handling so even with a broken theme due to missing files, password prompts will usually work. /usr/lib/dracut/modules.d/92mymod/mount-luks.sh
#!/bin/bash
mount_local_luks(){
if [ ! -b /dev/vg1/root ]
then
if [ ! -b /dev/mapper/headspace ]
then
mkdir /tmp/headspace
mkdir /tmp/mnt
mount /dev/sda2 /tmp/mnt
plymouth ask-for-password --prompt="Enter first Password:" | cryptsetup luksOpen /tmp/mnt/secret.img headspace
else
mount /dev/mapper/headspace /tmp/headspace
plymouth ask-for-password --prompt="Enter second Password:" | cryptsetup luksOpen /dev/sda3 lukscont --header /tmp/headspace/header.img --allow-discards
if [ ! -b /dev/mapper/lukscont ]
then
echo "YOU DIDN'T SAY THE MAGIC WORD"
umount /tmp/mnt
else
lvm pvscan
lvm vgscan
lvm lvscan
lvm vgchange -ay
umount /tmp/mnt
umount /tmp/headspace
cryptsetup luksClose headspace
fi
fi
fi
}
mount_local_luks
Remember to regenerate the UKI with
root #
emerge --config sys-kernel/gentoo-kernel
and install it to the efi partition.