Jens Krämer

Fully Encrypted Headless Debian Stretch Setup

 |  sysadmin, devops, encryption, linux, debian

Here’s my field notes from the server I recently set up. It’s a rental server at Hetzner, which comes with a very basic rescue system pre-installed.

Starting from there, I followed the steps outlined here. The differences between the Ubuntu version used there and Debian Stretch kept me busy quite a bit so I’m writing my adopted command chain down here to save you some time.

Partition and Encrypt the disks

Hetzner servers usually come with at least 2 disks which are partioned identically with 128MB for /boot (which stays unencrypted), and the rest for the encrypted system.

On top of that two Raid 1 arrays are formed, md0 for /boot and md1 for everything else.

sgdisk -og /dev/sda
sgdisk -n 1:2048:+256M -t 1:fd00 /dev/sda
sgdisk -n 128:-3M:0 -t 128:ef02 /dev/sda
sgdisk -n 2:0:0 -t 2:fd00 /dev/sda
sgdisk -R /dev/sdb /dev/sda
sgdisk -G /dev/sdb
partprobe

mdadm --create /dev/md0 --metadata=0.9 --level=1 --assume-clean --raid-devices=2 /dev/sd[ab]1
mdadm --create /dev/md1 --metadata=1.2 --level=1 --assume-clean --raid-devices=2 /dev/sd[ab]2

Now, md1 will be encrypted, and on top of that encrypted device we set up our root and other partitions using LVM. I tend to start with reasonable but small partition size and leave most space in the LVM volume group unassigned so I have room to create / enlarge partitions later on as needed.

cryptsetup -c aes-xts-plain -y -s 512 -h sha512 luksFormat /dev/md1

cryptsetup luksOpen /dev/md1 debian
pvcreate /dev/mapper/debian
vgcreate debian-vg /dev/mapper/debian

lvcreate -L 20GB -n root debian-vg
lvcreate -L 16GB -n swap debian-vg
lvcreate -L 40GB -n home debian-vg
lvcreate -L 100GB -n var debian-vg

mkfs.ext2 -L boot -I 128 /dev/md0
mkfs.ext4 -L root /dev/debian-vg/root
mkfs.ext4 -L home /dev/debian-vg/home
mkfs.ext4 -L var /dev/debian-vg/var

mkswap -L swap /dev/debian-vg/swap

mount /dev/debian-vg/root /mnt
mkdir /mnt/{boot,var,home}
mount /dev/md0 /mnt/boot
mount /dev/debian-vg/var /mnt/var
mount /dev/debian-vg/home /mnt/home

Set Up a Basic Debain Stretch With Debootstrap

The most recent version of debootstrap at the time you are doing this may vary, adjust accordingly.

wget http://ftp.de.debian.org/debian/pool/main/d/debootstrap/debootstrap_1.0.89_all.deb
ar -xf debootstrap_1.0.89_all.deb
tar -xf data.tar.gz -C /
debootstrap --arch=amd64  stretch /mnt http://ftp.de.debian.org/debian

Next, edit or create a couple config files.

# make sure resolv.conf is correct (mine was prefilled with Hetzner DNS servers) or do:
echo "nameserver 8.8.8.8" > /mnt/etc/resolv.conf

echo "test" > /mnt/etc/hostname
echo "debian UUID=$(blkid -s UUID -o value /dev/md1) none luks" > /mnt/etc/crypttab

cat > /mnt/etc/fstab << EOF
proc                        /proc   proc    defaults    0 0
UUID=$(blkid -s UUID -o value /dev/md0)                    /boot   ext2    defaults   0 0
UUID=$(blkid -s UUID -o value /dev/mapper/debian--vg-root) /       ext4    defaults   0 1
UUID=$(blkid -s UUID -o value /dev/mapper/debian--vg-var)  /var    ext4    defaults   0 2
UUID=$(blkid -s UUID -o value /dev/mapper/debian--vg-home) /home   ext4    defaults   0 2
UUID=$(blkid -s UUID -o value /dev/mapper/debian--vg-swap)  none    swap defaults     0 0
EOF

echo "deb http://security.debian.org/ stretch/updates main" >> /mnt/etc/apt/sources.list

And change into the fresh Debain system with chroot:

mount -o bind /dev /mnt/dev
mount -o bind /dev/pts /mnt/dev/pts
mount -t sysfs /sys /mnt/sys
mount -t proc /proc /mnt/proc
chroot /mnt

Set a password, create some symlinks for the Raid devices and create mtab. I’m not sure if the links are actually necessary.

passwd

mkdir /dev/md
ln -s /dev/md0 /dev/md/0
ln -s /dev/md1 /dev/md/1

cp /proc/mounts /etc/mtab

Upgrade the installed packages and install a couple more:

apt update
apt upgrade -y
DEBIAN_FRONTEND=noninteractive apt install -y \
  vim \
  linux-base \
  linux-image-amd64 linux-headers-amd64 \
  grub-pc \
  mdadm \
  cryptsetup \
  lvm2 \
  initramfs-tools \
  openssh-server \
  busybox \
  dropbear \
  locales \
  net-tools

dpkg-reconfigure locales # select en_US.UTF-8

