Compare commits

..

3 commits

3 changed files with 66 additions and 71 deletions

View file

@ -1,5 +1,5 @@
# An Alpine Linux server installation
This guide will demonstrate how to install [Alpine Linux](https://www.alpinelinux.org/) for server application. Alpine Linux will run on a raid configured encrypted ZFS filesystem with automatic decryption with TPM. User containers will be configured with podman and managed with runsvdir.
This guide will demonstrate how to install [Alpine Linux](https://www.alpinelinux.org/) for server application. Alpine Linux will run on a raid configured encrypted ZFS filesystem with automatic decryption using TPM. User containers will be configured with `podman` and managed with `runsvdir`.
Alpine Linux makes a good base for a server because of its simplicity, lightweightness and security. Check out the [Alpine Linux wiki](https://wiki.alpinelinux.org/wiki/Main_Page) for additional resources and information.

View file

@ -2,38 +2,36 @@
To install the Alpine Linux distribution on the system, the datasets of the system pool and the EFI partitions have to be mounted to the main system.
First import and decrypt the system pool.
First import and decrypt the system pool:
```
# zpool import -N -R /mnt tank
# zfs load-key -L file:///tmp/tank.key tank
```
Mount the datasets in the system pool and decrypt the home dataset.
Mount the datasets in the system pool and decrypt the home dataset:
```
# zfs mount tank/root/alpine
# mount tank/root/alpine /mnt -t zfs -o noatime
# zfs mount tank/home
# zfs mount tank/var
```
Mount the ESP.
Mount the ESP:
```
# mkdir /mnt/esp
# mount /dev/md/esp /mnt/esp -t vfat
```
Then install Alpine Linux.
Then install Alpine Linux:
```
# export BOOTLOADER=none
# setup-disk -m sys /mnt
```
> This will also add `grub` as bootloader which will be replaced but for now it will reside on the ESP.
To have a functional chroot into the system, bind the system process directories.
To have a functional chroot into the system, bind the system process directories:
```
# for dir in dev proc sys run; do
@ -67,7 +65,7 @@ clock_hctosys="NO"
clock_systohc="NO"
```
Configure the ESP raid array to mount.
Configure the ESP raid array to mount:
```
# modprobe raid1
@ -77,61 +75,50 @@ Configure the ESP raid array to mount.
# rc-update add mdadm-raid boot
```
Configure ZFS to mount.
Configure ZFS to mount:
```
rc-update add zfs-import sysinit
rc-update add zfs-mount sysinit
rc-update add zfs-load-key sysinit
```
> If a faster boot time is preferred, `zfs-import` and `zfs-load-key` can be omitted in certain cases.
Edit `/etc/fstab` for correct mounts:
```
/dev/md/esp /esp vfat defaults,nodev,nosuid,noexec 0 2
tmpfs /tmp tmpfs rw,size=4G,nr_inodes=5k,nodev,nosuid,noexec,mode=1777 0 0
proc /proc proc nodev,nosuid,noexec,hidepid=2 0 0
/dev/md/esp /esp vfat defaults,nodev,nosuid,noexec 0 2
tmpfs /tmp tmpfs rw,nodev,nosuid,noexec,mode=1777 0 0
proc /proc proc nodev,nosuid,noexec,hidepid=2 0 0
```
By default, Alpine Linux uses `mkinitfs` to create an initial ram filesystem, although it is minimal that also means that it lacks some functionality which is needed for a proper setup. Because of this `mkinitfs` and `grub-efi `will be replaced with `booster` and `secureboot-hook`.
Install the following packages to make `mkinitfs` compatible with secureboot and TPM decryption:
```
# apk add booster secureboot-hook sbctl
# apk del mkinitfs grub-efi
# apk add secureboot-hook sbctl tpm2-tools zlevis
```
To configure booster edit `/etc/booster.yaml`:
Configure `/etc/mkinitfs/mkinitfs.conf` to disable trigger and to add the `zlevis-hook`:
```
enable_zfs: true
busybox: false
modules: vfat,nls_cp437,nls_iso8859_1
features="... zlevis"
disable_trigger="yes"
```
The most important step is the creation of a UKI using `secureboot-hook` which also automatically signs them. First the hook itself will have to be tweaked to use `booster` instead of `mkinitfs`, edit `/etc/kernel-hooks.d/50-secureboot.hook` and change the line:
The most important step is the creation of a UKI using `secureboot-hook` which also automatically signs them. Configure `/etc/kernel-hooks.d/secureboot.conf` to set kernel cmdline options and secureboot:
```
/sbin/mkinitfs -o "$tmpdir"/initramfs "$NEW_VERSION-$FLAVOR"
```
cmdline="rw root=ZFS=tank/root/alpine rootflags=noatime quiet splash"
to:
```
/usr/bin/booster build "$tmpdir"/initramfs --kernel-version "$NEW_VERSION-$FLAVOR"
```
and configure `/etc/kernel-hooks.d/secureboot.conf` for cmdline and secureboot.
```
cmdline="rw zfs=tank/root/alpine quiet splash"
signing_cert="/usr/share/secureboot/keys/db/db.pem"
signing_key="/usr/share/secureboot/keys/db/db.key"
signing_cert="/var/lib/sbctl/keys/db/db.pem"
signing_key="/var/lib/sbctl/keys/db/db.key"
output_dir="/esp/efi/linux"
output_name="alpine-linux-{flavor}.efi"
```
Use `sbctl` to create secureboot keys and sign them.
Use `sbctl` to create secureboot keys and sign them:
```
# sbctl create-keys
@ -140,7 +127,7 @@ Use `sbctl` to create secureboot keys and sign them.
> Whilst enrolling the keys it might be necessary to add the `--microsoft` flag if you are unable to use custom keys.
Set the cache-file of the ZFS pool.
Set the cache-file of the ZFS pool:
```
# zpool set cachefile=/etc/zfs/zpool.cache tank
@ -152,9 +139,15 @@ Now to see if everything went successfully, run:
# apk fix kernel-hooks
```
Now to see if everything went successfully, run:
```
# apk fix kernel-hooks
```
and it should give no warnings if done properly.
As discussed earlier `grub` will be replaced, install `gummiboot` as a bootloader.
To install `gummiboot` as friendly bootloader:
```
# apk add gummiboot
@ -163,7 +156,7 @@ As discussed earlier `grub` will be replaced, install `gummiboot` as a bootloade
# cp /usr/lib/gummiboot/gummibootx64.efi /esp/efi/boot/bootx64.efi
```
Sign the bootloader with `sbctl`.
Sign the bootloader with `sbctl`:
```
# sbctl sign -s /esp/efi/boot/bootx64.efi
@ -171,15 +164,7 @@ Sign the bootloader with `sbctl`.
> One may verify the signed files by running `sbctl verify`, in this case `ESP_PATH=/esp` should be defined to work properly.
Remove some remnants of `grub`.
```
# rm -rf /boot/grub
# rm -rf /etc/default
# cd /boot && unlink boot && cd ..
```
`gummiboot` can be configured with the file `/esp/loader/loader.conf` with which the timeout and the default OS can be specified.
`gummiboot` can be configured with the file `/esp/loader/loader.conf` with which the timeout and the default OS can be specified:
```
default alpine-linux-lts.efi
@ -187,8 +172,6 @@ timeout 2
editor no
```
> Here, there should be lines explaining the setup of automatic decryption with TPM using Clevis. Which is still in development...
Now exit the chroot and you should be able to reboot into a working Alpine system.
```

View file

@ -1,6 +1,8 @@
# Provisioning
After flashing the Alpine Linux extended ISO, partition the disks. For this action internet is required since `zfs` and `sgdisk` are not included on the extended ISO, therefore it needs to be obtained from the repository.
Flash the Alpine Linux extended ISO and make sure the secureboot keys are reset and TPM is enabled in the BIOS of the host.
After booting the Alpine Linux extended ISO, partition the disks. For this action internet is required since `zfs`, `sgdisk` and various other necessary packages are not included on the extended ISO, therefore they need to be obtained from the alpine package repository.
To set it up `setup-interfaces` and `setup-apkrepos` will be used.
@ -9,10 +11,12 @@ To set it up `setup-interfaces` and `setup-apkrepos` will be used.
# setup-apkrepos -c1
```
A few packages will have to be installed first:
> To use Wi-Fi simply run `setup-interfaces -r` and select `wlan0` or similar.
A few packages will have to be installed first,
```
# apk add zfs lsblk sgdisk wipefs dosfstools acpid mdadm
# apk add zfs lsblk sgdisk wipefs dosfstools acpid mdadm tpm2-tools zlevis
```
and load the ZFS kernel module
@ -21,7 +25,7 @@ and load the ZFS kernel module
# modprobe zfs
```
Define the disks you want to use for this install
Define the disks you want to use for this install,
```
# export disks="/dev/disk/by-id/<id-disk-1> ... /dev/disk/by-id/<id-disk-n>"
@ -31,7 +35,7 @@ with `<id-disk-n>` for $n \in \mathbb{N}$ the `id` of the disk.
> According to [openzfs-FAQ](https://openzfs.github.io/openzfs-docs/Project%20and%20Community/FAQ.html) using `/dev/disk/by-id/` is the best practice for small pools. For larger pools, using serial Attached SCSI (SAS) and the like, see [vdev_id](https://openzfs.github.io/openzfs-docs/man/master/5/vdev_id.conf.5.html) for proper configuration.
Wipe the existing disk partitions
Wipe the existing disk partitions:
```
# for disk in $disks; do
@ -41,7 +45,7 @@ Wipe the existing disk partitions
> done
```
Create on each disk an `EFI system` partition (ESP) and a `Linux filesystem` partition
Create on each disk an `EFI system` partition (ESP) and a `Linux filesystem` partition:
```
# for disk in $disks; do
@ -50,13 +54,13 @@ Create on each disk an `EFI system` partition (ESP) and a `Linux filesystem` par
> done
```
Create device nodes
Create device nodes:
```
# mdev -s
```
Define the EFI partitions
Define the EFI partitions:
```
# export efiparts=""
@ -66,7 +70,7 @@ Define the EFI partitions
> done
```
Create a `mdraid` array on the EFI partitions
Create a `mdraid` array on the EFI partitions:
```
# modprobe raid1
@ -74,7 +78,7 @@ Create a `mdraid` array on the EFI partitions
# mdadm --assemble --scan
```
Format the array with a FAT32 filesystem
Format the array with a FAT32 filesystem:
```
# mkfs.fat -F 32 /dev/md/esp
@ -92,15 +96,15 @@ Define the pool partitions
> done
```
The ZFS system pool is going to be encrypted. First generate an encryption key and save it temporarily to the file `/tmp/crypt-key.txt` with:
The ZFS system pool is going to be encrypted. First generate an encryption key and save it temporarily to the file `/tmp/tank.key` with:
```
# cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 20 | head -n 1 > /tmp/tank.key && cat /tmp/tank.key
```
> Later on in the guide `clevis` will be used for automatic decryption, so this key only has to be entered a few times. However, if any changes are made to the bios or secureboot then this key will be needed again, so make sure to write it down.
> Later on in the guide `zlevis` will be used for automatic decryption, so this key only has to be entered a few times. However, if any changes are made to the bios or secureboot then this key will be needed again, so make sure to save it.
Create the system pool
Create the system pool:
```
# zpool create -f \
@ -111,26 +115,34 @@ Create the system pool
-O dnodesize=auto \
-O encryption=on \
-O keyformat=passphrase \
-O keylocation=file:///tmp/tank.key \
-O keylocation=prompt \
-m none \
tank raidz1 $poolparts
```
> Additionally, the `spare` option can be used to indicate spare disks. If more redundancy is preferred than `raidz2` and `raidz3` are possible [alternatives](https://openzfs.github.io/openzfs-docs/man/master/7/zpoolconcepts.7.html) for `raidz1`. If a single disk is used the `raidz` option can be left aside. For further information see [zpool-create](https://openzfs.github.io/openzfs-docs/man/master/8/zpool-create.8.html).
Then create the system datasets
Then create the system datasets:
```
# zfs create -o mountpoint=none tank/root
# zfs create -o canmount=noauto -o mountpoint=/ -o atime=off -o quota=24g tank/root/alpine
# zfs create -o mountpoint=legacy -o quota=24g tank/root/alpine
# zfs create -o mountpoint=/home -o atime=off -o setuid=off -o devices=off -o quota=<home-quota> tank/home
# zfs create -o mountpoint=/var -o exec=off -o setuid=off -o devices=off -o quota=16g tank/var
# zfs create -o mountpoint=/var -o atime=off -o exec=off -o setuid=off -o devices=off -o quota=16g tank/var
```
> Setting the `<home-quota>` depends on the total size of the pool, generally try to reserve some empty space in the pool.
Finally, export the zpool
Write the encryption key to TPM and store the jwe in tpm:jwe:
```
# zfs set tpm:jwe=$(zlevis-encrypt '{}' < /tmp/tank.key) tank
```
> To check if it worked, perform `zfs list -Ho tpm:jwe tank | zlevis-decrypt`.
Finally, export the zpool:
```
# zpool export tank
```
```