Full disk encryption on Fedora 26

Suppose that we have installed a fresh Fedora Core 26 with the default settings. Then we have:

[root@localhost ~]# fdisk -l /dev/vda
Disk /dev/vda: 8 GiB, 8589934592 bytes, 16777216 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xdefe279a

Device     Boot   Start      End  Sectors Size Id Type
/dev/vda1  *       2048  2099199  2097152   1G 83 Linux
/dev/vda2       2099200 16777215 14678016   7G 8e Linux LVM
[root@localhost ~]# pvs
  PV         VG     Fmt  Attr PSize PFree
  /dev/vda2  fedora lvm2 a--  7.00g    0 
[root@localhost ~]# lvs
  LV   VG     Attr       LSize   Pool Origin Data%  Meta%  Move Log Cpy%Sync Convert
  root fedora -wi-ao----   6.20g                                                    
  swap fedora -wi-ao---- 820.00m                                                    
[root@localhost ~]# df / /boot
Filesystem              1K-blocks    Used Available Use% Mounted on
/dev/mapper/fedora-root   6486016 1192916   5293100  19% /
/dev/vda1                  999320  119000    811508  13% /boot

This is a fairly standard partitioning scheme. We will add another disk of the same size (8g), which will be completely encrypted, and will transfer all the data to it.

[root@localhost ~]# grep vd /proc/partitions 
 252        0    8388608 vda
 252        1    1048576 vda1
 252        2    7339008 vda2
 252       16    8388608 vdb

The whole procedure will be performed in several stages, first we will encrypt our operating system, then we will encrypt the /boot partition.

Make PV encrypted

Let's create a partition on the added disk, leaving a some space before it.

Disk /dev/vdb: 8 GiB, 8589934592 bytes, 16777216 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xb6d5aab9

Device     Boot  Start      End  Sectors  Size Id Type
/dev/vdb1       512000 16777215 16265216  7.8G 8e Linux LVM

As you can see, I left 512m of free space before the data partition, we use it later. Now we format the data partition as a encrypted volume:

[root@localhost ~]# cryptsetup luksFormat /dev/vdb1

WARNING!
========
This will overwrite data on /dev/vdb1 irrevocably.

Are you sure? (Type uppercase yes): YES
Enter passphrase: 
Verify passphrase:
[root@localhost ~]# cryptsetup isLuks /dev/vdb1 && echo Success
Success
[root@localhost ~]# cryptsetup luksDump /dev/vdb1

According to the dump, Key Slot 0 is used by the passphrase you entered when setting up encryption. Start (open) the encrypted volume:

[root@localhost ~]# cryptsetup luksOpen /dev/vdb1 pv_fedora
Enter passphrase for /dev/vdb1: 
[root@localhost ~]# ls -l /dev/mapper/pv_fedora 
lrwxrwxrwx. 1 root root 7 Jan 16 13:31 /dev/mapper/pv_fedora -> ../dm-2
[root@localhost ~]# dmsetup table | grep pv_fedora
pv_fedora: 0 16261120 crypt aes-xts-plain64 0000000000000000000000000000000000000000000000000000000000000000 0 252:17 4096

You can save the key in a file, avoiding entering the passphrase manually at each boot. Since this key will be stored on the encrypted volume (/boot will be encrypted somewhat later), this can be harmless.

Generate random garbage in a file:

[root@localhost ~]# dd if=/dev/urandom of=/root/.passphrase bs=32 count=1
[root@localhost ~]# chmod 600 /root/.passphrase
[root@localhost ~]# cryptsetup luksAddKey /dev/vdb1 /root/.passphrase
Enter any existing passphrase:
[root@localhost ~]# cryptsetup luksDump /dev/vdb1

Now luksDump shows the second slot in use.

Create /etc/crypttab file.

[root@localhost ~]# blkid /dev/vdb1
/dev/vdb1: UUID="0cf9ef49-aa24-43a7-98a3-46e693ac315c" TYPE="crypto_LUKS" PARTUUID="b6d5aab9-01"
[root@localhost ~]# cat /etc/crypttab
pv_fedora UUID=0cf9ef49-aa24-43a7-98a3-46e693ac315c /root/.passphrase discard
[root@localhost ~]# chmod 400 /etc/crypttab

