User:Screenager/install/encrypted detached header lvm unified gentoo kernel image customized dracut

From Gentoo Wiki
Jump to:navigation Jump to:search

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

Important
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

Note
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
Note
If the device got erased just now, it might be necessary to run
root #partprobe /dev/sda
before lsblk will show the changes

Creating a partition scheme

Tip
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'.
Tip
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
Tip
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.
Warning
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
Note
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

FILE /etc/portage/package.use/lvm2
sys-fs/lvm2 lvm
Note
It might be better to do this after installing the kernel if using a reasonable new gentoo install media 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

Important
It might be a good idea to specify a key map when facing a password input
FILE /etc/dracut.conf
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"
Note
The resume kernel command line points to the UUID of the swap partition to allow for hibernation

Read 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:

CODE module-setup.sh
#!/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

CODE 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
Tip
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:

FILE /etc/kernel/install.conf
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

FILE /etc/portage/package.use/systemd-utils
sys-apps/systemd-utils kernel-install boot -ukify

configure the installkernel package to not use systemds kernel-install:

FILE /etc/portage/package.use/installkernel
sys-kernel/installkernel -systemd

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

CODE 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")
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

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

FILE /etc/dracut.conf
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

CODE 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.