Das freie Update-Tool RAUC verspricht, Embedded-Software robust und sicher auf den aktuellen Stand zu bringen. Die Konfiguration dazu spielt allerdings nicht immer auf Anhieb mit.
Software-Updates für vernetzte IoT-, Smart-Home- oder Industrie-4.0-Geräte sind komplex und alternativlos. Praktisch bedeutet fehlerfreie Software nichts als schöne Utopie. Um also bekannte Fehler durch unbekannte zu ersetzen, ohne das ansonsten laufende System zu bricken, überprüft die Installationsroutine die Herkunft sowie Integrität der neuen Gerätesoftware und – nach dem Neustart – deren Funktionalität. Das alles setzt zum einen eine geeignete Versionierung von Hard- und Software voraus und zum anderen ein ausgeklügeltes kryptografisches Framework (PKI).
Neben der Installationsroutine auf dem zu aktualisierenden System, dem Target, braucht es zum Generieren der Update-Datei Softwareunterstützung auf dem Entwicklungsrechner. Die grundsätzlichen Anforderungen, Probleme und Architekturen eines solchen Aktualisierungs-Frameworks beleuchtete bereits die letzte Kern-Technik-Folge [1] am Beispiel der Projekte RAUC [2] und SWUpdate [3]. Diesmal wenden wir uns der Praxis zu, installieren RAUC und testen es in einem überschaubaren Szenario.
Für das Update reicht ein einzelnes Stück Software nicht aus. Vielmehr benötigen wir sowohl Code für den Entwicklungsrechner, auf dem wir das Update-Bundle erstellen, als auch für das Target, auf dem wir das Update einspielen. Ersteres ist der Bundle-Generator, Letzteres der Update-Installer. Genau genommen umfasst RAUC auf dem Target noch mehr Komponenten, denn der eigentliche Installer läuft als Dienst im Hintergrund (Service, Daemon) und erhält Aufträge über ein Command Line Interface (CLI). Bei RAUC ist letztlich alles ein Programm, das abhängig von den Aufrufparametern wechselweise die Funktionen Generator, Installer oder CLI ausübt.
Beim Einsatz vom RAUC gehen Sie nach dem Schema aus Abbildung 1 vor. Zunächst gilt es, die Software auf dem Entwicklungsrechner zu installieren und zu generieren, danach ist das Target an der Reihe. Im nächsten Schritt setzen Sie eine PKI zur Signatur der Updates auf und legen eine Beschreibung der Systemarchitektur des zu aktualisierenden Systems (»system.conf«) an. Gerätebeschreibung und Zertifikat landen schließlich auf dem Target.
Um das Update-Bundle zu produzieren, richten Sie einen Ordner ein und legen sämtliche für das Update benötigten Dateien dort ab. Zusätzlich erstellen Sie ein Manifest als Beschreibung des aktuellen Updates. Es definiert den künftigen Systemzustand, also welche im Bundle befindliche Komponente auf welcher Partition des Zielsystems installiert werden soll. Haben Sie alles beisammen, geht es ans Generieren des Update-Bundles.
Software marsch
Auf dem Zielsystem muss der Update-Server laufen. Befindet sich ein Update-Bundle auf dem Target, lässt sich das Update selbst einspielen. Bei sauberer Integration in den Boot-Prozess sorgt RAUC dafür, dass anschließend in jedem Fall ein funktionstüchtiges System vorliegt. Funktioniert das neu installierte Update nicht, reaktiviert sich das vorherige System.
Listing 1 zeigt die Kommandos, über die sich RAUC unter Ubuntu 20.04 herunterladen und generieren lässt. Der Befehl aus Zeile 4 installiert RAUC auf dem Entwicklungsrechner. Ein erfolgreicher Generierungsvorgang setzt allerdings weitere Softwarepakete voraus. Beispielsweise benötigt RAUC das Paket libdbus-1-dev für die Kommunikation via D-Bus und libssl-dev zur Verschlüsselung (Zeile 3).
Listing 1
Host-Installation
$ mkdir ~/update $ cd ~/update $ sudo apt install build-essential automake libtool libdbus-1-dev libglib2.0-dev libcurl3-dev libssl-dev libjson-glib-dev $ git clone https://github.com/rauc/rauc.git $ cd rauc $ ./autogen.sh $ ./configure $ make
Das durch den Aufruf in der letzten Zeile erzeugte Executable dient sowohl als Bundle-Generator als auch als Bundle-Installer – allerdings mit der Einschränkung, dass es für eine x86-Architektur generiert wurde und somit nur auf einem entsprechenden System läuft. In den meisten Setups dürfte es sich bei der Zielplattform um eine andere CPU-Architektur handeln, vermutlich ARM. Deswegen verwenden Sie zur Host-Target-Entwicklung einen Cross-Compiler und übersetzen RAUC damit. Installieren und Generieren funktionieren auf einem Raspberry Pi unter Pi OS analog.
So leicht das Installieren auf dem Host von der Hand geht, so viel Fingerspitzengefühl verlangt der Umgang mit der Software selbst. Für einen Test brauchen Sie neben dem Entwicklungsrechner ein Zielsystem. Gemäß dem Motto “wer keine Hardware hat, emuliert sich eine” unternehmen Sie am besten mithilfe des Emulators Qemu erste Gehversuche. Damit entfällt angenehmerweise Cross-Generieren von RAUC.
Qemu bildet die Basis für den in RAUC integrierten Softwaretest, den »./qemu-test« im Quellcodeverzeichnis von RAUC startet. Das Ergebnis des Testdurchlaufs legt RAUC in der Datei »test-suite.log« ab. Das per Qemu simulierte System nutzt ähnlich wie ein Container die Programme und Daten des Host-Systems mit. Außer einem separaten Kernel, den sich »qemu-test« übers Netz holt, braucht es dadurch keine weitere Software.
Schlüsselfrage
Neben dem Erzeugen der Software selbst gehört es zu den Vorbereitungen, eine PKI zu erstellen und das Zielsystem zu beschreiben. Für die PKI genügt im einfachsten Fall ein einzelnes Schlüsselpaar, das aus dem privaten Schlüssel und dem selbst unterschriebenen Zertifikat besteht.
Doch Vorsicht: Der Common Name (CN) im Zertifikat muss unbedingt mit dem Inhalt der Variablen »compatible« in der Gerätebeschreibungsdatei »system.conf« übereinstimmen (Abbildung 2). Es empfiehlt sich, das Zertifikat in einem separaten Verzeichnis wie »~/update/ca/« per OpenSSL zu erzeugen (Listing 2).
Listing 2
Schlüsselpaar generieren
$ mkdir -p ~/update/ca
$ cd ~/update/ca
$ openssl req -x509 \
-newkey rsa:4096 \
-keyout key.pem \
-out cert.pem \
-days 3650 -nodes
# ACHTUNG: Common Name passend
# zu system.conf waehlen!
[...]
Als Zielsystem dient wieder das Qemu-System, das Sie per »qemu-test« erzeugen (Abbildung 3). Wie bei einem A/B-System üblich, gibt es ein aktives Root-Dateisystem sowie ein aktives Applikations-Filesystem (A) und das redundante Gegenstück (B) dazu.
Da Sie beim Test das redundante System aktualisieren, lässt sich das aktive System recht einfach modellieren. Pragmatischerweise kommt die aktive Root-Partition (»/dev/root/«) zum Einsatz, die Applikationspartition bleibt leer (»/dev/null/«). Für das redundante System legt das Skript »qemu-test-init« beim Hochfahren per Truncate die zwei (leeren) Image-Dateien »rootdev« und »appdev« im Verzeichnis »/tmp« an.
Eine Beschreibung dieser Gerätestruktur, die RAUC interpretieren kann, zeigt Listing 3. Zusätzlich müssen in der Datei »system.conf« noch Informationen zur Hard- und Softwarerevision zur Verfügung stehen. Das simulierte Gerät heißt im Test »Linux Magazin Test Device«.
Listing 3
system.conf
[system] compatible=Linux Magazin Test Device bootloader=grub grubenv=/tmp/boot/grub/grubenv statusfile=/tmp/rauc.status [keyring] path=/tmp/rauc/ca.cert.pem [slot.rootfs.0] device=/dev/root bootname=A [slot.rootfs.1] device=/tmp/rootdev bootname=B [slot.appfs.0] device=/dev/null parent=rootfs.0 [slot.appfs.1] device=/tmp/appdev parent=rootfs.1
Außer der Datei »system.conf« legen Sie auf dem Target auch noch das Zertifikat schreibgeschützt ab, um darüber später die Authentizität und Integrität einer Update-Datei gewährleisten zu können. Anschließend können Sie bereits Updates erstellen.
Auf dem Host-System mit dem privaten Schlüssel für die digitale Signatur kopieren Sie sämtliche für das Update notwendigen Dateien in das vorbereitete Verzeichnis »update/bundlefiles/«. Dort legen Sie außerdem unter dem Namen »manifest.raucm« das Manifest ab, das das Update im Detail beschreibt.
Für einen Test erstellen Sie mit den Kommandos aus Listing 4 den Ordner »bundlefiles/« und befüllen ihn mit dem Image »rootfs.img« für das Root-Dateisystem. Das Image selbst versorgen Sie mit einigen Pseudo-Files wie einer Datei, in die Sie via »date« den aktuellen Zeitstempel schreiben. Das Applikationsdateisystem erzeugen Sie durch schlichtes Kopieren des Root-Filesystems. Später lassen sich die zugehörigen Slots auf dem Testsystem mit diesen Image-Dateien aktualisieren.
Listing 4
Pseudo-Image erstellen
$ mkdir -p ~/update/bundlefiles $ cd ~/update/bundlefiles $ truncate --size=64M rootfs.img $ mkfs.ext4 rootfs.img $ sudo mount rootfs.img /mnt $ sudo touch /mnt/foo $ sudo touch /mnt/bar $ sudo touch /mnt/new_software $ sudo sh -c "date > date" $ sudo umount /mnt $ cp rootfs.img appfs.img
Den Inhalt der Manifest-Datei im INI-Format sehen Sie in Listing 5. Dort spezifizieren Sie in der Sektion »[update]« die Hardwarerevision in Form des Gerätenamens über die Variable »compatible« und vergeben über die Variable »version« eine Versionsnummer. Auch hier ist es wichtig, den eindeutigen Namen des zu aktualisierenden Systems (»Linux Magazin Test Device«) exakt so zu verwenden, wie er sich bereits im Zertifikat findet.
Listing 5
manifest.raucm
[update] compatible=Linux Magazin Test Device version=2022-04-29 [image.rootfs] filename=rootfs.img [image.appfs] filename=appfs.img
Des Weiteren beschreibt das Manifest, mit welchen Dateien die Slots des Systems bespielt werden sollen. Im Beispiel steht »filename=rootfs.img« für den ersten und »filename=appfs.img« für den zweiten Slot. Dabei genügt die Angabe des Dateinamens. Die für den Integritätsschutz notwendigen SHA256-Hashes erzeugt RAUC beim Erstellen des Update-Bundles ebenso selbstständig wie die Größenangabe der Datei.
Wie schon in der vorigen Kern-Technik-Folge angerissen und in der RAUC-Dokumentation [4] ausführlich erläutert, lassen sich im Manifest noch Handler und Hooks angeben. Als Handler bezeichnet man Skripte oder Programme, die sich bereits auf dem Target befinden und die der Installationsprozess dort aufruft. Hooks werden ebenfalls im Rahmen der Installation aufgerufen, liegen aber dem Update-Bundle bei.
Haben Sie alles Notwendige im Verzeichnis zusammengestellt, starten Sie die Generierung des Bundles unter Angabe des kryptografischen Keys (Listing 6). Der Bundle-Generator erzeugt in einer Image-Datei ein Splash-Filesystem, kopiert sämtliche für das Update zusammengestellten Dateien hinein und signiert das Paket (Abbildung 4).
Listing 6
Update-Bundle erstellen
$ cd ~/update/ $ rauc/rauc -d bundle --cert=ca/cert.pem --key=ca/key.pem bundlefiles/ 2022-04-29-update.rauc
Ortswechsel
Sobald Sie das Qemu-Testsystem »Linux Magazin Test Device« via »qemu_start system« im Quellcodeverzeichnis starten, präpariert Qemu es, stößt RAUC als Service im Hintergrund an und bootet in eine Root-Shell.
Da das Testsystem dank Virtio-Mount direkten Zugriff auf die Dateien des Entwicklungsrechners hat, löst sich damit das Problem des Transports der Konfigurationsdateien und des Update-Bundles schon fast. Fast deshalb, weil RAUC bezüglich Sicherheit erfreulich paranoid ist. So erwartet es beispielsweise, dass die Update-Datei dem User root gehört. Dazu muss das Update-Bundle ins Verzeichnis »/tmp/« kopiert sein.
Zudem ist der von »qemu-test-init« gestartete Update-Server mit der Konfiguration für den Pakettest und nicht mit der von Ihnen verwendeten Konfiguration angelaufen. Daher stoppen Sie ihn mit »killall rauc« und starten ihn unter Angabe der passenden Gerätebeschreibung »system.conf« neu. Listing 7 zeigt das Skript »lm_install.sh«, das alle notwendigen Vorbereitungen übernimmt und das Update anstößt (Abbildung 5).
Listing 7
lm_install.sh
#!/bin/bash cd ~/update/ cp ca/cert.pem /tmp/rauc/ca.cert.pem cp 2022-04-29-update.rauc /tmp/ killall rauc rauc service --conf=system.conf 2>/tmp/service.log & sleep 1 rauc install --keyring=ca/ca.crt --cert=ca/ca.crt /tmp/2022-04-29-update.rauc
RAUC wählt mithilfe des Bootloaders automatisch die redundante und gerade inaktive Partition aus. Im Testsystem ist das die Partition respektive der Slot B. Auf Basis der Dateierweiterung der zum Slot gehörenden Update-Datei entscheidet RAUC, wie es das Update im Detail installiert. Bei einem Image (».img«) kopiert RAUC das Image per Dd in die redundante Partition.
Nach einem erfolgreich abgeschlossenen Update weisen die beiden inaktiven, redundanten Partitionen einen neuen Inhalt auf. Um das zu überprüfen, hängen Sie die Partitionen per Mount-Kommando ein und sehen sich deren Inhalt genauer an (Abbildung 6).
RAUC aktualisiert zudem die Konfiguration des Bootloaders, sodass der – gegebenenfalls mithilfe eines Watchdogs – das sichere Funktionieren des Systems unter allen Umständen garantieren kann. Das Qemu-Testsystem geht dabei von einem Grub-Bootloader aus und richtet beim Booten eine rudimentäre Boot-Partition ein.
Maschine baut Maschine
Im industriellen Umfeld dient RAUC zur Aktualisierung eingebetteter Systeme, die typischerweise nicht auf einer x86-Architektur laufen. Deren Software lässt sich dabei meist mithilfe eines System-Builders wie Yocto oder Buildroot generieren, die RAUC beide unterstützt. Allerdings müssen Entwickler das Erstellen der Updates und Installieren auf dem Target noch in die eigenen Arbeitsprozesse integrieren. RAUC schlägt hier aus Sicherheitsgründen vor, mit unterschiedlichen Zertifikaten zu arbeiten. Das soll verhindern, aus Versehen für die Entwicklung generierte Updates auf Produktivgeräten auszurollen.
Fazit
Im Kontext von RAUC lauern einige Stolperfallen, die letztlich aus den Anforderungen hinsichtlich der Sicherheit und Robustheit resultieren. Entsprechend gilt es, sich erst einmal einige Zeit in das Werkzeug einzuarbeiten und damit zu experimentieren, bevor man es erfolgreich in eigene Projekte einbinden kann. Hinzu kommt, dass die Funktionen erheblich weiter reichen als in diesem Artikel angerissen. So unterstützt RAUC in der aktuellen Version das Streamen sowohl von Updates als auch von Delta-Updates, um ressourcenschonend aufzutreten. Aktualisierungen, die sensible Informationen enthalten, lassen sich verschlüsseln. Darüber hinaus ermöglicht RAUC ein Monitoring des Update-Vorgangs. (csi)
Die Autoren
Eva-Katharina Kunst ist seit den Anfängen von Linux Fan von Open Source. Jürgen Quade, Professor an der Hochschule Niederrhein, bietet auch für Unternehmen Schulungen zu den Themen Treiberprogrammierung und Embedded Linux an.
Infos
- Kern-Technik: Eva-Katharina Kunst, Jürgen Quade, “Luftnummer”, LM 05/2022, S. 76, https://www.lm-online.de/47347
- RAUC: https://rauc.io
- SWUpdate: https://sbabic.github.io/swupdate/swupdate.html
- RAUC-Dokumentation: https://rauc.readthedocs.io/en/latest/index.html












