/devops
Install

arm
luks
encryption

Armbian full disk encryption

This article is mainly based on this tutorial and is motivated by two objectives:

  1. Make sure that this knowledge is replicated where I could always find it
  2. Provide a variation with a second encrypted partition (/data) and a two stages decryption process

I detail here the installation process of Armbian on a SSD (/dev/nvme0n1) of a rock-5-itx board: adapt it to your board and target device. All the steps are executed on an Armbian (or any Debian based distro) booted from the SD card where we prepare the target device aiming for this set up and no LVM:

partition crypt mapper decryption mode mount point
/dev/nvme0n1p1 None None /boot
/dev/nvme0n1p2 /dev/mapper/rootfs LUKS, passphrase /
/dev/nvme0n1p3 /dev/mapper/datafs Binary key file /data

Installation from live SD card

Update your system and install cryptsetup

apt update && apt upgrade
apt install cryptsetup

Download the armbian image for your board

Create a build directory

mkdir armbenc-build && cd armbenc-build

download and extract the image for your board

wget https://dl.armbian.com/rock-5-itx/Bookworm_vendor_minimal
mv Bookworm_vendor_minimal Armbian_24.11.1_Rock-5-itx_noble_vendor_6.1.75_minimal.img.xz
xz -dv *.img.xz

Create mount directories and set up the loop mount

mkdir -p mnt boot root

Identify the first free loop device (most probably /dev/loop0)

losetup -f

Associate the device with the image

losetup -P /dev/loop0 *.img

From the output of this command, determine the Disklabel type (MBR vs GPT) and the start sector of the partition

fdisk -l /dev/loop0

Here, the image uses a GPT start disk label and starts at 32768

Disk /dev/loop0: 1,46 GiB, 1564475392 bytes, 3055616 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: gpt
Disk identifier: 1C84A660-73BD-9C4C-95E1-E3EC148A5EBF

Device       Start     End Sectors  Size Type
/dev/loop0p1 32768 3055582 3022815  1,4G Linux root (ARM-64)

Mount the loop device

mount /dev/loop0p1 mnt

Copy the boot loader to the target device

From this point, we start to alter the target device (/dev/nvme0n1) and any data it might contain. Identify your target device using fdisk -l and adapt if necessary.

Determine the partition label expected by the image

fdisk -l *.img
### MBR (DOS) images:
dd if=$(echo *.img) of=/dev/nvme0n1 bs=512 count=32768

### GPT images:
dd if=$(echo *.img) of=/dev/nvme0n1 bs=512 skip=64 seek=64 count=32704

Partition the target device

fdisk /dev/nvme0n1

disklabel

Create a new disklabel using:

  • o for MBR images OR
  • g for GPT images

first partition: boot

Press n to create a new 400M partition starting at the same sector of our image (in our case: 32768)

Command (m for help): n
Partition number (1-128, default 1): 
First sector (2048-7814037134, default 2048): 32768
Last sector, +/-sectors or +/-size{K,M,G,T,P} (32768-7814037134, default 7814035455): +400M

Created a new partition 1 of type 'Linux filesystem' and of size 400 MiB.

Then, set the partition type using t (you can list all the partition types using l). In our case, we are looking for Linux root (ARM-64) (number 27).

Command (m for help): t
Selected partition 1
Partition type or alias (type L to list all): 27
Changed type of partition 'Linux filesystem' to 'Linux root (ARM-64)'.

check with p that you have the following

Command (m for help): p
Disk /dev/nvme0n1: 3,64 TiB, 4000787030016 bytes, 7814037168 sectors
Disk model: Samsung SSD 990 EVO Plus 4TB            
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: gpt
Disk identifier: 6534C15B-008E-CC49-968E-6B8C9E033F3F

Device         Start    End Sectors  Size Type
/dev/nvme0n1p1 32768 851967  819200  400M Linux root (ARM-64)

Second partition: system

Press n again to create a new 120G partition (recommended by openmediavault, adapt to your needs).

Command (m for help): n
Partition number (2-128, default 2): 
First sector (2048-7814037134, default 851968): 
Last sector, +/-sectors or +/-size{K,M,G,T,P} (851968-7814037134, default 7814035455): +120G

Created a new partition 2 of type 'Linux filesystem' and of size 120 GiB.

set also the partition type using t to Linux root (ARM-64)

Command (m for help): t
Partition number (1,2, default 2): 
Partition type or alias (type L to list all): 27

Changed type of partition 'Linux filesystem' to 'Linux root (ARM-64)'.

third partition: data

Create a final partition filling the rest of the disk

Command (m for help): n
Partition number (3-128, default 3): 
First sector (2048-7814037134, default 252510208): 
Last sector, +/-sectors or +/-size{K,M,G,T,P} (252510208-7814037134, default 7814035455): 

