Just enough buildroot to run docker applications from a light weight OS wrapper providing verified boot, A/B booting, updates and file system encryption.
The high level approach is to have a single file boot ramdisk that contains a bootable Linux initramfs that is capable of updating itself and installing an application component. This image can be signed for secure-boot and launched using the Raspberry Pi 4 (or newer) network-install feature in the bootloader.
The application component is installed to a local block device (e.g. MMC). Typically, a self-updating service such as docker would be used as in this example however, it should be straightforward to replace docker with other solutions.
See https://github.com/timg236/buildroot/tree/rpi-system-update for the buildroot packages and defconfig.
The software update process is separate from the component which downloads
new updates to install. For simplicity, this example uses scripts curl
to download updates.
- Runs on Raspberry Pi 4 and Pi 5 family.
- Minimal patches to core buildroot.
- Easy for users to customize the setup scripts as desired e.g. custom docker setup.
- Easy to verify / understand security model.
- Replacable FOTA model (for when wget isn't enough!)
The system-software in the boot ramdisk contains:-
- Linux Kernel
- Device-tree overlays
- initramfs containing modules and software required to install and launch the application component (e.g. Docker)
- A RSA public key used to verify updates.
- Raspberry Pi GPU firmware (aka start.elf) for Pi4 family.
This package shows how signed boot images can be used to securely provision and upgrade the system. Please see the secure-boot tutorial for more information about how to enable secure-boot in the bootloader.
The rpi-system-update
package also implements an option (BR2_PACKAGE_RPI_SYSTEM_UPDATE_ENCRYPT_APPLICATION_FS
)
to encrypt the application file-system using LUKS and a private key stored in the OTP.
The key must have already been programmed e.g using the rpi-otp-private-key
utility in the usbboot repo.
IMPORTANT:
- Since this is an example UART login is enabled in order to debug / experiment with the update scripts. In a production system this should be disabled when deciding what if any remote access should be permitted.
The system consists of four main components:-
- The application image - installed to an SD card (or build time configurable block-device).
- A network install
boot.img
that can be launched via the Raspberry Pi bootloader. This downloads the full application image to a blank SD card. - A bootloader EEPROM image configured to download the net-install release from the web server.
- A web-server hosting the latest application image updates (
boot.img
andboot.sig
) files plus version manifest file.
This system uses the latest (rpi-update master) kernel and firmware releases and no custom configuration is required.
To customize the firmware config.txt
or cmdline.txt
edit the files under board/raspberrypi-system-update
- The Raspberry Pi bootloader is updated with a custom EEPROM image containing the network install configuration.
- User powers on the Raspberry Pi holding down a push-button attached to a GPIO.
- This triggers GPIO conditional filter in the EEPROM config to go into network install mode.
- The network install
boot.img
is downloaded into RAM and executed. rpi-system-update
is launched via systemd ininstall mode
.rpi-system-update
waits for the block device specified in/etc/default/rpi-system-update
to appear. It then creates the file-system.rpi-systme-update
downloads theversion
andversion.sig
files specified in/etc/default/rpi-eeprom-update
.rpi-system-update
verifies the authenticity ofversion
+version.sig
using the public key specified in/etc/default/rpi-eeprom-update
. If the verification fails then the version files are downloaded again.rpi-system-update
downloads the compressedboot.img.gz
specified in the version file and the corresponding signature file.rpi-system-update
decompresses theboot.img.gz
, verifies the signature. If the signature matches then theboot.img
is decompressed and installed to either/boot/boota
or/boot/bootb
depending on whatever partition is the active partition (the one the system booted from. The inactive partition is referred to as theTRYBOOT partition
.rpi-system-update
triggers a reboot.- The EEPROM bootloader boots from the specified block device (since the system-update GPIO button has been released).
- The bootloader loads
boot.img
, checks the signature if secure-boot is enabled then starts booting. rpi-system-update
is run inapplication mode
. If the local file-system is blank then it is formatted as Linux and configured for the specified docker service. Otherwise, it is just mounted and docker starts as normal.
rpi-system-update
is run indownload mode
from a systemd timer.- If a newer version is available it is downloaded if an upgrade is NOT already pending
- After verifying the authenticity of the download
rpi-system-update
copies the new image to theTRYBOOT partition
rpi-system-update
sets a flag (a file) indicating that there is a pending update ready to be tested.- When the system is next rebooted the
rpi-system-update
will apply the upgrade.
rpi-system-update
is launched by systemdupgrade mode
early during boot.- If a
tryboot
upgrade is pending thenrpi-system-update
clears the update-pending flag and runssudo reboot "0 tryboot"
to reboot intotryboot
mode. - If the system is already in
tryboot
mode then the update was successful so/boot/auto/autoboot.txt
is modified to swap the active/tryboot partitions. - The system is rebooted into the new
active partition.
This package installs rpi-system-update scripts and systemd services.
The package config provides various configuration options so that /etc/default/rpi-system-update
can be automatically generated from the buildroot defconfig.
See BR2_PACKAGE_RPI_SYSTEM_UPDATE_*
options in "make menuconfig"
For DEBUG ONLY, this package also creates a user called 'admin' with SSH access. Password authentication is
not enabled, instead define the authorized_keys
files to be written to /home/admin/.ssh/authorized_keys
This package installs the secure-boot
scripts from the usbboot to support signing of boot images.
There are some minor enhancements to this package.
- Make it easier to select a custom command line and
config.txt
file from the buildroot board config files. - Install the overlays to the initrd image to support runtime dtoverlay configuration from the userspace in an initrd.
mkdir -p rpi-buildroot-dev
cd rpi-buildroot-dev
git clone --branch rpi-system-update https://github.com/timg236/buildroot buildroot
git clone --branch rpi-system-update https://github.com/timg236/buildroot buildroot-net-install
git clone --branch rpi-system-update https://github.com/timg236/rpi-system-update rpi-system-update
Before building the buildroot image the signing keys, and update server information must be defined.
cd buildroot
make raspberrypi-system-update_defconfig
make menuconfig
Go to Target packages --> System Tools --> rpi-system-update
Public key
The path of the public key .PEM file must be supplies so that it can be embedded in the image file. This is used to check the authenticity of newer versions of the image.
Private key
If a private-key file is specified then the this will be used to automatically sign the image file. Alternatively, leave this blank to create an unsigned image. That might be desirable if the private is stored in a TPM and you want to sign the images on a separate secure server.
Update URL
The URL of the version
file that rpi-system-update
will poll for updates.
Version number
The version number is simply an integer defined by BR2_PACKAGE_RPI_SYSTEM_UPDATE_VERSION
and embedded in /etc/default/rpi-system-update
.
After updating the version number the rpi-system-update
package must be rebuilt.
- The
rpi-eeprom-update
script never downgrades to a lower version number. - Version numbers do not describe API compatibility.
cd buildroot
make raspberrypi-system-update_defconfig
make
Output files:-
- output/target/images/boot.img.gz
- output/target/images/boot.sig
The output images can be copied to the download server and the verison
file update.
Rebuilding after a configuration change
make raspberrypi-system-update_defconfig && make rpi-system-update-reconfigure && make
The network-install image is optimized for size (32-bit kernel) and needs to be built in a different tree.
However, it's unlikely that this would need to be updated frequently because it's sole purpose is to
download and install the real boot.img
.
cd buildroot-net-install
make raspberrypi-system-update-net-install_defconfig
make
Configure the public key, private key and update URL. The version number should be set to 0 since the initial install just calls the update routine.
Output files:-
- output/target/images/boot.img
- output/target/images/boot.sig
The following EEPROM configuration shows how a GPIO conditional filter may be used to start an automated network install image.
Example rpi-system-update/server/boot.conf
If secure-boot
is enabled in OTP then the contents of the boot.img
file is always verified using the EEPROM public key. If secure boot is not required then SIGNED_BOOT
can be placed within the GPIO conditional section so that the signature is only checked for the network install step.
Replace update.raspberrypi.com
with the domain name of your software update server.
[all]
BOOT_UART=1
HDMI_DELAY=0
WAKE_ON_GPIO=1
POWER_OFF_ON_HALT=0
[gpio2=0]
# There is currently no support for custom CA certs so HTTP only for now.
# version.sig and boot.sig verify authenticity.
SIGNED_BOOT=1
BOOT_ORDER=0xf7
HTTP_HOST=update.raspberrypi.com
HTTP_PATH=rpi-system-update/net-install
To generate rpi-eeprom-recovery.zip
run
mkdir -p update-server
./rpi-system-update/build-eeprom update-server
To use a different EEPROM image specify the filename in the RPI_EEPROM_IMAGE
environment variable.
The rpi-system-update/server/add-release
script can be used to create populate a new directory in the suitable structure from images built from buildroot.
Edit rpi-system-update-server/config
to specify the base URL to include
in the version
file.
Add release version 2 to the update-server directory.
mkdir -p update-server
./rpi-system-update/server/add-release update-server 2
Update the net-install images from the buildroot-net-install
mkdir -p update-server
./rpi-system-update/server/add-release update-server net-install
Example version
file - replace with your HTTP (not HTTPS) server name.
version: 2
signature-url: https://update.raspberrypi.com/releases/1/boot.sig
image-url: https://update.raspberrypi.com/releases/1/boot.img.gz
The HomeAssistant docker-application is a real world example to verify the installation and launching of applications in an encrypted disk. Docker applications also benefit from Docker's built in install / OTA mechanism at the application.
The docker service is selected by defining BR2_PACKAGE_RPI_SYSTEM_UPDATE_DOCKER_APPLICATION
e.g. home-assistant
.