Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 52 additions & 1 deletion crates/lib/src/bootc_composefs/boot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1142,6 +1146,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) fn setup_composefs_boot(
root_setup: &RootSetup,
Expand Down Expand Up @@ -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)?,
)?;
}

Expand Down
37 changes: 35 additions & 2 deletions crates/lib/src/bootloader.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::fs::create_dir_all;
use std::process::Command;

use anyhow::{anyhow, bail, Context, Result};
Expand All @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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")]
Expand Down
4 changes: 4 additions & 0 deletions docs/src/man/bootc-install.8.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 -->

Expand Down
Loading