diff --git a/crates/lib/src/bootc_composefs/boot.rs b/crates/lib/src/bootc_composefs/boot.rs index 67d9f15a9..61d4e03af 100644 --- a/crates/lib/src/bootc_composefs/boot.rs +++ b/crates/lib/src/bootc_composefs/boot.rs @@ -66,7 +66,7 @@ use std::fs::create_dir_all; use std::io::Write; use std::path::Path; -use anyhow::{anyhow, Context, Result}; +use anyhow::{anyhow, bail, Context, Result}; use bootc_blockdev::find_parent_devices; use bootc_kernel_cmdline::utf8::{Cmdline, Parameter}; use bootc_mount::inspect_filesystem_of_dir; @@ -132,6 +132,10 @@ const SYSTEMD_LOADER_CONF_PATH: &str = "loader/loader.conf"; const INITRD: &str = "initrd"; const VMLINUZ: &str = "vmlinuz"; +const BOOTC_AUTOENROLL_PATH: &str = "usr/lib/bootc/install/secureboot-keys"; + +const AUTH_EXT: &str = "auth"; + /// We want to be able to control the ordering of UKIs so we put them in a directory that's not the /// directory specified by the BLS spec. We do this because we want systemd-boot to only look at /// our config files and not show the actual UKIs in the bootloader menu @@ -1142,6 +1146,52 @@ pub(crate) fn setup_composefs_uki_boot( Ok(()) } +pub struct SecurebootKeys { + pub dir: Dir, + pub keys: Vec, +} + +fn get_secureboot_keys(fs: &Dir, p: &str) -> Result> { + let mut entries = vec![]; + + // if the dir doesn't exist, return None + let keys_dir = match fs.open_dir_optional(p)? { + Some(d) => d, + _ => return Ok(None), + }; + + // https://github.com/systemd/systemd/blob/26b2085d54ebbfca8637362eafcb4a8e3faf832f/man/systemd-boot.xml#L392 + + for entry in keys_dir.entries()? { + let dir_e = entry?; + let dirname = dir_e.file_name(); + if !dir_e.file_type()?.is_dir() { + bail!("/{p}/{dirname:?} is not a directory"); + } + + let dir_path: Utf8PathBuf = dirname.try_into()?; + let dir = dir_e.open_dir()?; + for entry in dir.entries()? { + let e = entry?; + let local: Utf8PathBuf = e.file_name().try_into()?; + let path = dir_path.join(local); + + if path.extension() != Some(AUTH_EXT) { + continue; + } + + if !e.file_type()?.is_file() { + bail!("/{p}/{path:?} is not a file"); + } + entries.push(path); + } + } + return Ok(Some(SecurebootKeys { + dir: keys_dir, + keys: entries, + })); +} + #[context("Setting up composefs boot")] pub(crate) fn setup_composefs_boot( root_setup: &RootSetup, @@ -1181,6 +1231,7 @@ pub(crate) fn setup_composefs_boot( &root_setup.physical_root_path, &state.config_opts, None, + get_secureboot_keys(&mounted_fs, BOOTC_AUTOENROLL_PATH)?, )?; } diff --git a/crates/lib/src/bootloader.rs b/crates/lib/src/bootloader.rs index 6126cef9e..6d27497d7 100644 --- a/crates/lib/src/bootloader.rs +++ b/crates/lib/src/bootloader.rs @@ -1,3 +1,4 @@ +use std::fs::create_dir_all; use std::process::Command; use anyhow::{anyhow, bail, Context, Result}; @@ -9,7 +10,7 @@ use fn_error_context::context; use bootc_blockdev::{Partition, PartitionTable}; use bootc_mount as mount; -use crate::bootc_composefs::boot::mount_esp; +use crate::bootc_composefs::boot::{mount_esp, SecurebootKeys}; use crate::{discoverable_partition_specification, utils}; /// The name of the mountpoint for efi (as a subdirectory of /boot, or at the toplevel) @@ -19,6 +20,9 @@ pub(crate) const EFI_DIR: &str = "efi"; #[allow(dead_code)] const BOOTUPD_UPDATES: &str = "usr/lib/bootupd/updates"; +// from: https://github.com/systemd/systemd/blob/26b2085d54ebbfca8637362eafcb4a8e3faf832f/man/systemd-boot.xml#L392 +const SYSTEMD_KEY_DIR: &str = "loader/keys"; + #[allow(dead_code)] pub(crate) fn esp_in(device: &PartitionTable) -> Result<&Partition> { device @@ -74,6 +78,7 @@ pub(crate) fn install_systemd_boot( _rootfs: &Utf8Path, _configopts: &crate::install::InstallConfigOpts, _deployment_path: Option<&str>, + autoenroll: Option, ) -> Result<()> { let esp_part = device .find_partition_of_type(discoverable_partition_specification::ESP) @@ -87,7 +92,35 @@ pub(crate) fn install_systemd_boot( Command::new("bootctl") .args(["install", "--esp-path", esp_path.as_str()]) .log_debug() - .run_inherited_with_cmd_context() + .run_inherited_with_cmd_context()?; + + if let Some(SecurebootKeys { dir, keys }) = autoenroll { + let path = esp_path.join(SYSTEMD_KEY_DIR); + create_dir_all(&path)?; + + let keys_dir = esp_mount + .fd + .open_dir(SYSTEMD_KEY_DIR) + .with_context(|| format!("Opening {path}"))?; + + for filename in keys.iter() { + let p = path.join(&filename); + + // create directory if it doesn't already exist + if let Some(parent) = p.parent() { + create_dir_all(parent)?; + } + + dir.copy(&filename, &keys_dir, &filename) + .with_context(|| format!("Copying secure boot key: {p}"))?; + println!("Wrote Secure Boot key: {p}"); + } + if keys.is_empty() { + tracing::debug!("No Secure Boot keys provided for systemd-boot enrollment"); + } + } + + Ok(()) } #[context("Installing bootloader using zipl")] diff --git a/docs/src/man/bootc-install.8.md b/docs/src/man/bootc-install.8.md index cc4a91a4c..43a585a39 100644 --- a/docs/src/man/bootc-install.8.md +++ b/docs/src/man/bootc-install.8.md @@ -28,6 +28,10 @@ updates. An installation is not simply a copy of the container filesystem, but includes other setup and metadata. +## Secure Boot Keys + +When installing with `systemd-boot`, bootc can let `systemd-boot` can handle enrollment of Secure Boot keys by putting signed EFI signature lists in `/usr/lib/bootc/install/secureboot-keys` which will copy over into `ESP/loader/keys` after bootloader installation. The keys will be copied to `loader/keys` subdirectory of the ESP. after installing `systemd-boot` to the system. More information on how key enrollment works with `systemd-boot` is available in the [systemd-boot](https://github.com/systemd/systemd/blob/26b2085d54ebbfca8637362eafcb4a8e3faf832f/man/systemd-boot.xml#L392) man page. +