Encrypted remote rootserver
2021-03-13
After figuring out how to remote unlock (initramfs, tinyssh) a homeserver, I'll do the same for a Hetzner-hosted rootserver.
Approach
- install unencrypted Archlinux (per Hetzner default tools)
- boot into unencrypted Archlinux
- install
extra/arch-install-scripts
- run installation process as usual
- create encrypted LUKS volume
- create filesystem
- install base system
- install all packages that were present in Hetzner's install
- copy Hetzner configuration
- copy all configuration (
/etc
) into encrypted LUKS volume - manually resolve any conflicts
- copy all configuration (
- install and configure initramfs/tinyssh
core/mkinitcpio-busybox
community/mkinitcpio-netconf
community/mkinitcpio-tinyssh
community/mkinitcpio-utils
- configure GRUB
- add ip settings to
GRUB_CMDLINE
- add encrypted filesystem's details to
GRUB_CMDLINE
- add ip settings to
Preparation
Install Archlinux from Hetzner's rescue system, according to the standard documentation.
I chose three partitions (boot, root and home). This was mostly to use Hetzner's mechanisms to take care of setting up mdraid
. Partition boot
will be kept (but mostly overwritten), partition root
will (only) be used during the installation process and partition home
will become the new cryptroot
.
Cryptroot installation
Start by adding zsh
, grml-zsh-config
and tmux
for convenience, then switch shell to zsh
: chsh -s /bin/zsh
Identify where and how networking got auto-configured by Hetzner's install script, then preserve those settings for later re-use:
root@archlinux ~ # ip addr show
(...)
2: enp4s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 60:a4:4c:24:0d:ab brd ff:ff:ff:ff:ff:ff
inet 144.76.71.150 peer 144.76.71.129/32 scope global enp4s0
valid_lft forever preferred_lft forever
inet6 2a01:4f8:191:8495::2/64 scope global
valid_lft forever preferred_lft forever
inet6 fe80::62a4:4cff:fe24:dab/64 scope link
valid_lft forever preferred_lft forever
root@archlinux ~ # grep 144.76.71.150 /etc --recursive
/etc/systemd/network/10-enp4s0.network:Address=144.76.71.150
/etc/hosts:144.76.71.150 vetinari.oepkes.net
Some trouble:
root@archlinux ~ # cryptsetup luksFormat /dev/md2
WARNING: Device /dev/md2 already contains a 'ext4' superblock signature.
WARNING!
========
This will overwrite data on /dev/md2 irrevocably.
Are you sure? (Type 'yes' in capital letters): YES
Enter passphrase for /dev/md2:
Verify passphrase:
Device /dev/md2 is in use. Can not proceed with format operation.
Shoot the trouble (/dev/md
was already mounted):
root@archlinux ~ # umount /dev/md2
root@archlinux ~ # cryptsetup luksFormat /dev/md2
WARNING: Device /dev/md2 already contains a 'ext4' superblock signature.
WARNING!
========
This will overwrite data on /dev/md2 irrevocably.
Are you sure? (Type 'yes' in capital letters): YES
Enter passphrase for /dev/md2:
Verify passphrase:
cryptsetup luksFormat /dev/md2 9.90s user 0.85s system 92% cpu 11.609 total
Open LUKS volume, create btrfs filesystem, mount to /mnt
root@archlinux ~ # cryptsetup open /dev/md2 cryptroot
Enter passphrase for /dev/md2:
cryptsetup open /dev/md2 cryptroot 4.92s user 0.31s system 70% cpu 7.371 total
root@archlinux ~ # mount /dev/mapper/cryptroot /mnt
mount: /mnt: wrong fs type, bad option, bad superblock on /dev/mapper/cryptroot, missing codepage or helper program
, or other error.
32 root@archlinux ~ # mkfs.btrfs --label root /dev/mapper/cryptroot
btrfs-progs v5.11
See http://btrfs.wiki.kernel.org for more information.
Label: root
UUID: cd356fa0-37ee-4ec6-b4c9-462da3bd952e
Node size: 16384
Sector size: 4096
Filesystem size: 2.72TiB
Block group profiles:
Data: single 8.00MiB
Metadata: DUP 1.00GiB
System: DUP 8.00MiB
SSD detected: no
Incompat features: extref, skinny-metadata
Runtime features:
Checksum: crc32c
Number of devices: 1
Devices:
ID SIZE PATH
1 2.72TiB /dev/mapper/cryptroot
root@archlinux ~ # mount /dev/mapper/cryptroot /mnt
root@archlinux ~ # lsblk --fs
NAME FSTYPE FSVER LABEL UUID FSAVAIL FSUSE% MOUNTPOINT
sda
__sda1 linux_raid_member 1.2 rescue:0 e4550606-7663-74c6-368a-74e058afa4b2
_ __md0 ext3 1.0 53a9ed32-75d1-4697-9e30-01b24427d51e 860.4M 8% /boot
__sda2 linux_raid_member 1.2 rescue:1 c774e188-eae8-ccbc-885d-261077f91210
_ __md1 ext4 1.0 597db7b8-caf1-4967-a7ee-0a896e57ddb1 7.2G 21% /
__sda3 linux_raid_member 1.2 rescue:2 2812faef-fc0a-90d1-11ec-013dbbbba5cc
_ __md2 crypto_LUKS 2 e7417b77-7099-4f2b-8521-0c851a1f50f3
_ __cryptroot btrfs root cd356fa0-37ee-4ec6-b4c9-462da3bd952e 2.7T 0% /mnt
__sda4
sdb
__sdb1 linux_raid_member 1.2 rescue:0 e4550606-7663-74c6-368a-74e058afa4b2
_ __md0 ext3 1.0 53a9ed32-75d1-4697-9e30-01b24427d51e 860.4M 8% /boot
__sdb2 linux_raid_member 1.2 rescue:1 c774e188-eae8-ccbc-885d-261077f91210
_ __md1 ext4 1.0 597db7b8-caf1-4967-a7ee-0a896e57ddb1 7.2G 21% /
__sdb3 linux_raid_member 1.2 rescue:2 2812faef-fc0a-90d1-11ec-013dbbbba5cc
_ __md2 crypto_LUKS 2 e7417b77-7099-4f2b-8521-0c851a1f50f3
_ __cryptroot btrfs root cd356fa0-37ee-4ec6-b4c9-462da3bd952e 2.7T 0% /mnt
__sdb4
Mount boot partition into future chroot:
root@archlinux ~ # mount | grep mnt
/dev/mapper/cryptroot on /mnt type btrfs (rw,relatime,space_cache,subvolid=5,subvol=/)
root@archlinux ~ # umount /boot
root@archlinux ~ # mkdir /mnt/boot
root@archlinux ~ # mount /dev/md0 /mnt/boot
root@archlinux ~ # mount | grep mnt
/dev/mapper/cryptroot on /mnt type btrfs (rw,relatime,space_cache,subvolid=5,subvol=/)
/dev/md0 on /mnt/boot type ext3 (rw,relatime)
Initialize system with pacstrap:
root@archlinux ~ # pacstrap /mnt base linux linux-firmware
==> Creating install root at /mnt
==> Installing packages to /mnt
(...)
Store Hetzner's default install's list of packages into future cryptroot:
root@archlinux ~ # pacman -Qq >> /mnt/hetzner-archlinux-packagelist
Now would be a good time to make a btrfs snapshot of the current cryptroot. Install btrfs-progs
, create snapshot folder, create snapshot:
root@archlinux ~ # arch-chroot /mnt
[root@archlinux /]# zsh
root@archlinux / # mkdir /.snapshots
root@archlinux / # date "+%F_%H%M%S"
2021-03-13_121927
root@archlinux / # pacman -S btrfs-progs
(...)
pacman -S btrfs-progs 8.86s user 3.75s system 108% cpu 11.653 total
root@archlinux / # btrfs subvolume snapshot / /.snapshots/root_$(date "+%F_%H%M%S")
Create a snapshot of '/' in '/.snapshots/root_2021-03-13_122057'
Install all packages that are present in Hetzner's default install:
root@archlinux / # cat /hetzner-archlinux-packagelist | pacman -S --needed -
warning: acl-2.3.0-1 is up to date -- skipping
warning: archlinux-keyring-20210110-1 is up to date -- skipping
warning: argon2-20190702-3 is up to date -- skipping
(...)
Package (22) New Version Net Change Download Size
extra/arch-install-scripts 23-2 0.04 MiB 0.01 MiB
core/cronie 1.5.5-2 0.23 MiB 0.08 MiB
core/dnssec-anchors 20190629-3 0.00 MiB 0.00 MiB
extra/gptfdisk 1.0.7-1 0.79 MiB 0.20 MiB
core/grub 2:2.04-10 32.94 MiB 6.75 MiB
extra/haveged 1.9.14-1 0.15 MiB 0.05 MiB
core/ldns 1.7.1-2 1.85 MiB 0.43 MiB
core/libaio 0.3.112-2 0.02 MiB 0.01 MiB
core/libedit 20191231_3.1-3 0.27 MiB 0.11 MiB
core/libinih 52-2 0.04 MiB 0.01 MiB
core/libnsl 1.3.0-1 0.19 MiB 0.06 MiB
core/lvm2 2.03.11-5 6.00 MiB 1.46 MiB
core/mdadm 4.1-2 0.95 MiB 0.34 MiB
core/net-tools 2.10-1 0.55 MiB 0.14 MiB
core/openssh 8.5p1-1 5.75 MiB 0.99 MiB
extra/python 3.9.2-1 79.28 MiB 31.78 MiB
extra/rsync 3.2.3-3 0.57 MiB 0.30 MiB
core/run-parts 4.8.6.1-2 0.04 MiB 0.03 MiB
core/thin-provisioning-tools 0.9.0-1 1.57 MiB 0.48 MiB
extra/wget 1.21.1-1 2.99 MiB 0.72 MiB
core/xfsprogs 5.10.0-2 5.36 MiB 1.05 MiB
community/xxhash 0.8.0-1 0.28 MiB 0.07 MiB
Total Download Size: 45.07 MiB
Total Installed Size: 139.87 MiB
:: Proceed with installation? [Y/n]
Create another snapshot as we'll overwrite a lot of configuration in the next step:
root@archlinux / # btrfs subvolume snapshot / /.snapshots/root_$(date "+%F_%H%M%S")
Create a snapshot of '/' in '/.snapshots/root_2021-03-13_124538'
Check if all worked well with the snapshots:
root@archlinux / # diff /.snapshots/root_2021-03-13_{122057,124538}/etc/pacman.conf
36c36
< #VerbosePkgLists
---
> VerbosePkgLists
Leave arch-chroot
, copy all of Hetzner's config into /mnt
, fix overwritten /etc/fstab
, enter arch-chroot
again:
root@archlinux ~ # cp --archive --interactive /etc /mnt
cp: overwrite '/mnt/etc/locale.gen'? y
cp: overwrite '/mnt/etc/inputrc'? y
cp: overwrite '/mnt/etc/mdadm.conf'? y
cp: overwrite '/mnt/etc/mkinitcpio.conf'? y
(...)
Recognize that --interactive
is waaaay too annoying and simply overwrite any files instead: cp --archive /etc /mnt
. After all, we have the btrfs snapshot if anything goes bad ;-)
Adjust mkinitcpio configuration
Add module btrfs
and hooks to /etc/mkinitcpio.conf
:
MODULES=(btrfs)
(...)
HOOKS=(base udev autodetect modconf block mdadm_udev lvm2 filesystems keyboard sleep netconf tinyssh encryptssh fsck)
Adjust GRUB configuration
A bit more trouble: translating the Hetzner networking config for use in /etc/default/grub
. Hetzner's config doesn't use a normal gateway, but uses a host route to .129
. I haven't seen a setup like this before. I'll try to simply set a subnet mask of /32
in the GRUB config and hope it'll work. Another caveat is that in early boot my NIC will still be named eth0
and not enp4s0
which needs to be reflected in GRUB's config. UUID has to be taken from lsblk --fs
and cryptroot
is the name used by the devicemapper.
(...)
GRUB_CMDLINE_LINUX="ip=144.76.71.150::144.76.71.129:255.255.255.255::eth0:none cryptdevice=UUID=e7417b77-7099-4f2b-8521-0c851a1f50f3:cryptroot"
(...)
After reboot, it turns out that this syntax was exactly right: configure a /32
host route in the GRUB_CMDLINE
.
Remote unlock
Contents of my SSH client config:
(...)
# Normal use
Host vetinari.oepkes.net
RequestTTY yes
RemoteCommand tmux new-session -As tammo
# If rescue system is required (always autogenerates new SSH keys)
Host vetinari-rescue
HostKeyAlias vetinari-rescue
HostName vetinari.oepkes.net
StrictHostKeyChecking=no
User root
# For remote unlocking (and tracking of host keys!)
Host vetinari-disk-unlock
HostName vetinari.oepkes.net
User root
HostKeyAlias vetinari-disk-unlock
(...)