setenv bootargs "rd.luks.uuid=ae51db2d-0890-4b1b-abc5-8c10f01da353 root=/dev/mapper/vg-root rootwait <leave the rest as is>"
Full Disk Encryption (FDE) protects our data against unauthorised access in case someone gains physical access to the storage media. This document describes how to install ArchLinux with Full Disk Encryption on ODROID-C2. The encryption method is LUKS with XTS key-size 512 bit (AES-256).
In a nutshell, Full Disk Encryption requires
Encrypting a partition and copying the root filesystem to it.
The kernel to include the dm_crypt
kernel module. In our case this is already included by default therefore we won’t need to re-compile the kernel.
The initramfs
to include the dm_crypt
kernel module and the cryptsetup
binary. We use a tool called dracut
to generate the required initramfs
. Dracut supports the required functionality via the additional modules crypt
and lvm
.
Passing the dracut options for LUKS to the initramfs
via the bootargs
property inside boot.ini
. For example, say that in our case we want the initramfs
to unlock a LUKS volume with UUID ae51db2d-0890-4b1b-abc5-8c10f01da353
and load the root filesystem from the device mapper /dev/mapper/vg-root
. To pass these dracut options we configure the following
setenv bootargs "rd.luks.uuid=ae51db2d-0890-4b1b-abc5-8c10f01da353 root=/dev/mapper/vg-root rootwait <leave the rest as is>"
Note
|
A lot of the steps throughout this document involve editing configuration files. To keep the words to the minimum, we use the above notation as a very concise way to describe such file editing steps. The above notation means
|
Additionally, for a headless setup you will need to enable remote unlocking via SSH as described in Remotely unlock the LUKS rootfs during boot using Dropbear sshd.
Last but not least, if you prefer to use the described functionality out of the box, simply download this OS image. Either way, the current document will provide more technical details in regards to the underlying components and how they work together in a Full Disk Encryption environment.
ODROID-C2
A Linux box from which you will flash the OS image and interact with the ODROID-C2
USB disk with at least 4G capacity
microSD card or eMMC module with at least 4G capacity
(Optional) USB-UART Module Kit for connecting with the ODROID-C2’s serial console. Refer to this post for instructions on how to connect along with explanation why the serial console is highly recommended in this case.
Flash the OS image to the USB disk by following the instructions from https://archlinuxarm.org/platforms/armv8/amlogic/odroid-c2
Replace /dev/mmcblk0
in the following instructions with the device name for the microSD card as it appears on your computer.
If mounted, unmount the partitions of the microSD card
lsblk umount /dev/mmcblk0p1 umount /dev/mmcblk0p2
Zero the beginning of the microSD card
sudo dd if=/dev/zero bs=1M count=8 of=/dev/mmcblk0 sync
Using a tool like GParted, create an MBR/msdos partition table and two partitions on the microSD card
ext4
partition with 128M
size
lvm2
partition occupying the rest of the space (no need to format yet)
Copy the contents of the /boot
directory from the USB disk into the first partition of the microSD card
sudo cp -R /media/user/usb-disk/boot/* /media/user/micro-sd-card-part1/
Create a symbolic link as a workaround for the hardcoded boot.ini path of the alarm/uboot-odroid-c2
cd /media/user/micro-sd-card-part1 sudo ln -s . boot
Flash the bootloader files
sudo ./sd_fusing.sh /dev/mmcblk0
Determine the UUID of the USB disk
$ sudo lsblk -o name,uuid,mountpoint
NAME UUID MOUNTPOINT
sdb
└─sdb1 2b53696c-2e8e-4e61-a164-1a7463fd3785 /media/user/usb-disk
Caution
|
If there are duplicate UUIDs among the partitions of the USB disk and the microSD card then deduplicate them (e.g. sudo tune2fs /dev/sda2 -U $(uuidgen) ) to avoid future conflicts.
|
Configure the boot.ini
to boot from the USB disk. To do so, use the UUID from the previous step to configure the boot.ini
of the microSD card.
setenv bootargs "root=UUID=2b53696c-2e8e-4e61-a164-1a7463fd3785 rootwait <leave the rest as is>"
Unmount, run sync
few times, and remove the microSD card and the USB disk from the Linux box.
Plug the microSD card and the USB disk to the ODROID-C2.
Boot the ODROID-C2 and connect to its serial console. If you need instructions on how to connect to the serial console, refer here.
If all goes well you should boot into the USB disk.
Caution
|
If root=UUID=2b53696c-2e8e-4e61-a164-1a7463fd3785 doesn’t work then try root=/dev/sda1 , root=/dev/sdb1 or whatever device name you see in the console prior to the failed boot (e.g. [ 14.812393] sd 1:0:0:0: [sda] Attached SCSI removable disk ). If still having issues try restarting some times and/or repositioning the USB disk into a different USB port on the ODROID-C2. Don’t worry if it seems to be giving you trouble, as you won’t have to boot to the USB disk again after the first successful boot.
|
The default credentials are alarm/alarm
and root/root
Verify the root filesystem is mounted from the USB disk
df -h
Change the passwords for the alarm
and the root
user. The default credentials are alarm/alarm
and root/root
passwd su passwd
su
pacman -Syu
pacman -S --needed sudo python git rsync lvm2 cryptsetup
(Optional) Setup passwordless sudo for the user alarm
echo 'alarm ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/010_alarm-nopasswd
Install trizen (AUR helper)
sudo pacman -S --needed base-devel
mkdir ~/aur && cd $_
git clone https://aur.archlinux.org/trizen.git
cd trizen
makepkg -si --needed --noconfirm
Install dracut using the trizen tool
trizen -S dracut
Verify the dracut installation by listing modules
dracut --list-modules
If trizen -S dracut
reports an error that aarch64
architecture is not supported by the package, then follow these steps to configure support for aarch64
cd /tmp/trizen-alarm/dracut/
nano PKGBUILD # replace `arch=("i686" "x86_64")` with `arch=("aarch64")`
makepkg -si --needed --noconfirm
If the makepkg
reports an error like dracut-046.tar … FAILED (unknown public key 340F12141EA0994D)
, then type these commands and try again
gpg --full-gen-key gpg --recv-key 340F12141EA0994D
Refer to Makepkg signature checking for more details.
If gpg --full-gen-key
reports the error Key generation failed: No pinentry
, then follow the below steps to configure gpg as described in https://wiki.archlinux.org/index.php/GnuPG#gpg-agent, and try again.
The gpg-agent needs to know how to ask the user for the password.
pinentry-program /usr/bin/pinentry-curses
Reload the gpg-agent
gpg-connect-agent reloadagent /bye
If makepkg
reports missing dependencies error, then upgrade the packages and try again.
sudo pacman -Syu
trizen -Syua
If after running trizen -S dracut
the dracut command is not installed, then try installing it manually without using the trizen AUR helper.
cd /tmp/trizen-alarm/dracut/
makepkg -si --needed --noconfirm
Encrypt the second partition of the microSD card (see also Recommended options for LUKS)
sudo cryptsetup -v -y -c aes-xts-plain64 -s 512 -h sha512 -i 5000 --use-random luksFormat /dev/mmcblk0p2
-v = verbose -y = verify passphrase, ask twice, and complain if they don't match -c = specify the cipher used -s = specify the key size used -h = specify the hash used -i = number of milliseconds to spend passphrase processing (if using anything more than sha1, must be great than 1000) --use-random = which random number generator to use luksFormat = to initialize the partition and set a passphrase /dev/mmcblk0p2 = the partition to encrypt
Unlock the LUKS device and mount it at /dev/mapper/lvm
sudo cryptsetup luksOpen /dev/mmcblk0p2 lvm
Create primary volume, volume group, and logical volume
sudo pvcreate /dev/mapper/lvm
sudo vgcreate vg /dev/mapper/lvm
sudo lvcreate -l 100%FREE -n root vg
Create filesystem
sudo mkfs.ext4 -O ^metadata_csum,^64bit /dev/mapper/vg-root
Mount the new encrypted root volume (logical volume)
sudo mount /dev/mapper/vg-root /mnt
Copy the existing root volume to the new, encrypted root volume (with a 1.5G
installation, completes in about 6 min on an average microSD)
sudo rsync -av \
--exclude=/boot \
--exclude=/mnt \
--exclude=/proc \
--exclude=/dev \
--exclude=/sys \
--exclude=/tmp \
--exclude=/run \
--exclude=/media \
--exclude=/var/log \
--exclude=/var/cache/pacman/pkg \
--exclude=/usr/src/linux-headers* \
--exclude=/home/*/.gvfs \
--exclude=/home/*/.local/share/Trash \
/ /mnt
If the SSH host keys are empty, remove them so that they will be regenerated the next time the sshd starts. This will prevent the memory leak issue as described here.
sudo rm /mnt/etc/ssh/ssh_host*key*
Create some directories and mount the boot partition
sudo mkdir -p /mnt/boot /mnt/mnt /mnt/proc /mnt/dev /mnt/sys /mnt/tmp
sudo mount -t ext4 /dev/mmcblk0p1 /mnt/boot
Register the encrypted volume in crypttab
sudo bash -c 'echo lvm UUID=$(cryptsetup luksUUID /dev/mmcblk0p2) none luks>> /mnt/etc/crypttab'
Configure fstab
/dev/mapper/vg-root / ext4 errors=remount-ro,noatime,discard 0 1
/dev/mmcblk0p1 /boot ext4 noatime,discard 0 2
Generate new initramfs using dracut. The below will add the dracut modules crypt
and lvm
to the initramfs. These modules will prompt for LUKS password during boot and unlock the LUKS volume. Mind that the order of the modules is important.
sudo dracut --force --hostonly -a "crypt lvm" /mnt/boot/initramfs-linux.img
Determine the LUKS UUID
sudo cryptsetup luksUUID /dev/mmcblk0p2 470cc9eb-f36b-40a2-98d8-7fce3285bb89
Configure the rd.luks.uuid
and root
dracut options in bootargs
. These will unlock the LUKS volume and load the rootfs from it during boot.
setenv bootargs "rd.luks.uuid=470cc9eb-f36b-40a2-98d8-7fce3285bb89 root=/dev/mapper/vg-root rootwait <leave the rest as is>"
Caution
|
In the above step, do NOT delete the rest of bootargs , essentially replace root=UUID=2b53696c-2e8e-4e61-a164-1a7463fd3785 with rd.luks.uuid=470cc9eb-f36b-40a2-98d8-7fce3285bb89 root=/dev/mapper/vg-root and leave the rest of bootargs untouched.
|
Unmount and reboot into the LUKS rootfs
sudo umount /mnt/boot sudo umount /mnt sudo reboot
If all goes well you will be prompted to enter the LUKS password during boot.
Verify the LUKS rootfs
df -h
Filesystem Size Used Avail Use% Mounted on
devtmpfs 714M 0 714M 0% /dev
tmpfs 859M 0 859M 0% /dev/shm
tmpfs 859M 8.3M 851M 1% /run
tmpfs 859M 0 859M 0% /sys/fs/cgroup
/dev/mapper/vg-root 1.7G 1.4G 256M 85% /
tmpfs 859M 0 859M 0% /tmp
/dev/mmcblk0p1 120M 26M 86M 23% /boot
tmpfs 172M 0 172M 0% /run/user/1000
Replace 10.0.0.100
in the following instructions with the IP address assigned to the ODROID-C2 by your local DHCP server. Use the fing
tool to find the assigned IP address (e.g. sudo fing 10.0.0.1/24
).
sudo systemctl status sshd journalctl -u sshd -n 100
If the above commands report that sshd
fails with memory allocation error, then
sudo rm /etc/ssh/ssh_host*key* sudo systemctl start sshd
See also memory leak of sshd
Install the dracut module crypt-ssh
trizen -S dracut-crypt-ssh-git
From your Linux box, copy the public SSH key to the appconf/dracut-crypt-ssh/authorized_keys
file on the remote ODROID-C2 server
cat ~/.ssh/*.pub | ssh alarm@10.0.0.100 'umask 077; mkdir -p appconf/dracut-crypt-ssh; touch appconf/dracut-crypt-ssh/authorized_keys; cat >>appconf/dracut-crypt-ssh/authorized_keys'
Configure the crypt-ssh
module
dropbear_acl="/home/alarm/appconf/dracut-crypt-ssh/authorized_keys"
Generate new initramfs using dracut. The below will add the dracut modules network
and crypt-ssh
to the initramfs. Mind that the order of the modules is important.
sudo dracut --force --hostonly -a "network crypt lvm crypt-ssh" /boot/initramfs-linux.img
Enable network access during boot by adding rd.neednet
and ip
dracut options to bootargs
setenv bootargs "rd.neednet=1 ip=10.0.0.100::10.0.0.1:255.255.255.0:archlinux-luks-host:eth0:off rd.luks.uuid=ae51db2d-0890-4b1b-abc5-8c10f01da353 root=/dev/mapper/vg-root rootwait <leave the rest as is>"
If you prefer DHCP instead of static ip, simply replace with ip=dhcp
.
Refer to network documentation of dracut and dracut options for more options (man dracut.cmdline
).
Reboot so that Dropbear starts, allowing for remote unlocking
sudo reboot
From your Linux box, connect to the remote Dropbear SSH server running on the ODROID-C2
ssh -p 222 root@10.0.0.100
Unlock the volume (asks you for the passphrase and sends it to console)
console_auth Passphrase:
If unlocking the device succeeded, the initramfs will clean up itself and Dropbear terminates itself and your connection.
console_peek
Prints what’s on the console
Note
|
There is also the unlock command but we encountered this issue when tested it at the time of this writing with all the latest updates installed. |
Some use cases require to feed input automatically to the interactive command console_auth
.
From your Linux box, unlock the volume
ssh -p 222 root@10.0.0.100 console_auth < password-file
or
gpg2 --decrypt password-file.gpg | ssh -p 222 root@10.0.0.100 console_auth
For additional security, you might want to only allow the execution of the command console_auth
and nothing else. To achieve this you need to configure the SSH key with restricting options in the authorized_keys
file.
From your Linux box, copy the public SSH key, with restricting options, to the appconf/dracut-crypt-ssh/authorized_keys
file on the remote ODROID-C2 server
(printf 'command="console_auth",no-agent-forwarding,no-port-forwarding,no-pty,no-X11-forwarding ' && cat ~/.ssh/*.pub) | ssh alarm@10.0.0.100 'umask 077; mkdir -p appconf/dracut-crypt-ssh; touch appconf/dracut-crypt-ssh/authorized_keys; cat >appconf/dracut-crypt-ssh/authorized_keys'
Refer to the Dropbear documentation for a full list of restricting options. Prior to continuing, it might be a good idea to create a copy of the initramfs
sudo cp /boot/initramfs-linux.img /boot/initramfs-linux.img-`date +%y%m%d-%H%M%S`
Caution
|
In headless setup, carefuly examine the restricting options to avoid locking yourself out. |
Generate new initramfs using dracut
sudo dracut --force --hostonly -a "network crypt lvm crypt-ssh" /boot/initramfs-linux.img
In this case, you can unlock the volume interactively by simply typing
ssh -p 222 root@10.0.0.100
Note that when typing the above command
The console_auth
command is automatically invoked on the remote server and immediately prompts for password, as if you just typed ssh -p 222 root@10.0.0.100 console_auth
.
While you type the password, it will be displayed on the screen in plain text. Therefore, you should avoid unlocking interactively when the access is restricted to the console_auth
command.
When you press enter you will be disconnected no matter whether the password was correct or not. Whereas with the non-restricted login (see Unlocking the volume interactively) you would only be disconnected if the password was correct, meaning that you would have feedback for whether the unlocking was successful or not.
On the other hand, to unlock the volume using a password file, from your Linux box, type
ssh -p 222 root@10.0.0.100 < password-file
or
gpg2 --decrypt password-file.gpg | ssh -p 222 root@10.0.0.100