1
0
mirror of https://github.com/containers/bootc.git synced 2026-02-05 15:45:53 +01:00

sdboot: add support for key enrollment in bootc install

systemd-boot has support for automatically enrolling keys
for Secure Boot, this adds support for copying these keys
as embedded in the input container image into the location
where systemd-boot can perform automatic enrollment on them.

Commit-message-written-by: Colin Walters <walters@verbum.org>
Signed-off-by: Gareth Widlansky <gareth.widlansky@proton.me>
This commit is contained in:
Gareth Widlansky
2025-12-01 21:03:19 -05:00
committed by Colin Walters
parent 3efcbddeb3
commit 439deff2f7
3 changed files with 91 additions and 3 deletions

View File

@@ -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;
@@ -137,6 +137,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
@@ -1146,6 +1150,52 @@ pub(crate) fn setup_composefs_uki_boot(
Ok(())
}
pub struct SecurebootKeys {
pub dir: Dir,
pub keys: Vec<Utf8PathBuf>,
}
fn get_secureboot_keys(fs: &Dir, p: &str) -> Result<Option<SecurebootKeys>> {
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) async fn setup_composefs_boot(
root_setup: &RootSetup,
@@ -1185,6 +1235,7 @@ pub(crate) async fn setup_composefs_boot(
&root_setup.physical_root_path,
&state.config_opts,
None,
get_secureboot_keys(&mounted_fs, BOOTC_AUTOENROLL_PATH)?,
)?;
}

View File

@@ -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<SecurebootKeys>,
) -> 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")]

View File

@@ -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.
<!-- BEGIN GENERATED OPTIONS -->
<!-- END GENERATED OPTIONS -->