The initrd image must be able to run the encrypted volume before initializing the root LV. So the next step is to build such an initrd before we actually move the data.

Edit /boot/grub2/grub.cfg, replacing rhgb quiet with rd.break. This keyword will launch the bash prompt immediately after mounting the root volume and help us debug the initrd.

Use the following commands to create the initrd and then inspect it. Check the contents of the etc/crypttab file in the initrd. If it's not empty, you've done a good job.

[root@localhost ~]# mkdir /tmp/i
[root@localhost ~]# cd /tmp/i
[root@localhost i]# mv /boot/initramfs-$(uname -r).img /boot/initramfs-$(uname -r).img.old
[root@localhost i]# dracut --install /root/.passphrase --force -a "crypt lvm bash" --add-device /dev/mapper/pv_fedora /boot/initramfs-$(uname -r).img
[root@localhost i]# gzip -dc /boot/initramfs-$(uname -r).img | cpio -i
89652 blocks
[root@localhost i]# cat etc/crypttab 
pv_fedora /dev/disk/by-uuid/0cf9ef49-aa24-43a7-98a3-46e693ac315c none discard
[root@localhost i]# ll root/.passphrase 
-rw-------. 1 root root 32 Jan 29 17:44 root/.passphrase

NOTE: The UUID should not be quoted in the /etc/crypttab file, otherwise the dracut will ignore it. (Bug ?)

The familiar mkinird command is now the shell wrapper for the dracut command and is less flexible in its options than dracut itself, so the dracut command is used directly without a wrapper. Here, the --add-device option is required, otherwise dracut will not include our device in the initrd as it detects that device still not used by the system yet and does not required for boot process. The --install option will add our key to the initrd.

Restart the server now. Because of the "rd.break" option added to grub.cfg, you can verify that the initrd has successfully activated our encrypted volume. Use "exit" to exit the debug shell and continue the download.

Once everything works as it should, move the data to encrypted volume:

[root@localhost ~]# pvcreate /dev/mapper/pv_fedora 
  Physical volume "/dev/mapper/pv_fedora" successfully created.
[root@localhost ~]# vgextend fedora /dev/mapper/pv_fedora
  Volume group "fedora" successfully extended
[root@localhost ~]# pvs
  PV                    VG     Fmt  Attr PSize PFree
  /dev/mapper/pv_fedora fedora lvm2 a--  7.75g 7.75g
  /dev/vda2             fedora lvm2 a--  7.00g    0 
[root@localhost ~]# pvmove /dev/vda2
 ..
  /dev/vda2: Moved: 100.00%
[root@localhost ~]# vgreduce fedora /dev/vda2
  Removed "/dev/vda2" from volume group "fedora"
[root@localhost ~]# pvremove /dev/vda2
  Labels on physical volume "/dev/vda2" successfully wiped.

Now the data had migrated to encrypted volume and you can recheck whole procedure by rebooting the server.

Encrypting /boot

Add another partition in reserved space on disk vdb:

Disk /dev/vdb: 8 GiB, 8589934592 bytes, 16777216 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xb6d5aab9

Device     Boot  Start      End  Sectors  Size Id Type
/dev/vdb1       512000 16777215 16265216  7.8G 8e Linux LVM
/dev/vdb2         2048   511999   509952  249M 83 Linux

Partition table entries are not in disk order.

Command (m for help): w
The partition table has been altered.
Calling ioctl() to re-read partition table.
Re-reading the partition table failed.: Device or resource busy

The kernel still uses the old table. The new table will be used at the next reboot or after you run partprobe(8) or kpartx(8).

[root@localhost ~]# partprobe /dev/vdb
[root@localhost ~]# grep vdb /proc/partitions 
 252       16    8388608 vdb
 252       17    8132608 vdb1
 252       18     254976 vdb2

Make the partition encrypted, format it as EXT4 and copy everything from /boot there:

[root@localhost ~]# cryptsetup luksFormat /dev/vdb2

WARNING!
========
This will overwrite data on /dev/vdb2 irrevocably.

