Verschlüsselte Root-Partition auf dem Raspberry Pi
Marvin Gülker · 26.10.2014
Der ungewöhnliche Boot-Prozess der Raspberry Pi macht es nicht ganz leicht, die Root-Partition vollständig zu verschlüsseln.
Kategorien: RaspberryPi, Software
Der Bootprozess des Raspberry Pi unterscheidet sich von demjenigen eines gewöhnlichen x86-basierten Desktop-Rechners oder Laptops ganz erheblich. Dort läuft der reguläre Bootprozess wie folgt ab:
- Einschalten
- Power-On Self-Test (POST)
- Firmware wird geladen (BIOS oder EFI)
- Firmware startet den Bootloder der ersten oder konfigurierten Platte (bei EFI optional)
- Bootloader (oder bei EFI das EFI selbst) startet den Kernel
- Der Kernel lädt das initramfs von der Partition, auf der er sich befindet
- Das initramfs bereitet den Hauptsystemstart vor, insbesondere besteht hier die Möglichkeit, das Passwort für eine verschlüsselte Root-Partition zu erfragen.
- Das initramfs bindet die per Kernel-Parameter
root=
übergebene Partition in das Verzeichnis/new_root
ein und führt das Programmswitch_root
aus, welches von Busybox mitgeliefert wird und welches ein Chroot in dieses Verzeichnis gefolgt von einemexec /init
ausführt. /init
, meist ein Symlink auf/sbin/init
,/usr/bin/systemd
oder ähnliches, kümmert sich um den Rest des Starts bis zum Login-Prompt.
Beim Raspberry Pi dagegen läuft der Bootprozess so ab:
- Einschalten
- Firware wird geladen
- Firmware liest die Datei
config.txt
von der ersten Partition der SD-Karte. - Firmware startet den auf der ersten Partition der SD-Karte
befindlichen Kernel, und übergibt ihm den Inhalt der
cmdline.txt
-Datei, die sich auch auf der ersten Partition der SD-Karte befindet. - Der Kernel bindet die Root-Partition ein, welche ihm per
Kernel-Parameter
root=
übergeben wurde. - Der Kernel führt das
/init
-Programm aus, etwa ein Symlink auf/usr/bin/systemd
, das sich um den Rest des Starts bis zum Login-Prompt kümmert.
Insbesondere fällt der Unterschied auf, dass der Raspberry Pi ohne ein
Initramfs bootet. Für einen normalen Betrieb ist dies unerheblich, da
die meisten Anwendungszwecke der initialen Ramdisk mittlerweile
entfallen sind, etwa das Anlegen der Blockdateien unter /dev
, welches
heute von udev
bzw. systemd
automatisiert übernommen wird. Jedoch
gibt es auch heute noch Fälle, in denen ein initramfs erforderlich ist,
insbesondere die Verwendung einer verschlüsselten Root-Partition, da der
Kernel selbst nicht in der Lage ist, ein Kennwort dafür anzufordern
(warum eigentlich nicht?), sodass Hilfsprogramme wie cryptsetup(8)
erforderlich werden. Diese müssen vor Einbindung der verschlüsselten
Root-Partition zur Verfügung stehen, können sich demzufolge auf dieser
nicht befinden. Hierfür bietet das initramfs sich an. Nur: Der Raspberry
Pi hat keins.
Es ist aber offenkundig möglich, eins einzurichten. Forscht man weiter
nach, stößt man in der offiziellen config.txt-Dokumentation für den
Raspberry-Pi-Bootloader auf einen Parameter namens initramfs
, der
eine Datei von der ersten Partition der SD-Karte lesen und an eine
angegebene Adresse im Speicher kopieren kann (urks). Diese
Speicheradresse wiederum kann man dann dem Kernel über den Parameter
initrd=
mitgeben, der das dort vorgefundene komprimierte initramfs
dann entpackt und als Verzeichnis /
einbindet, um unmittelbar darauf
das Programm /init
mit PID 1 auszuführen.
Unter ArchLinux wird ein initramfs normalerweise mithilfe des
Programms mkinitcpio
erzeugt, welches automatisch (per
Post-Install-Skript) nach jedem Kernel-Update ausgeführt wird und
welches dann die Datei /boot/initramfs-linux.img
erzeugt. Unter
ArchLinuxARM ist dieses Programm weder installiert, noch wird es nach
jedem Kernel-Update automatisch ausgeführt. Es befindet sich jedoch in
den Repositories.
Die Installation von ArchLinuxARM auf einem RaspberryPi mit verschlüsselter Root-Partition läuft aufgrund der dargelegten Tatsachen abweichend von der Standardprozedur ab. Eine besondere Hürde stellt die Tatsache da, dass die armv6h-Binaries nicht bzw. nur mit einem erheblichen Aufwand (QEMU) auf einem x86-System ausgeführt werden können. Um diese Problematik zu umgehen, wird ArchLinuxARM im Folgenden gewissermaßen „zweimal“ installiert, einmal auf einem externen USB-Stick, und einmal auf der SD-Karte, wobei letztere die verschlüsselte Root-Partition enthalten soll.
Zunächst einmal ist freilich der aktuelle Root-Tarball
herunterzuladen. Währenddessen können sowohl der USB-Stick als auch
die SD-Karte an einen vorhanden Rechner angeschlossen werden, um
danach auf ihre neue Aufgabe vorbereitet werden zu können. Bei
/dev/sde
handelt es sich bei mir um die SD-Karte, /dev/sdf
ist der
USB-Stick. Diese Angaben müssen selbstverständlich an die lokalen
Abweichungen angepasst werden.
Als erstes werden ein paar Einhängepunkte erstellt:
# cd /mnt # mkdir boot root root2
Dann wird die Partitionierung vorgenommen, d.h. die erste Partition auf
der SD-Karte als FAT32 (wie die RPi-Firmware es erfordert), der Rest als
verschlüsselte LUKS-Partition eingerichtet, in der sich wiederum eine
einzelne ext4-Partition verbirgt. Der USB-Stick wird vollständig mit
einer ext4-Partition bedeckt, er stellt nur das „Hifssystem“ da, welches
wir benötigen, um mkinitcpio
auf dem Pi ausführen zu können. Er steht
nach Abschluss der Arbeiten wieder für andere Zwecke zur Verfügung. Bei
der Wahl der Passphrase für die LUKS-Partition sind die üblichen
Sicherheitsanforderungen zu beachten.
# fdisk /dev/sde > o # Neue Partitionstabelle > n # Neue Partition > p # Primäre Partition > 1 # Erste Partition > [Enter] # Ersten Sektor akzeptieren > +100M # 100 MiB große Partition > t # Typ festlegen > c # Fat mit LBA > n # Neue Partition > 2 # Zweite Partition > [Enter] # Ersten Sektor akzeptieren > [Enter] # Letzten Sektor akzeptieren > w # Ausführen # fdisk /dev/sdf > o # Neue Partitionstabelle > n # Neue Partition > p # Primäre Partition > 1 # Erste Partition > [Enter] # Ersten Sektor akzeptieren > [Enter] # Letzten Sektor akzeptieren > w # Ausführen # mkfs.vfat /dev/sde1 # cryptsetup luksFormat /dev/sde2 > geheimepassphrase # cryptsetup luksOpen /dev/sde2 sdcard # mkfs.ext4 /dev/mapper/sdcard # mkfs.ext4 /dev/sdf1
Die neu erstellten Dateisysteme einbinden:
# mount /dev/sde1 /mnt/boot # mount /dev/mapper/sdcard /mnt/root # mount /dev/sdf1 /mnt/root2
Nun wird der Root-Dateisystemtarball zweimal entpackt. Einmal für das
verschlüsselte Hauptsystem, und einmal für das unverschlüsselte
Hilfssystem. Außerdem wird der Inhalt des Verzeichnisses boot/
der
offiziellen Anleitung entsprechend auf die erste Partition der SD-Karte
kopiert.
# cd /mnt # tar -xpf /home/quintus/Downloads/ArchlinuxARM-rpi-latest.tar.gz -C root # tar -xpf /home/quintus/Downloads/ArchlinuxARM-rpi-latest.tar.gz -C root2 # mv root/boot/* boot/
Da das verschlüsselte System zum jetzigen Zeitpunkt nicht bootfähig ist
(kein initramfs zum Abfragen der Passphrase), muss zunächst das
Hilfssystem auf dem USB-Stick als Root-Partition angegeben werden. Dazu
wird die Datei boot/cmdline.txt
geöffnet und der Kernel-Parameter
root=
wie folgt geändert:
root=/dev/sda1
Alle anderen Parameter bleiben unverändert erhalten. Da es sich bei dem
USB-Stick um das einzige Blockdevice im engeren Sinne handelt, wird er
zwangsweise unter der Bezeichnung sda
von udev erkannt werden.
Jetzt können alle Partitionen ausgehangen werden:
# umount boot root root2 # cryptsetup luksClose sdcard
Nachdem diese Kommandos durchgelaufen sind, können SD-Karte und
USB-Stick vom bisherigen Rechner getrennt und an den Raspberry Pi
angesteckt werden. Außerdem werden eine USB-Tastatur sowie ein Monitor
(HDMI) und ein Ethernet-Kabel an den Raspberry Pi angeschlossen. Dann
wird das Gerät durch Einstecken des Stromkabels gestartet. Läuft alles
gut, befindet man sich jetzt im unverschlüsselten Hilfssystem, auf dem
man sich als root anmelden kann (Passwort standardmäßig root
). Dort
umgehend die Paketdatenbank aktualisieren und mkinitcpio
installieren
(eine Internetverbindung via DHCP stellt ArchLinuxARM bereits
standardmäßig automatisch her, ein manueller Aufruf von dhcpcd
entfällt daher):
# pacman -Sy mkinitcpio
Nun wird die Konfigurationsdatei von mkinitcpio
,
/etc/mkinitcpio.conf
so angepasst, dass die HOOKS
-Zeile so aussieht
(als Editor steht nano
zur Verfügung):
HOOKS="base udev autodetect modconf block keyboard encrypt filesystems fsck"
Dabei handelt es sich um die Standard-Hooks von ArchLinux, mit der
Besonderheit, dass der filesystems
-Hook hinter dem encrypt
-Hook
ausgeführt wird (da selbige sich in einer verschlüsselten Partition
befinden) und dass keyboard
vor dem neu einzufügenden encrypt
-Hook
zu listen ist, um bei der Passworteingabe bereits Tastaturzugriff zu
genießen.
Anschließend wird mkinitcpio
aufgerufen, um das initramfs zu bauen:
# mkinitcpio -k $(uname -r) -g /boot/initrd
Dies erzeugt die Datei /boot/initrd
. Der Name ist frei wählbar und hat
keine spezifische Bedeutung. Dieser Befehl muss nach jedem Kernel-Update
wie schon erwähnt neu ausgeführt werden, da er Kernelmodule in das
initramfs verpackt, und Kernelmodule sind per definitionem abhängig von
der Kernelversion.
Mit dem Wissen um die Option initramfs
in der Raspberry-Pi-Konfiguration wird nun die Datei
/boot/config.txt
um folgende Zeile ergänzt:
initramfs initrd 0x00f00000
Dies führt dazu, dass die Datei initrd
auf der ersten Partition der
SD-Karte vom RPi-Bootloader an die Speicheradresse 0x00f00000 kopiert
wird, bevor der Kernel geladen wird. Diese Adresse stammt
von dieser Seite, ihre
Herkunft ist mir unbekannt. Die Elinux.org-Seite empfiehlt als Adresse
dagegen 0x00800000. Die genaue Wahl dürfte jedoch recht unerheblich
sein, da es sich nicht um einen fest definierten Wert handelt. Vielmehr
wird diese Adresse nun dem Kernel über die Kommandozeilenoption
initrd=
mitgeteilt, indem die Datei /boot/cmdline.txt
entsprechend
bearbeitet wird. Neben dieser Option werden die üblichen, für eine
verschlüsselte Root-Partition
erforderlichen
Parameter cryptdevice=
und root=
hinzugefügt bzw. angepasst:
cryptdevice=/dev/mmcblk0p2:root root=/dev/mapper/root initrd=0x00f00000
Die übrigen Kernel-Parameter bleiben unverändert. cryptdevice=
instruiert den encrypt
-Hook des initramfs, nach einem Passwort für
diese Partition zu fragen und anschließend das entschlüsselte
Device-Node mit dem Namen root
(das ist der Teil hinter dem
Doppelpunkt, der Name ist frei wählbar) unterhalb von /dev/mapper
zu
erstellen. Dieser Pfad wird dann an die bekannte Kernel-Option root=
gegeben, die für das initiale Einhängen von /
verantwortlich ist.
initrd=
dient wie beschrieben dazu, dem Kernel mitzuteilen, wo im
Speicher sich das initramfs befindet (dort, wo dem RPi-Bootloader gesagt
wurde, dass er es hinpacken soll).
Zuletzt wird die Datei /etc/fstab
so abgeändert, dass sie einen neuen
Eintrag für das Root-Dateisystem erhält. Folgende Zeile ist als erste in
die Datei aufzunehmen, die übrigen Zeilen (es sollte sich lediglich um
eine weitere für /boot
handeln) rutschen nach unten.
/dev/mapper/root / ext4 defaults 0 1
Leerzeile am Ende der Datei nicht vergessen.
Damit sind die Arbeiten abgeschlossen. Neustart:
# systemctl reboot
Das initramfs sollte nunmehr geladen werden und nach einem Passwort für
die verschlüsselte Partition fragen. Nach Entschlüsselung wird diese als
/
einghangen und das Hauptsystem gebootet. Dort nun ebenfalls
mkinitcpio
installieren:
# pacman -Sy mkinitcpio
Fertig. Bei jedem Kernel-Update muss nun nur noch daran gedacht werden,
mkinitcpio
mit der Version des zukünftigen Kernels auszuführen (hat
man also etwa 3.15.1 laufen und 3.15.2 kam per Update rein, muss der
Befehl auf mkinitcpio -k 3.15.2 -g /boot/initrd
lauten).
Der RaspberryPi kann nun heruntergefahren werden und der USB-Stick abgezogen werden.