2017-06-30

Installing Linux Mint 18/Ubuntu 16.04: Encrypted ZFS Root and /boot Partitions

A while ago I cobbled together a spate of howtos into a singular method for installing a root-on-ZFS Ubuntu/Mint system with LUKS. Since then, I've kept reading on the subject and I found a solution for a long-standing problem with Linux/GRUB OSes. I lamented that there wasn't a good way to boot your machine using full-disk encryption, so even if all of your /home and system data is secure when the system is powered off, you still had this pesky /boot partition hanging around out in the open. There's a big Achilles' heel in your nice, secure disk encryption setup if your bootloader and kernel are just sitting ducks.

Turns out, there's a way you can really get full disk encryption with LUKS and GRUB. With a few changes to the previous instructions, we can get a system that runs ZFS on root in a LUKS-encrypted container and get an LUKS-encrypted /boot partition as well.

For stability purposes, the /boot partition will remain ext2/3-formatted. There was a recent mailing list thread crowing about ext2's "worse is better" design philosophy that I found quite entertaining.

The following is a terse set of instructions that skip a lot of explanation. Refer to the previous instructions for further details where desired. All the usual caveats still apply: do not perform these steps on a disk that contains data you care about. Be comfortable with the concepts of using disk partitioning tools and ZFS. Your actual mileage may vary. Not applicable where void by law. Safety not guaranteed. Use at your own risk. This means you.

Boot the Linux Mint 18+ (or Ubuntu 16.04+) ISO. Open a terminal and become root.

sudo su
killall xscreensaver

Identify your storage device. This is usually /dev/sda, and we use that as an example in this howto. Wipe the partition table off of this device with dd or, if you prefer, wipefs.

ROOTDISK=/dev/sda
wipefs --force --all ${ROOTDISK}
# or:
# dd if=/dev/zero of=${ROOTDISK} bs=1M count=2

Set up a new partition table on the disk. Create one partition for your encrypted /boot container and one for your encrypted ZFS pool.

/sbin/parted --script ${ROOTDISK} mklabel msdos
/sbin/parted --script --align optimal ${ROOTDISK} mkpart primary  1MiB 513MB  # encrypted /boot
/sbin/parted --script --align optimal ${ROOTDISK} mkpart primary 513MB  100%  # cryptroot
/sbin/parted --script ${ROOTDISK} set 1 boot on

Our steps now begin to differ from the original howto. Instead of one LUKS container, we create and mount two. I first started experimenting with containers named "cryptroot" and "cryptboot" and you can imagine how quickly that got confusing. Among other things, /boot holds the kernel so I call the LUKS container that will hold /boot "cryptkern" here to reduce confusion.

cryptsetup luksFormat -h sha512 ${ROOTDISK}1
cryptsetup luksFormat -h sha512 ${ROOTDISK}2
cryptsetup luksOpen  ${ROOTDISK}1 cryptkern
cryptsetup luksOpen  ${ROOTDISK}2 cryptroot

Install the ZFS utilities on the live CD session. You can also fetch these .DEB files and store them locally, but that is not covered here.

apt update
apt install -y zfsutils-linux
zpool create -O mountpoint=none -O compression=lz4 -O atime=off -o ashift=12 zmint /dev/mapper/cryptroot
zfs   create                       zmint/root
zfs   create -o mountpoint=/       zmint/root/mint18
zpool set bootfs=zmint/root/mint18 zmint

Additional ZFS datasets can be created at this point, but this howto skips them for simplicity.

Export the zpool and reimport it under /mnt.

zpool export -a
zpool import -R /mnt zmint

Format and mount the /boot partition. Note that the mkfs.ext3 command is formatting the unlocked LUKS container device and not ${ROOTDISK}1, which is the LUKS container itself.

mkfs.ext3 /dev/mapper/cryptkern
mkdir /mnt/boot
mount -o noatime /dev/mapper/cryptkern /mnt/boot


Install the OS to /mnt. I use unsquashfs.

apt install -y squashfs-tools
time unsquashfs -f -d /mnt/ /media/cdrom/casper/filesystem.squashfs
cp -v -p /run/resolvconf/resolv.conf /mnt/run/resolvconf/resolv.conf
cp -v -p /media/cdrom/casper/vmlinuz /mnt/boot/vmlinuz-`uname -r`

A totally random aside:

There's a lot of banter online about the merits and deficiencies of using /dev/random versus /dev/urandom and which OSes have greater or fewer differences between the two. Even professionals like to bicker about it. Ultimately, I suggest we all start using /dev/arandom. He who fears PRNG-deciphering nation states and loves wearing a tinfoil hat can make his own decisions. Where you value speed, I find haveged to be great at dramatically improving PRNG performance if you trust your machine's rand()-making device. It's less useful here than when creating enormously-sized PGP keys, but it's handy to have around.

# Optional
apt install -y haveged
haveged