Are you sure? (Type uppercase yes): YES
Enter passphrase: 
Verify passphrase: 
[root@localhost ~]# cryptsetup luksOpen /dev/vdb2 boot
Enter passphrase for /dev/vdb2: 
[root@localhost ~]# mkfs.ext4 -j -m0 /dev/mapper/boot 
mke2fs 1.43.4 (31-Jan-2017)
Creating filesystem with 252928 1k blocks and 63240 inodes
Filesystem UUID: 886ffdba-ec1a-47c1-8e9e-92ee2679fc40
Superblock backups stored on blocks: 
        8193, 24577, 40961, 57345, 73729, 204801, 221185

Allocating group tables: done                            
Writing inode tables: done                            
Creating journal (4096 blocks): done
Writing superblocks and filesystem accounting information: done 

[root@localhost ~]# mount /dev/mapper/boot /mnt
[root@localhost ~]# rsync -av /boot/ /mnt/

Fix /etc/fstab to mount new encrypted volume on /boot instead of original:

[root@localhost ~]# blkid /dev/mapper/boot 
/dev/mapper/boot: UUID="886ffdba-ec1a-47c1-8e9e-92ee2679fc40" TYPE="ext4"
[root@localhost ~]# grep /boot /etc/fstab
UUID=886ffdba-ec1a-47c1-8e9e-92ee2679fc40 /boot                   ext4    defaults        1 2
[root@localhost ~]# umount /boot /mnt
[root@localhost ~]# mount /boot
[root@localhost ~]# df /boot
Filesystem       1K-blocks   Used Available Use% Mounted on
/dev/mapper/boot    240840 138988     97756  59% /boot

Meanwhile, lets add an encrypted volume to /etc/crypttab:

[root@localhost ~]# cryptsetup luksAddKey /dev/vdb2 /root/.passphrase
Enter any existing passphrase:
[root@localhost ~]# blkid /dev/vdb2
/dev/vdb2: UUID="603a3e1e-d86b-41de-8b61-96cf3926b468" TYPE="crypto_LUKS" PARTUUID="b6d5aab9-02"
[root@localhost ~]# cat /etc/crypttab
pv_fedora UUID=0cf9ef49-aa24-43a7-98a3-46e693ac315c /root/.passphrase discard
boot UUID=603a3e1e-d86b-41de-8b61-96cf3926b468 /root/.passphrase discard

Note that fstab uses the UUID of the file system, and crypttab uses the UUID of the physical block device.

Fix /etc/default/grub as following:

[root@localhost ~]# vi /etc/default/grub 
[root@localhost ~]# cat !$
cat /etc/default/grub
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
GRUB_DEFAULT=saved
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="console"
GRUB_DISABLE_RECOVERY="true"
GRUB_CMDLINE_LINUX="rd.lvm.lv=fedora/root rd.lvm.lv=fedora/swap rd.luks=1 rd.shell"
GRUB_ENABLE_CRYPTODISK=y

And rebuild grub.cfg:

[root@localhost ~]# grub2-mkconfig > /boot/grub2/grub.cfg 
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-4.11.8-300.fc26.x86_64
Found initrd image: /boot/initramfs-4.11.8-300.fc26.x86_64.img
Found linux image: /boot/vmlinuz-0-rescue-d91bc70d6778494dafacaebdc6b03e73
Found initrd image: /boot/initramfs-0-rescue-d91bc70d6778494dafacaebdc6b03e73.img
done
[root@localhost ~]# grep crypt /boot/grub2/grub.cfg
 ..

Check that some lines related to cryptodisk were added to cfg file. Now, install grub on vdb:

[root@localhost ~]# grub2-install /dev/vdb
Installing for i386-pc platform.
Installation finished. No error reported.

Shutdown server, disconnect vda and config server to boot from vdb. Boot it.

Now our server runs on a fully encrypted disk, but GRUB requests a password to decrypt /boot at the boot time. It's not so convenient. The server should be booted automatically and without human intervention. A promising project is the johnlane GRUB cryptomount extention, which allows you to hide the key for the GRUB. However, the configuration file still open and allows reverse decryption.

In addition, this is not a solution for creating a closed packed embedded system, because you must provide at least a key for GRUB, and the rest can be decrypted in reverse order.


Updated on Mon Jan 29 22:10:48 IST 2018 by Oleg Volkov More documentations here