Created a new partition 3 of type 'Linux filesystem' and of size 3,5 TiB.

However, this time, we do need a different partition type. We will use Linux server data (option 21)

Command (m for help): t
Partition number (1-3, default 3): 
Partition type or alias (type L to list all): 21

Changed type of partition 'Linux filesystem' to 'Linux server data'

Check and apply

Check with p that you have the following

Device             Start        End    Sectors  Size Type
/dev/nvme0n1p1     32768     851967     819200  400M Linux root (ARM-64)
/dev/nvme0n1p2    851968  252510207  251658240  120G Linux root (ARM-64)
/dev/nvme0n1p3 252510208 7814035455 7561525248  3,5T Linux server data

If so, apply the changes with w, if not, reset with d and start over

Copy the system to the target device

copy the boot partition (/dev/nvme0n1p1)

mkfs.ext4 /dev/nvme0n1p1
e2label /dev/nvme0n1p1 CRYPTO_BOOT
mount /dev/nvme0n1p1 boot
cp -av mnt/boot/* boot
(cd boot; ln -s . boot)

encrypt and format the system partition (/dev/nvme0n1p2)

Encrypt the partition. You will be asked to choose a LUKS passphrase that you will need every time you reboot your system.

cryptsetup luksFormat /dev/nvme0n1p2

Decrypt the very same partition and format it in EXT4 (or any journaling file system but you will need to adapt the rest of the procedure).

cryptsetup luksOpen /dev/nvme0n1p2 rootfs
mkfs.ext4 /dev/mapper/rootfs

Mount the partition and copy the system to it.

mount /dev/mapper/rootfs root
(cd mnt && rsync -a --info=progress2 --exclude=boot * ../root)
sync
mkdir root/boot
mkdir root/data
touch root/root/.no_rootfs_resize

encrypt and format the data partition (/dev/nvme0n1p3)

To avoid to get a second prompt to unlock this second partition, we will create a key stored in the encrypted system partition. This key will live in /root/.luks_keys and should remain readable and writable only by root (600)

mkdir -p root/root/.luks_keys
chmod u=rw,g=,o= root/root/.luks_keys
dd if=/dev/urandom of=root/root/.luks_keys/data_part.key bs=1024 count=1
chmod u=rw,g=,o= root/root/.luks_keys/data_part.key

Use it to encrypt this third partition

cryptsetup luksFormat /dev/nvme0n1p3 root/root/.luks_keys/data_part.key

then open it and format it

cryptsetup luksOpen -d root/root/.luks_keys/data_part.key /dev/nvme0n1p3 datafs
mkfs.ext4 /dev/mapper/datafs

we will edit /etc/crypttab later to automate the decryption once the system partition is decrypted

unmount the boot partition

umount mnt boot
losetup -d /dev/loop0

Prepare the target system chroot

Define practical env vars for later partition identification

BOOT_PART=($(lsblk -l -o NAME,LABEL | grep CRYPTO_BOOT))
ROOT_PART=${BOOT_PART%1}2
DATA_PART=${BOOT_PART%1}3
ROOT_UUID="$(lsblk --nodeps --noheadings --output=UUID /dev/$ROOT_PART)"
DATA_UUID="$(lsblk --nodeps --noheadings --output=UUID /dev/$DATA_PART)"
BOOT_UUID="$(lsblk --noheadings --output=UUID /dev/$BOOT_PART)"

Make sure all UUID are properly set by comparing the following with blkid’s output

echo $BOOT_UUID
echo $ROOT_UUID
echo $DATA_UUID

Then

cd root
mount /dev/$BOOT_PART boot
mount -o rbind /dev dev
mount -t proc proc proc
mount -t sysfs sys sys

In order to have a working internet connection inside the chroot, copy the following files

rm etc/resolv.conf && cat /etc/resolv.conf > etc/resolv.conf
rm etc/hosts && cat /etc/hosts > etc/hosts

Edit or create required configuration files in the target system

We then need to configure initramfs in order to get a minimal dropbear SSH server to unlock the system in case of reboot (console from screen and keyboard will also be available).

Armbian env setting

If boot/armbianEnv.txt exists, edit console and rootdev using nano or vi. Modulo the board’s name in overlay_prefix and fdtfile, the file should look like this

verbosity=1
bootlogo=false
console=display 
extraargs=cma=256M
overlay_prefix=rockchip-rk3588
fdtfile=rockchip/rk3588-rock-5-itx.dtb
rootdev=/dev/mapper/rootfs                       
rootfstype=ext4

If boot/armbianEnv.txt does not exist, please refer to the original tutorial (step 9)

initramfs configuration

Identify the default network interface using ip addr (in my case, enP3p49s0). Edit etc/initramfs-tools/initramfs.conf.

For fixed IP, you can set it together with the network mask using, for instance

IP=192.168.1.49:::255.255.255.0::enP3p49s0:off

For DHCP, simply set the network interface using

DEVICE=enP3p49s0

adapt to your NIC name

We also need to create etc/initramfs-tools/hooks/cryptroot-unlock.sh content of which should be:

#!/bin/sh

if [ "$1" = 'prereqs' ]; then echo 'dropbear-initramfs'; exit 0; fi

. /usr/share/initramfs-tools/hook-functions

source='/tmp/cryptroot-unlock-profile'

root_home=$(echo $DESTDIR/root-*)
root_home=${root_home#$DESTDIR}

echo 'if [ "$SSH_CLIENT" ]; then /usr/bin/cryptroot-unlock; fi' > $source

copy_file ssh_login_profile $source $root_home/.profile

exit 0

and make it executable

chmod 755 'etc/initramfs-tools/hooks/cryptroot-unlock.sh'

Dropbear configuration

Create an authorized_keys file that will contain the public keys of all the clients allowed to unlock the server (I recommend minimum 2).

mkdir -p etc/dropbear/initramfs
touch etc/dropbear/initramfs/authorized_keys

Add the keys using

echo "<public_key>" >> etc/dropbear/initramfs/authorized_keys

Create dropbear configuration file

echo 'DROPBEAR_OPTIONS="-I 180 -p 2222"' > etc/dropbear/initramfs/dropbear.conf
echo 'DROPBEAR=y' >> etc/dropbear/initramfs/dropbear.conf

Here we set the timeout to 180 seconds using -I as an extra layer of security but you are free to remove it if you find it ridiculously paranoid.

Edit fstab

echo '/dev/mapper/rootfs / ext4 defaults,noatime,nodiratime,commit=600,errors=remount-ro 0 1' > etc/fstab
echo '/dev/mapper/datafs /data ext4 defaults,noatime,nodiratime,commit=600,errors=remount-ro 0 2' >> etc/fstab
echo "UUID=$BOOT_UUID /boot ext4 defaults,noatime,nodiratime,commit=600,errors=remount-ro 0 2" >> etc/fstab
echo 'tmpfs /tmp tmpfs defaults,nosuid 0 0' >> etc/fstab

Crypttab configuration

echo "rootfs UUID=$ROOT_UUID none initramfs,luks" > etc/crypttab
echo "datafs UUID=$DATA_UUID /root/.luks_keys/data_part.key noauto" >> etc/crypttab

Make sure the file looks like this

cat etc/crypttab
rootfs UUID=d3d5d09e-9774-4b64-b6fa-209ed62eca6e none initramfs,luks
datafs UUID=8bfda2c2-8037-4be0-8be7-63ddeac03e1b /root/.luks_keys/data_part.key noauto

rootfs is unlocked at boot time using initramfs, luks. systemd will look for data_part.key when it becomes available after boot and rootfs decryption thanks to noauto.

Chroot into the target system, install packages and configure

chroot .

Update the system and install cryptsetup-initramfs and dropbear-initramfs

apt update
echo 'force-confdef' > /root/.dpkg.cfg
apt --yes install cryptsetup-initramfs dropbear-initramfs
rm /root/.dpkg.cfg

Make sure that initramfs is properly set up (all 3 commands should produce an output)

lsinitramfs /boot/initrd.img-* | grep 'usr.*cryptsetup'
lsinitramfs /boot/initrd.img-* | grep dropbear
lsinitramfs /boot/initrd.img-* | grep authorized_keys

generate SSH host keys

ssh-keygen -A

Exit the chroot and shut the system down

exit
halt -p

Owner’s turn

Remove the SD card, start your system and, ssh to port 2222. You should get a prompt

ssh root@192.168.1.49 -p 2222       
To unlock root partition, and maybe others like swap, run `cryptroot-unlock`.


BusyBox v1.35.0 (Debian 1:1.35.0-4+b3) built-in shell (ash)
Enter 'help' for a list of built-in commands.

Please unlock disk rootfs: 
cryptsetup: rootfs set up successfully
~ # Connection to 192.168.1.49 closed by remote host.
Connection to 192.168.1.49 closed.

If everything went well, you should be able to ssh root@192.168.1.49. Your fully encrypted armbian is ready

Troubleshooting

After boot, I can’t find dropbear listening to 2222

There is probably something wrong with initramfs.conf or the dropbear configuration. Check th

I managed to get a dropbear SSH session, succesfully unlocked rootfs but get an other prompt later in the boot sequence

Make sure the entry for datafs in /etc/crypttab uses noauto, that the key exists and is properly set. With this option, systemd will then not open the encrypted device on boot, but instead wait until it is actually accessed.

zar3bski

DataOps