Skip to content

Commit 6bd32cd

Browse files
author
Gareth Widlansky
committed
add autoenroll to sdboot
Signed-off-by: Gareth Widlansky <gareth.widlansky@proton.me>
1 parent e715203 commit 6bd32cd

File tree

4 files changed

+104
-8
lines changed

4 files changed

+104
-8
lines changed

Dockerfile.sdboot

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
# Override via --build-arg=base=<image> to use a different base
22
ARG base=localhost/bootc
33
# Image to sign systemd-boot first, BEFORE, installing onto the image
4-
ARG buildroot=quay.io/centos/centos:stream10
4+
# We need to use fedora for the `efitools` package
5+
ARG buildroot=quay.io/fedora/fedora:42
56

67
FROM $base AS base-unsigned
78

89
FROM $buildroot as buildroot-base
910
RUN <<EORUN
1011
set -xeuo pipefail
11-
dnf install -y pesign openssl
12+
dnf install -y pesign openssl efitools
1213
dnf clean all
1314
EORUN
1415

@@ -41,10 +42,29 @@ EORUN
4142

4243

4344
FROM base-unsigned as final
44-
RUN --mount=type=bind,from=signer,target=/run/sdboot \
45+
RUN --mount=type=secret,id=cert \
46+
--mount=type=bind,from=signer,target=/run/sdboot \
4547
<<EORUN
4648
set -xeuo pipefail
49+
50+
# enable auto enrollment for systemd-boot
51+
auto_enroll=/usr/lib/bootc/keys
52+
mkdir -p ${auto_enroll}
53+
# in prod, it would be a good idea to also enroll the microsoft keys here as well
54+
openssl x509 -outform DER -in run/secrets/cert -out "${auto_enroll}/db.cer"
55+
4756
sdboot=/usr/lib/systemd/boot/efi/systemd-bootx64.efi
4857
# copy signed sdboot from buildroot
4958
cp "/run/sdboot/sdboot.efi" ${sdboot}
59+
cp -r "/run/sdboot/keys" "/usr/lib/bootc/keys/"
60+
61+
guid=$(cat /run/secrets/guid)
62+
cert-to-efi-sig-list -g ${guid} /run/secrets/cert /db.esl
63+
sign-efi-sig-list -k /run/secrets/key -c /run/secrets/cert db /db.esl ${keyout}/db.auth
64+
65+
cert-to-efi-sig-list -g ${guid} /run/secrets/pk_cert /pk.esl
66+
sign-efi-sig-list -k /run/secrets/pk_key -c /run/secrets/pk_cert PK /pk.esl ${keyout}/pk.auth
67+
68+
cert-to-efi-sig-list -g ${guid} /run/secrets/kek_cert /kek.esl
69+
sign-efi-sig-list -k /run/secrets/kek_key -c /run/secrets/kek_cert KEK /kek.esl ${keyout}/kek.auth
5070
EORUN

crates/lib/src/bootc_composefs/boot.rs

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
use std::ffi::OsStr;
21
use std::fs::create_dir_all;
32
use std::io::Write;
43
use std::path::Path;
4+
use std::{ffi::OsStr, os::unix::ffi::OsStrExt};
55