Once you trust your randomness, create a LUKS decryption key file for each container. This prevents us from needing to type decryption passwords for each container every time we boot.

time dd bs=512 count=4 if=/dev/random iflag=fullblock of=/mnt/boot/rootkey.bin
time dd bs=512 count=4 if=/dev/random iflag=fullblock of=/mnt/root/kernelkey.bin

cryptsetup luksAddKey ${ROOTDISK}1 /mnt/root/kernelkey.bin
cryptsetup luksAddKey ${ROOTDISK}2 /mnt/boot/rootkey.bin

chmod 0 /mnt/root/kernelkey.bin
chmod 0 /mnt/boot/rootkey.bin

Mount important system directories into /mnt and chroot to the new system:

cd /
for i in /dev /dev/pts /proc /sys; do mount -B $i /mnt$i; done
chroot /mnt /bin/bash --login

Write your fstab:

/dev/mapper/cryptkern /boot ext3 defaults,noatime 0 2
/dev/mapper/cryptroot /      zfs defaults         0 0

Configure the system. This section is abbreviated for simplicity. See the original instructions for details.

passwd
passwd -u root
ln -s /proc/self/mounts /etc/mtab
echo myhostname > /etc/hostname
echo 127.0.0.1 myhostname >> /etc/hosts
dpkg-reconfigure tzdata
# add a user account, etc...

Get the list of UUIDs for your system's partitions. You will use these UUIDs for decrypting the LUKS container partitions and in the bootloader.

ls -l /dev/disk/by-uuid

Write your crypttab. If your cryptkern partition is /dev/sda1, use the sda1 UUID for that line in crypttab and so on. crypttab is evaluated from top to bottom, so ordering matters here: I put the cryptroot line at the top and the cryptkern line beneath it, since the key for cryptkern is kept in the cryptroot container.

vi /etc/crypttab
# Add these lines:
cryptroot UUID=UUIDHERE /rootkey.bin        luks,keyscript=/bin/cat
cryptkern UUID=UUIDHERE /root/kernelkey.bin luks,discard

Write a hook script for initramfs to get a copy of the key to decrypt cryptroot.

vi /etc/initramfs-tools/hooks/crypto_keyfile
# Add these lines:
#!/bin/sh
cp -p /boot/rootkey.bin "${DESTDIR}"

Make the hook script executable.

chmod +x /etc/initramfs-tools/hooks/crypto_keyfile

Install ZFS and a ZFS-aware initramfs on the installed system.

apt update # again
apt install -y zfsutils-linux
apt install -y zfs-initramfs
cp -v -p /lib/udev/rules.d/60-zpool.rules /etc/udev/rules.d/

Edit /etc/default/grub. This is where you'll point to the encrypted root partition.

vi /etc/default/grub
# Make the following changes
GRUB_CMDLINE_LINUX_DEFAULT=""
GRUB_CMDLINE_LINUX="cryptdevice=UUID=UUIDHERE:cryptroot"
GRUB_ENABLE_CRYPTODISK=y

Note that you're NOT putting the cryptkern UUID and LUKS container here. Your boot sequence will be:

  1. Power on
  2. Get password for cryptkern
  3. Load initramfs
  4. initramfs loads key for cryptroot from /boot
  5. Mount /
  6. System loads key to mount cryptkern from /root

You could use passwords for all of your LUKS containers if you really wanted to do so, but keys are quicker and relatively safe if you can wrap you head around this ping ponging of device decryption.

Symlink your cryptroot device under /dev so updating the bootloader won't throw an error.

ln -sf /dev/mapper/cryptroot /dev
echo 'ENV{DM_NAME}=="cryptroot", SYMLINK+="cryptroot"' > /etc/udev/rules.d/99-cryptroot.rules

Update your initramfs and bootloader.

update-initramfs -c -k all
update-grub
grub-install /dev/sda

Quit the chroot, ummount everything, and reboot.

exit
umount /mnt/dev/pts
umount /mnt/dev
umount /mnt/proc
umount /mnt/sys
umount /mnt/boot
zfs umount -a
zpool export -a
reboot

If all goes according to plan, after POST you'll get a screen like this:

Attempting to decrypt master key...
Enter passphrase for hd0.msdos1 (a9e29f6295bc49919d5ed7820f941974):

That's hd0 (your disk), msdos1 (your MBR partition table), and as an example, a9e29f6295bc49919d5ed7820f941974, which corresponds to the UUID of your cryptkern LUKS container as per /dev/disk/by-uuid. If you type your password correctly, you'll see this:

Slot 0 opened

And your boot sequence will proceed as intended. When necessary to decrypt each LUKS container, the boot sequence will use to the keys you specified in /etc/crypttab and not prompt for manual input.

External References and Resources

http://www.pavelkogan.com/2014/05/23/luks-full-disk-encryption

https://community.linuxmint.com/tutorial/view/2026

No comments: