From 1de5f0f1155dba09e2018afb5f6a64373ff11668 Mon Sep 17 00:00:00 2001 From: Gareth Widlansky Date: Mon, 1 Dec 2025 21:03:19 -0500 Subject: [PATCH 1/6] add support for key autoenroll to sdboot Signed-off-by: Gareth Widlansky add autoenroll to sdboot Signed-off-by: Gareth Widlansky asdf Signed-off-by: Gareth Widlansky --- crates/lib/src/bootc_composefs/boot.rs | 45 ++++++++++++++++++++++++-- crates/lib/src/bootloader.rs | 32 ++++++++++++++++-- 2 files changed, 73 insertions(+), 4 deletions(-) diff --git a/crates/lib/src/bootc_composefs/boot.rs b/crates/lib/src/bootc_composefs/boot.rs index 67d9f15a9..039f9d9a6 100644 --- a/crates/lib/src/bootc_composefs/boot.rs +++ b/crates/lib/src/bootc_composefs/boot.rs @@ -61,12 +61,12 @@ //! 1. **Primary**: New/upgraded deployment (default boot target) //! 2. **Secondary**: Currently booted deployment (rollback option) -use std::ffi::OsStr; use std::fs::create_dir_all; use std::io::Write; use std::path::Path; +use std::{ffi::OsStr, os::unix::ffi::OsStrExt}; -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; @@ -78,6 +78,7 @@ use cap_std_ext::{ }; use clap::ValueEnum; use composefs::fs::read_file; +use composefs::generic_tree::{Directory, Inode, LeafContent}; use composefs::tree::RegularFile; use composefs_boot::BootOps; use composefs_boot::{ @@ -1142,6 +1143,42 @@ pub(crate) fn setup_composefs_uki_boot( Ok(()) } +pub struct AuthFile { + pub filename: Utf8PathBuf, + pub file: RegularFile, +} + +fn get_autoenroll_keys(root: &Directory>) -> Result> { + let mut entries = vec![]; + match root.get_directory("/usr/lib/bootc/keys".as_ref()) { + Ok(keys_dir) => { + for (filename, inode) in keys_dir.entries() { + if !filename.as_bytes().ends_with(b".auth") { + continue; + } + + let Inode::Leaf(leaf) = inode else { + bail!("/usr/lib/bootc/keys/{filename:?} is a directory"); + }; + + let LeafContent::Regular(file) = &leaf.content else { + bail!("/usr/lib/bootc/keys/{filename:?} is not a regular file"); + }; + let path = match Utf8PathBuf::from_os_string(filename.into()) { + Ok(p) => p, + Err(_) => bail!("couldn't get pathbuf: /usr/lib/bootc/keys/{filename:?}"), + }; + entries.push(AuthFile { + filename: path, + file: file.clone(), + }); + } + } + Err(other) => Err(other)?, + }; + Ok(entries) +} + #[context("Setting up composefs boot")] pub(crate) fn setup_composefs_boot( root_setup: &RootSetup, @@ -1160,6 +1197,8 @@ pub(crate) fn setup_composefs_boot( let postfetch = PostFetchState::new(state, &mounted_fs)?; + let keys = get_autoenroll_keys(&fs.root)?; + let boot_uuid = root_setup .get_boot_uuid()? .or(root_setup.rootfs_uuid.as_deref()) @@ -1181,6 +1220,8 @@ pub(crate) fn setup_composefs_boot( &root_setup.physical_root_path, &state.config_opts, None, + keys, + &repo, )?; } diff --git a/crates/lib/src/bootloader.rs b/crates/lib/src/bootloader.rs index 6126cef9e..f1529ade4 100644 --- a/crates/lib/src/bootloader.rs +++ b/crates/lib/src/bootloader.rs @@ -1,15 +1,19 @@ +use std::fs::create_dir_all; use std::process::Command; use anyhow::{anyhow, bail, Context, Result}; use bootc_utils::CommandRunExt; use camino::Utf8Path; +use cap_std_ext::cap_std::ambient_authority; use cap_std_ext::cap_std::fs::Dir; +use cap_std_ext::dirext::CapStdExtDirExt; +use composefs::fs::read_file; 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, AuthFile}; use crate::{discoverable_partition_specification, utils}; /// The name of the mountpoint for efi (as a subdirectory of /boot, or at the toplevel) @@ -74,6 +78,8 @@ pub(crate) fn install_systemd_boot( _rootfs: &Utf8Path, _configopts: &crate::install::InstallConfigOpts, _deployment_path: Option<&str>, + autoenroll: Vec, + repo: &crate::store::ComposefsRepository, ) -> Result<()> { let esp_part = device .find_partition_of_type(discoverable_partition_specification::ESP) @@ -87,7 +93,29 @@ 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 autoenroll.len() < 1 { + return Ok(()); + } + + println!("Autoenrolling keys"); + let path = esp_path.join("loader/keys/auto"); + create_dir_all(&path)?; + + let keys_dir = Dir::open_ambient_dir(&path, ambient_authority()) + .with_context(|| format!("Opening {path}"))?; + for a in autoenroll.iter() { + let p = path.join(a.filename.clone()); + keys_dir + .atomic_write( + &a.filename, + read_file(&a.file, &repo).context("reading file")?, + ) + .with_context(|| format!("Writing secure boot key: {p}"))?; + println!("Wrote secure boot key: {p}"); + } + Ok(()) } #[context("Installing bootloader using zipl")] From d8a145d8671178f7d719f525c53ec7c7f73f6309 Mon Sep 17 00:00:00 2001 From: Gareth Widlansky Date: Tue, 2 Dec 2025 13:05:43 -0500 Subject: [PATCH 2/6] cleanup Signed-off-by: Gareth Widlansky --- crates/lib/src/bootc_composefs/boot.rs | 67 +++++++++++++------------- crates/lib/src/bootloader.rs | 46 +++++++++--------- 2 files changed, 56 insertions(+), 57 deletions(-) diff --git a/crates/lib/src/bootc_composefs/boot.rs b/crates/lib/src/bootc_composefs/boot.rs index 039f9d9a6..a3f132984 100644 --- a/crates/lib/src/bootc_composefs/boot.rs +++ b/crates/lib/src/bootc_composefs/boot.rs @@ -78,7 +78,6 @@ use cap_std_ext::{ }; use clap::ValueEnum; use composefs::fs::read_file; -use composefs::generic_tree::{Directory, Inode, LeafContent}; use composefs::tree::RegularFile; use composefs_boot::BootOps; use composefs_boot::{ @@ -133,6 +132,8 @@ 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/keys"; + /// 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 @@ -1143,40 +1144,41 @@ pub(crate) fn setup_composefs_uki_boot( Ok(()) } -pub struct AuthFile { - pub filename: Utf8PathBuf, - pub file: RegularFile, +pub enum AutoEnroll { + None, + Keys { dir: Dir, keys: Vec }, } -fn get_autoenroll_keys(root: &Directory>) -> Result> { +fn get_systemd_boot_autoenroll(fs: &Dir, p: &str) -> Result { let mut entries = vec![]; - match root.get_directory("/usr/lib/bootc/keys".as_ref()) { - Ok(keys_dir) => { - for (filename, inode) in keys_dir.entries() { - if !filename.as_bytes().ends_with(b".auth") { - continue; - } - let Inode::Leaf(leaf) = inode else { - bail!("/usr/lib/bootc/keys/{filename:?} is a directory"); - }; - - let LeafContent::Regular(file) = &leaf.content else { - bail!("/usr/lib/bootc/keys/{filename:?} is not a regular file"); - }; - let path = match Utf8PathBuf::from_os_string(filename.into()) { - Ok(p) => p, - Err(_) => bail!("couldn't get pathbuf: /usr/lib/bootc/keys/{filename:?}"), - }; - entries.push(AuthFile { - filename: path, - file: file.clone(), - }); - } + let keys_dir = fs.open_dir(p)?; + + for entry in keys_dir.entries()? { + let file = entry?; + + let name = file.file_name(); + if !file.file_type()?.is_file() { + bail!("/{p}/{name:?} is a not a regular file"); } - Err(other) => Err(other)?, - }; - Ok(entries) + + if !name.as_bytes().ends_with(b".auth") { + continue; + } + + let path = match Utf8PathBuf::from_os_string(name.clone()) { + Ok(p) => p, + Err(_) => bail!("couldn't get pathbuf: /{p}/{name:?}"), + }; + entries.push(path); + } + if entries.len() > 0 { + return Ok(AutoEnroll::Keys { + dir: keys_dir, + keys: entries, + }); + } + return Ok(AutoEnroll::None); } #[context("Setting up composefs boot")] @@ -1197,8 +1199,6 @@ pub(crate) fn setup_composefs_boot( let postfetch = PostFetchState::new(state, &mounted_fs)?; - let keys = get_autoenroll_keys(&fs.root)?; - let boot_uuid = root_setup .get_boot_uuid()? .or(root_setup.rootfs_uuid.as_deref()) @@ -1220,8 +1220,7 @@ pub(crate) fn setup_composefs_boot( &root_setup.physical_root_path, &state.config_opts, None, - keys, - &repo, + get_systemd_boot_autoenroll(&mounted_fs, BOOTC_AUTOENROLL_PATH)?, )?; } diff --git a/crates/lib/src/bootloader.rs b/crates/lib/src/bootloader.rs index f1529ade4..ef5806cec 100644 --- a/crates/lib/src/bootloader.rs +++ b/crates/lib/src/bootloader.rs @@ -7,13 +7,12 @@ use camino::Utf8Path; use cap_std_ext::cap_std::ambient_authority; use cap_std_ext::cap_std::fs::Dir; use cap_std_ext::dirext::CapStdExtDirExt; -use composefs::fs::read_file; use fn_error_context::context; use bootc_blockdev::{Partition, PartitionTable}; use bootc_mount as mount; -use crate::bootc_composefs::boot::{mount_esp, AuthFile}; +use crate::bootc_composefs::boot::{mount_esp, AutoEnroll}; use crate::{discoverable_partition_specification, utils}; /// The name of the mountpoint for efi (as a subdirectory of /boot, or at the toplevel) @@ -23,6 +22,8 @@ pub(crate) const EFI_DIR: &str = "efi"; #[allow(dead_code)] const BOOTUPD_UPDATES: &str = "usr/lib/bootupd/updates"; +const SYSTEMD_AUTOENROLL: &str = "loader/keys/auto"; + #[allow(dead_code)] pub(crate) fn esp_in(device: &PartitionTable) -> Result<&Partition> { device @@ -78,8 +79,9 @@ pub(crate) fn install_systemd_boot( _rootfs: &Utf8Path, _configopts: &crate::install::InstallConfigOpts, _deployment_path: Option<&str>, - autoenroll: Vec, - repo: &crate::store::ComposefsRepository, + // autoenroll: Vec, + // bootc_keys_dir: Option, + autoenroll: AutoEnroll, ) -> Result<()> { let esp_part = device .find_partition_of_type(discoverable_partition_specification::ESP) @@ -95,25 +97,23 @@ pub(crate) fn install_systemd_boot( .log_debug() .run_inherited_with_cmd_context()?; - if autoenroll.len() < 1 { - return Ok(()); - } - - println!("Autoenrolling keys"); - let path = esp_path.join("loader/keys/auto"); - create_dir_all(&path)?; - - let keys_dir = Dir::open_ambient_dir(&path, ambient_authority()) - .with_context(|| format!("Opening {path}"))?; - for a in autoenroll.iter() { - let p = path.join(a.filename.clone()); - keys_dir - .atomic_write( - &a.filename, - read_file(&a.file, &repo).context("reading file")?, - ) - .with_context(|| format!("Writing secure boot key: {p}"))?; - println!("Wrote secure boot key: {p}"); + match autoenroll { + AutoEnroll::None => return Ok(()), + AutoEnroll::Keys { dir, keys } => { + println!("Autoenrolling keys"); + let path = esp_path.join(SYSTEMD_AUTOENROLL); + create_dir_all(&path)?; + + let keys_dir = Dir::open_ambient_dir(&path, ambient_authority()) + .with_context(|| format!("Opening {path}"))?; + for filename in keys.iter() { + let p = path.join(filename.clone()); + keys_dir + .atomic_write(&filename, dir.read(&filename)?) + .with_context(|| format!("Writing secure boot key: {p}"))?; + println!("Wrote secure boot key: {p}"); + } + } } Ok(()) } From f20ccd0223bdae70e29dabd1ff5de92febd8247e Mon Sep 17 00:00:00 2001 From: Gareth Widlansky Date: Tue, 2 Dec 2025 13:32:27 -0500 Subject: [PATCH 3/6] don't expect `/usr/lib/bootc/keys` to exist Signed-off-by: Gareth Widlansky --- crates/lib/src/bootc_composefs/boot.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/lib/src/bootc_composefs/boot.rs b/crates/lib/src/bootc_composefs/boot.rs index a3f132984..037e494e1 100644 --- a/crates/lib/src/bootc_composefs/boot.rs +++ b/crates/lib/src/bootc_composefs/boot.rs @@ -1152,7 +1152,10 @@ pub enum AutoEnroll { fn get_systemd_boot_autoenroll(fs: &Dir, p: &str) -> Result { let mut entries = vec![]; - let keys_dir = fs.open_dir(p)?; + let keys_dir = match fs.open_dir(p) { + Ok(d) => d, + Err(_) => return Ok(AutoEnroll::None), // if the directory doesn't exist it just means we don't have any keys to enroll + }; for entry in keys_dir.entries()? { let file = entry?; From 64ef4c4b4990c1f3c0c435da5f941d1251c003b3 Mon Sep 17 00:00:00 2001 From: Gareth Widlansky Date: Tue, 2 Dec 2025 14:25:59 -0500 Subject: [PATCH 4/6] remove comment Signed-off-by: Gareth Widlansky --- crates/lib/src/bootloader.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/lib/src/bootloader.rs b/crates/lib/src/bootloader.rs index ef5806cec..e4d76a5d8 100644 --- a/crates/lib/src/bootloader.rs +++ b/crates/lib/src/bootloader.rs @@ -79,8 +79,6 @@ pub(crate) fn install_systemd_boot( _rootfs: &Utf8Path, _configopts: &crate::install::InstallConfigOpts, _deployment_path: Option<&str>, - // autoenroll: Vec, - // bootc_keys_dir: Option, autoenroll: AutoEnroll, ) -> Result<()> { let esp_part = device From 16ba8ca675ead3dc9c2e4ae542ea5b82ed59d819 Mon Sep 17 00:00:00 2001 From: Gareth Widlansky Date: Tue, 2 Dec 2025 14:33:09 -0500 Subject: [PATCH 5/6] don't use ambient authority Signed-off-by: Gareth Widlansky --- crates/lib/src/bootloader.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/lib/src/bootloader.rs b/crates/lib/src/bootloader.rs index e4d76a5d8..6199561f2 100644 --- a/crates/lib/src/bootloader.rs +++ b/crates/lib/src/bootloader.rs @@ -4,7 +4,6 @@ use std::process::Command; use anyhow::{anyhow, bail, Context, Result}; use bootc_utils::CommandRunExt; use camino::Utf8Path; -use cap_std_ext::cap_std::ambient_authority; use cap_std_ext::cap_std::fs::Dir; use cap_std_ext::dirext::CapStdExtDirExt; use fn_error_context::context; @@ -102,7 +101,9 @@ pub(crate) fn install_systemd_boot( let path = esp_path.join(SYSTEMD_AUTOENROLL); create_dir_all(&path)?; - let keys_dir = Dir::open_ambient_dir(&path, ambient_authority()) + let keys_dir = esp_mount + .fd + .open_dir(SYSTEMD_AUTOENROLL) .with_context(|| format!("Opening {path}"))?; for filename in keys.iter() { let p = path.join(filename.clone()); From 7825e031c2939d0f1e84556bb65abaea7180a589 Mon Sep 17 00:00:00 2001 From: Gareth Widlansky Date: Tue, 2 Dec 2025 16:35:45 -0500 Subject: [PATCH 6/6] use option rather than an enum Signed-off-by: Gareth Widlansky --- crates/lib/src/bootc_composefs/boot.rs | 16 ++++++++-------- crates/lib/src/bootloader.rs | 6 +++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/crates/lib/src/bootc_composefs/boot.rs b/crates/lib/src/bootc_composefs/boot.rs index 037e494e1..cf82d84c3 100644 --- a/crates/lib/src/bootc_composefs/boot.rs +++ b/crates/lib/src/bootc_composefs/boot.rs @@ -1144,17 +1144,17 @@ pub(crate) fn setup_composefs_uki_boot( Ok(()) } -pub enum AutoEnroll { - None, - Keys { dir: Dir, keys: Vec }, +pub struct AutoEnroll { + pub dir: Dir, + pub keys: Vec, } -fn get_systemd_boot_autoenroll(fs: &Dir, p: &str) -> Result { +fn get_systemd_boot_autoenroll(fs: &Dir, p: &str) -> Result> { let mut entries = vec![]; let keys_dir = match fs.open_dir(p) { Ok(d) => d, - Err(_) => return Ok(AutoEnroll::None), // if the directory doesn't exist it just means we don't have any keys to enroll + Err(_) => return Ok(None), // if the directory doesn't exist it just means we don't have any keys to enroll }; for entry in keys_dir.entries()? { @@ -1176,12 +1176,12 @@ fn get_systemd_boot_autoenroll(fs: &Dir, p: &str) -> Result { entries.push(path); } if entries.len() > 0 { - return Ok(AutoEnroll::Keys { + return Ok(Some(AutoEnroll { dir: keys_dir, keys: entries, - }); + })); } - return Ok(AutoEnroll::None); + return Ok(None); } #[context("Setting up composefs boot")] diff --git a/crates/lib/src/bootloader.rs b/crates/lib/src/bootloader.rs index 6199561f2..1612e293a 100644 --- a/crates/lib/src/bootloader.rs +++ b/crates/lib/src/bootloader.rs @@ -78,7 +78,7 @@ pub(crate) fn install_systemd_boot( _rootfs: &Utf8Path, _configopts: &crate::install::InstallConfigOpts, _deployment_path: Option<&str>, - autoenroll: AutoEnroll, + autoenroll: Option, ) -> Result<()> { let esp_part = device .find_partition_of_type(discoverable_partition_specification::ESP) @@ -95,8 +95,8 @@ pub(crate) fn install_systemd_boot( .run_inherited_with_cmd_context()?; match autoenroll { - AutoEnroll::None => return Ok(()), - AutoEnroll::Keys { dir, keys } => { + None => return Ok(()), + Some(AutoEnroll { dir, keys }) => { println!("Autoenrolling keys"); let path = esp_path.join(SYSTEMD_AUTOENROLL); create_dir_all(&path)?;