6-
use anyhow::{anyhow, Context, Result};
6+
use anyhow::{anyhow, bail, Context, Result};
77
use bootc_blockdev::find_parent_devices;
88
use bootc_kernel_cmdline::utf8::{Cmdline, Parameter};
99
use bootc_mount::inspect_filesystem_of_dir;
@@ -15,6 +15,7 @@ use cap_std_ext::{
1515
};
1616
use clap::ValueEnum;
1717
use composefs::fs::read_file;
18+
use composefs::generic_tree::{Directory, Inode, LeafContent};
1819
use composefs::tree::RegularFile;
1920
use composefs_boot::BootOps;
2021
use composefs_boot::{
@@ -1026,6 +1027,42 @@ pub(crate) fn setup_composefs_uki_boot(
10261027
Ok(())
10271028
}
10281029

1030+
pub struct AuthFile {
1031+
pub filename: Utf8PathBuf,
1032+
pub file: RegularFile<Sha512HashValue>,
1033+
}
1034+
1035+
fn get_autoenroll_keys(root: &Directory<RegularFile<Sha512HashValue>>) -> Result<Vec<AuthFile>> {
1036+
let mut entries = vec![];
1037+
match root.get_directory("/usr/lib/bootc/keys".as_ref()) {
1038+
Ok(keys_dir) => {
1039+
for (filename, inode) in keys_dir.entries() {
1040+
if !filename.as_bytes().ends_with(b".auth") {
1041+
continue;
1042+
}
1043+
1044+
let Inode::Leaf(leaf) = inode else {
1045+
bail!("/usr/lib/bootc/keys/{filename:?} is a directory");
1046+
};
1047+
1048+
let LeafContent::Regular(file) = &leaf.content else {
1049+
bail!("/usr/lib/bootc/keys/{filename:?} is not a regular file");
1050+
};
1051+
let path = match Utf8PathBuf::from_os_string(filename.into()) {
1052+
Ok(p) => p,
1053+
Err(_) => bail!("couldn't get pathbuf: /usr/lib/bootc/keys/{filename:?}"),
1054+
};
1055+
entries.push(AuthFile {
1056+
filename: path,
1057+
file: file.clone(),
1058+
});
1059+
}
1060+
}
1061+
Err(other) => Err(other)?,
1062+
};
1063+
Ok(entries)
1064+
}
1065+
10291066
#[context("Setting up composefs boot")]
10301067
pub(crate) fn setup_composefs_boot(
10311068
root_setup: &RootSetup,
@@ -1044,6 +1081,8 @@ pub(crate) fn setup_composefs_boot(
10441081

10451082
let postfetch = PostFetchState::new(state, &mounted_fs)?;
10461083

1084+
let keys = get_autoenroll_keys(&fs.root)?;
1085+
10471086
let boot_uuid = root_setup
10481087
.get_boot_uuid()?
10491088
.or(root_setup.rootfs_uuid.as_deref())
@@ -1065,6 +1104,8 @@ pub(crate) fn setup_composefs_boot(
10651104
&root_setup.physical_root_path,
10661105
&state.config_opts,
10671106
None,
1107+
keys,
1108+
&repo,
10681109
)?;
10691110
}
10701111

crates/lib/src/bootloader.rs

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
1+
use std::fs::create_dir_all;
12
use std::process::Command;
23

34
use anyhow::{anyhow, bail, Context, Result};
45
use bootc_utils::CommandRunExt;
56
use camino::Utf8Path;
7+
use cap_std_ext::cap_std::ambient_authority;
68
use cap_std_ext::cap_std::fs::Dir;
9+
use cap_std_ext::dirext::CapStdExtDirExt;
10+
use composefs::fs::read_file;
711
use fn_error_context::context;
812

913
use bootc_blockdev::{Partition, PartitionTable};
1014
use bootc_mount as mount;
1115

12-
use crate::bootc_composefs::boot::mount_esp;
16+
use crate::bootc_composefs::boot::{mount_esp, AuthFile};
1317
use crate::{discoverable_partition_specification, utils};
1418

1519
/// 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(
7478
_rootfs: &Utf8Path,
7579
_configopts: &crate::install::InstallConfigOpts,
7680
_deployment_path: Option<&str>,
81+
autoenroll: Vec<AuthFile>,
82+
repo: &crate::store::ComposefsRepository,
7783
) -> Result<()> {
7884
let esp_part = device
7985
.find_partition_of_type(discoverable_partition_specification::ESP)
@@ -87,7 +93,29 @@ pub(crate) fn install_systemd_boot(
8793
Command::new("bootctl")
8894
.args(["install", "--esp-path", esp_path.as_str()])
8995
.log_debug()
90-
.run_inherited_with_cmd_context()
96+
.run_inherited_with_cmd_context()?;
97+
98+
if autoenroll.len() < 1 {
99+
return Ok(());
100+
}
101+
102+
println!("Autoenrolling keys");
103+
let path = esp_path.join("loader/keys/auto");
104+
create_dir_all(&path)?;
105+
106+
let keys_dir = Dir::open_ambient_dir(&path, ambient_authority())
107+
.with_context(|| format!("Opening {path}"))?;
108+
for a in autoenroll.iter() {
109+
let p = path.join(a.filename.clone());
110+
keys_dir
111+
.atomic_write(
112+
&a.filename,
113+
read_file(&a.file, &repo).context("reading file")?,
114+
)
115+
.with_context(|| format!("Writing secure boot key: {p}"))?;
116+
println!("Wrote secure boot key: {p}");
117+
}
118+
Ok(())
91119
}
92120

93121
#[context("Installing bootloader using zipl")]

tests/build-sealed

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,14 @@ fi
5757
sdboot_signed="${input_image}_signed"
5858
podman build -t $sdboot_signed --build-arg=base=${input_image} \
5959
--secret=id=key,src=${secureboot}/db.key \
60-
--secret=id=cert,src=${secureboot}/db.crt -f Dockerfile.sdboot .
60+
--secret=id=cert,src=${secureboot}/db.crt \
61+
--secret=id=pk_key,src=${secureboot}/PK.key \
62+
--secret=id=pk_cert,src=${secureboot}/PK.crt \
63+
--secret=id=kek_key,src=${secureboot}/KEK.key \
64+
--secret=id=kek_cert,src=${secureboot}/KEK.crt \
65+
--secret=id=guid,src=${secureboot}/GUID.txt \
66+
-f Dockerfile.sdboot .
67+
6168

6269
graphroot=$(podman system info -f '{{.Store.GraphRoot}}')
6370
echo "Computing composefs digest..."

0 commit comments

Comments
 (0)