Make sure you can actually log into the new system by putting your SSH public key into /root/.ssh/authorized_keys.

echo "YOUR PUBLIC SSHKEY HERE" > /root/.ssh/authorized_keys

Last but not least, set up networking so your system actually goes online after unlocking. I tried to use the modern way, but for some reason it didn’t work, so I went with the classic /etc/network/interfaces config style. Next hurdle was to find out the (not so) predictable network interface name the kernel assigns to my server’s ethernet adaptor. I got it from the syslog after accessing the system through the rescue image (see below for the steps to access the machine in case you cannot get into it after reboot).

It might also be possible to force the Kernel to use the old eth0 style names using the net.ifnames=0 kernel boot parameter (link to serverfault).

cat > /etc/network/interfaces.d/lo << EOF
auto lo
iface lo inet loopback
EOF

# your device name may be different
cat > /etc/network/interfaces.d/enp0s31f6 << EOF
auto enp0s31f6
iface enp0s31f6 inet static
  address YOUR.IPv4.ADDRESS
  netmask YOUR.IPv4.NETMASK
  gateway YOUR.IPv4.GATEWAY

iface enp0s31f6 inet6 static
  address YOUR.IPv6.ADDRESS
  netmask YOUR.IPv6.NETMASK
  gateway YOUR.IPv6.GATEWAY
EOF

Set Up the Initial Ramdisk for Remote Password Entry

Since the whole system (except /boot) is encrypted, you will have to enter the passphrase at boot time. This is accomplished by embedding the dropbear SSH server into the initial ramdisk image, which allows you to SSH into the server before it even finished booting to enter the passphrase. Luckily this is all easy and straight forward to set up nowadays:

sed -i "s/NO_START=1/NO_START=0/" /etc/default/dropbear
echo "DEVICE=eth0" >> /etc/initramfs-tools/initramfs.conf
sed -i "s/^#CRYPTSETUP=$/CRYPTSETUP=y/" /etc/cryptsetup-initramfs/conf-hook

For logging in via SSH, install your SSH key, again. You can use the one you just put into /root/.ssh/authorized_keys, as long as it is an RSA key. Other key types (I tried my ed25519 key) will most certainly not work. The options before the key limit what can be done when logging in with that key, and the command is what will be executed directly after log in. Since the sole purpose of this is to unlock the disk, the script which does just that is launched directly after log in.

To prevent dropbear from starting when it isn’t needed (that is, outside the initrd in your ‘real’ system), run systemctl disable dropbear. Thanks to Thomas pointing this out in the comments.

echo "no-port-forwarding,no-agent-forwarding,no-X11-forwarding,command=\"/bin/cryptroot-unlock\" YOUR PUBLIC RSA SSHKEY" > /etc/dropbear-initramfs/authorized_keys

In order to avoid SSH complaining about changed host keys every time you unlock the server or connect to it when unlocked there are two ways: run Dropbear on a different port than you run the ‘normal’ SSH server on the unlocked system on, or use a separate known_hosts file for unlocking. I chose to run Dropbear on another port and set some other options while I’m at it:

sed -i "s/^#DROPBEAR_OPTIONS=$/DROPBEAR_OPTIONS=\"-p 2323 -s -j -k -I 60\"/" /etc/dropbear-initramfs/config

With all that done, it’s time to update the initrd and install the Grub bootloader. The ip kernel boot parameter is essential for setting up the network in the initial ram disk, otherwise dropbear won’t be reachable. Be sure to replace the placeholders with whatever IPv4 config your server has.

update-initramfs -u -k all

echo "GRUB_CMDLINE_LINUX_DEFAULT=\"ip=IPv4IP::GATEWAY:NETMASK\"" >> /etc/default/grub
grub-install /dev/sda
grub-install /dev/sdb
update-grub

# exit the chroot and reboot
exit
umount /mnt/{boot,var,home}
sync
swapoff -L swap

reboot

At this point, you should be able to connect to the server on the configured port and immediately be asked for your passphrase. Enter it, press enter and the connection is closed, while the server continues booting.

It doesn’t work!

You cannot connect to unlock the system, or you unlock it but cannot connect after that? Don’t panic.

With Hetzner, there is always a way to reboot the machine into the rescue system, which can then be used to fix things.

After you logged into the system, follow these steps to unlock disks and get into the Debian again:

cryptsetup luksOpen /dev/md1 debian
vgchange -a y

mount /dev/debian-vg/root /mnt
mount /dev/md0 /mnt/boot
mount /dev/debian-vg/var /mnt/var
mount /dev/debian-vg/home /mnt/home
mount /dev/debian-vg/srv /mnt/srv
mount /dev/debian-vg/data /mnt/opt/data

mount -o bind /dev /mnt/dev
mount -o bind /dev/pts /mnt/dev/pts
mount -t sysfs /sys /mnt/sys
mount -t proc /proc /mnt/proc

chroot /mnt

I needed these quite a few times while fixing my network config. Once you’re ready to give it another go, do the same steps as above, but skip the Grub installation:

update-initramfs -u -k all
update-grub

exit
umount /mnt/{boot,var,home}
sync
swapoff -L swap

reboot

That’s it, have fun with your shiny new server!