From 365f9d0f03275e52b7f33d9fc9e6990969925560 Mon Sep 17 00:00:00 2001 From: Pragyan Poudyal Date: Fri, 27 Feb 2026 12:00:42 +0530 Subject: [PATCH 1/3] xtask: Add `seal-state` and `boot-type` options seal-state: Required to switch between secure/insecure firmware options boot-type: Required to send kargs to only bls installs Signed-off-by: Pragyan Poudyal --- crates/xtask/src/tmt.rs | 24 +++++++++++--------- crates/xtask/src/xtask.rs | 46 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 10 deletions(-) diff --git a/crates/xtask/src/tmt.rs b/crates/xtask/src/tmt.rs index 1f14ddcf4..6306b4e7e 100644 --- a/crates/xtask/src/tmt.rs +++ b/crates/xtask/src/tmt.rs @@ -34,7 +34,7 @@ const DISTRO_CENTOS_9: &str = "centos-9"; const COMPOSEFS_KERNEL_ARGS: [&str; 1] = ["--karg=enforcing=0"]; // Import the argument types from xtask.rs -use crate::{Bootloader, RunTmtArgs, TmtProvisionArgs}; +use crate::{BootType, Bootloader, RunTmtArgs, SealState, TmtProvisionArgs}; /// Generate a random alphanumeric suffix for VM names fn generate_random_suffix() -> String { @@ -113,12 +113,7 @@ const DEFAULT_SB_KEYS_DIR: &str = "target/test-secureboot"; /// /// For sealed images, secure boot keys must be present or an error is returned. #[context("Building firmware arguments")] -fn build_firmware_args( - sh: &Shell, - image: &str, - bootloader: &Option, -) -> Result> { - let is_sealed = is_sealed_image(sh, image)?; +fn build_firmware_args(is_sealed: bool, bootloader: &Option) -> Result> { let sb_keys_dir = Utf8Path::new(DEFAULT_SB_KEYS_DIR); let r = if is_sealed { @@ -349,7 +344,12 @@ pub(crate) fn run_tmt(sh: &Shell, args: &RunTmtArgs) -> Result<()> { println!("Detected distro: {}", distro); println!("Detected VARIANT_ID: {variant_id}"); - let firmware_args = build_firmware_args(sh, image, &args.bootloader)?; + let firmware_args = build_firmware_args( + args.seal_state + .as_ref() + .is_some_and(|v| *v == SealState::Sealed), + &args.bootloader, + )?; // Create tmt-workdir and copy tmt bits to it // This works around https://github.com/teemtee/tmt/issues/4062 @@ -488,7 +488,11 @@ pub(crate) fn run_tmt(sh: &Shell, args: &RunTmtArgs) -> Result<()> { let filesystem = args.filesystem.as_deref().unwrap_or("ext4"); opts.push(format!("--filesystem={}", filesystem)); opts.push("--composefs-backend".into()); - opts.extend(COMPOSEFS_KERNEL_ARGS.map(|x| x.into())); + + // UKI install fails with extra args + if args.boot_type == BootType::Bls { + opts.extend(COMPOSEFS_KERNEL_ARGS.map(|x| x.into())); + } } if let Some(b) = &args.bootloader { @@ -750,7 +754,7 @@ pub(crate) fn tmt_provision(sh: &Shell, args: &TmtProvisionArgs) -> Result<()> { println!(" VM name: {}\n", vm_name); // TODO: Send bootloader param here - let firmware_args = build_firmware_args(sh, image, &None)?; + let firmware_args = build_firmware_args(is_sealed_image(sh, image)?, &None)?; // Launch VM with bcvk // Use ds=iid-datasource-none to disable cloud-init for faster boot diff --git a/crates/xtask/src/xtask.rs b/crates/xtask/src/xtask.rs index 056bd780e..1e0fc0d44 100644 --- a/crates/xtask/src/xtask.rs +++ b/crates/xtask/src/xtask.rs @@ -96,6 +96,44 @@ impl Display for Bootloader { } } +/// The boot type for composefs backend +#[derive(Debug, Default, Clone, ValueEnum, PartialEq, Eq)] +pub enum BootType { + /// Type1 (BLS) boot + #[default] + Bls, + /// UKI boot + Uki, +} + +impl Display for BootType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + BootType::Bls => f.write_str("bls"), + BootType::Uki => f.write_str("uki"), + } + } +} + +/// Whether the image is sealed or not +#[derive(Debug, Default, Clone, ValueEnum, PartialEq, Eq)] +pub enum SealState { + /// The image is sealed + Sealed, + /// The image is unsealed + #[default] + Unsealed, +} + +impl Display for SealState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + SealState::Sealed => f.write_str("sealed"), + SealState::Unsealed => f.write_str("unsealed"), + } + } +} + /// Arguments for run-tmt command #[derive(Debug, Args)] pub(crate) struct RunTmtArgs { @@ -130,6 +168,14 @@ pub(crate) struct RunTmtArgs { #[arg(long, requires = "composefs_backend")] pub(crate) filesystem: Option, + + /// Required to switch between secure/insecure firmware options + #[arg(long, requires = "composefs_backend")] + pub(crate) seal_state: Option, + + // Required to send kargs to only bls installs + #[arg(long, default_value_t, requires = "composefs_backend")] + pub(crate) boot_type: BootType, } /// Arguments for tmt-provision command From a698c15ef66a24719a44c063b562f42223c59c1f Mon Sep 17 00:00:00 2001 From: Pragyan Poudyal Date: Fri, 27 Feb 2026 12:02:33 +0530 Subject: [PATCH 2/3] composefs/tests: More flexibility for insecure UKI testing Update the CI matrix to include `seal_state` and `boot_type`. This does not increase our matrix, but only rearranges it to be a bit more meaningful. Earlier even when testing "insecure UKI", it still showed up as "composefs-sealeduki-sdboot" which is incorrect. This also allows us flexibility to, in future, test grub + UKI which is disabled currently. Update Justfile and the Dockerfile to make use of these new arguments. Now we only sign the UKI, if `seal_state == sealed`, and in the Justfile we disallow combinations that don't make sense, like BLS boot + sealed, allowing missing verity (xfs) + sealed, etc. Signed-off-by: Pragyan Poudyal --- .github/workflows/ci.yml | 79 +++++++++++++++++++------------------- Dockerfile | 9 +++-- Justfile | 65 +++++++++++++++++-------------- contrib/packaging/seal-uki | 17 ++++++-- 4 files changed, 94 insertions(+), 76 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6797e5004..66de5e026 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -161,22 +161,36 @@ jobs: matrix: # No fedora-44 due to https://bugzilla.redhat.com/show_bug.cgi?id=2429501 test_os: [fedora-43, centos-9, centos-10] - variant: [ostree, composefs-sealeduki-sdboot, composefs-sdboot, composefs-grub] + variant: [ostree, composefs] filesystem: ["ext4", "xfs"] + bootloader: ["grub", "systemd"] + boot_type: ["bls", "uki"] + seal_state: ["sealed", "unsealed"] + exclude: - # centos-9 UKI is experimental/broken (https://github.com/bootc-dev/bootc/issues/1812) - - test_os: centos-9 - variant: composefs-sealeduki-sdboot # centos-9 fails with EUCLEAN (https://github.com/bootc-dev/bootc/issues/1812) # See: https://github.com/bootc-dev/bcvk/pull/204 - test_os: centos-9 - variant: composefs-sdboot - - test_os: centos-9 - variant: composefs-grub + variant: composefs + - seal_state: "sealed" + boot_type: bls + - seal_state: "sealed" + bootloader: grub + - seal_state: "sealed" + filesystem: xfs + - seal_state: "unsealed" + filesystem: ext4 + - bootloader: grub + boot_type: "uki" + # We only test filesystems for composefs to test if composefs backend will work on fs # without fsverity - variant: ostree filesystem: ext4 + - variant: ostree + boot_type: uki + - variant: ostree + bootloader: systemd runs-on: ubuntu-24.04 @@ -194,35 +208,13 @@ jobs: BASE=$(just pullspec-for-os base ${{ matrix.test_os }}) echo "BOOTC_base=${BASE}" >> $GITHUB_ENV echo "RUST_BACKTRACE=full" >> $GITHUB_ENV - echo "RUST_LOG=trace" >> $GITHUB_ENV - echo "BOOTC_filesystem=${{ matrix.filesystem }}" >> $GITHUB_ENV + echo "RUST_LOG=debug" >> $GITHUB_ENV - case "${{ matrix.variant }}" in - composefs-grub) - echo "BOOTC_variant=composefs" >> $GITHUB_ENV - echo "BOOTC_bootloader=grub" >> $GITHUB_ENV - ;; - - composefs-sdboot) - echo "BOOTC_variant=composefs" >> $GITHUB_ENV - echo "BOOTC_bootloader=systemd" >> $GITHUB_ENV - ;; - - composefs-sealeduki-sdboot) - echo "BOOTC_variant=${{ matrix.variant }}" >> $GITHUB_ENV - echo "BOOTC_bootloader=systemd" >> $GITHUB_ENV - ;; - - ostree) - echo "BOOTC_variant=${{ matrix.variant }}" >> $GITHUB_ENV - echo "BOOTC_bootloader=grub" >> $GITHUB_ENV - ;; - esac - - if [ "${{ matrix.variant }}" = "composefs-sealeduki-sdboot" ]; then - BUILDROOTBASE=$(just pullspec-for-os buildroot-base ${{ matrix.test_os }}) - echo "BOOTC_buildroot_base=${BUILDROOTBASE}" >> $GITHUB_ENV - fi + echo "BOOTC_variant=${{ matrix.variant }}" >> $GITHUB_ENV + echo "BOOTC_filesystem=${{ matrix.filesystem }}" >> $GITHUB_ENV + echo "BOOTC_bootloader=${{ matrix.bootloader }}" >> $GITHUB_ENV + echo "BOOTC_boot_type=${{ matrix.boot_type }}" >> $GITHUB_ENV + echo "BOOTC_seal_state=${{ matrix.seal_state }}" >> $GITHUB_ENV - name: Download package artifacts uses: actions/download-artifact@v7 @@ -241,14 +233,14 @@ jobs: - name: Unit and container integration tests run: just test-container - - name: Validate composefs digest (sealed UKI only) - if: matrix.variant == 'composefs-sealeduki-sdboot' + - name: Validate composefs digest (UKI only) + if: matrix.boot_type == 'uki' run: just validate-composefs-digest - name: Run TMT integration tests run: | - if [[ "${{ matrix.variant }}" = composefs* ]]; then - just "test-${{ matrix.variant }}" "${{ matrix.filesystem }}" + if [[ "${{ matrix.variant }}" = composefs ]]; then + just test-composefs "${{ matrix.bootloader }}" "${{ matrix.filesystem }}" "${{ matrix.boot_type }}" "${{ matrix.seal_state }}" else just test-tmt integration fi @@ -259,7 +251,14 @@ jobs: if: always() uses: actions/upload-artifact@v6 with: - name: tmt-log-PR-${{ github.event.number }}-${{ matrix.test_os }}-${{ matrix.variant }}-${{ matrix.filesystem }}-${{ env.ARCH }} + name: "tmt-log-PR-${{ github.event.number }}-\ + ${{ matrix.test_os }}-\ + ${{ matrix.variant }}-\ + ${{ matrix.bootloader }}-\ + ${{ matrix.boot_type }}-\ + ${{ matrix.filesystem }}-\ + ${{ matrix.seal_state }}-\ + ${{ env.ARCH }}" path: /var/tmp/tmt # Test bootc install on Fedora CoreOS (separate job to avoid disk space issues diff --git a/Dockerfile b/Dockerfile index 2d32c49a1..d89ede527 100644 --- a/Dockerfile +++ b/Dockerfile @@ -177,6 +177,8 @@ RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp FROM tools as sealed-uki ARG variant ARG filesystem +ARG seal_state +ARG boot_type # Install our bootc package (only needed for the compute-composefs-digest command) RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp \ --mount=type=bind,from=packages,src=/,target=/run/packages \ @@ -194,20 +196,21 @@ if [[ $filesystem == "xfs" ]]; then allow_missing_verity=true fi -if test "${variant}" = "composefs-sealeduki-sdboot"; then - /run/packaging/seal-uki /run/target /out /run/secrets $allow_missing_verity +if test "${boot_type}" = "uki"; then + /run/packaging/seal-uki /run/target /out /run/secrets $allow_missing_verity $seal_state fi EORUN # And now the final image FROM base-penultimate ARG variant +ARG boot_type # Copy the sealed UKI and finalize the image (remove raw kernel, create symlinks) RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp \ --mount=type=bind,from=packaging,src=/,target=/run/packaging \ --mount=type=bind,from=sealed-uki,src=/,target=/run/sealed-uki <&2 + exit 1 +fi # Find the kernel version (needed for output filename) kver=$(bootc container inspect --rootfs "${target}" --json | jq -r '.kernel.version') @@ -28,10 +35,12 @@ ukifyargs=(--measure --json pretty --output "${output}/${kver}.efi") -# Signing options, we use sbsign by default -ukifyargs+=(--signtool sbsign - --secureboot-private-key "${secrets}/secureboot_key" - --secureboot-certificate "${secrets}/secureboot_cert") +if [[ $seal_state == "sealed" ]]; then + # Signing options, we use sbsign by default + ukifyargs+=(--signtool sbsign + --secureboot-private-key "${secrets}/secureboot_key" + --secureboot-certificate "${secrets}/secureboot_cert") +fi # Baseline container ukify options containerukifyargs=(--rootfs "${target}") From b2bc375bdab18d21e60442427cfe979b5d4ce1b1 Mon Sep 17 00:00:00 2001 From: Pragyan Poudyal Date: Fri, 27 Feb 2026 15:54:11 +0530 Subject: [PATCH 3/3] tests: Fix container tests, update CI matrix Remove "composefs-sealeduki-sdboot" variant Only exclude ext4-unsealed-uki as we still want to run tests on unsealed bls ext4 systems Signed-off-by: Pragyan Poudyal --- .github/workflows/ci.yml | 1 + Justfile | 4 ++-- crates/tests-integration/src/container.rs | 21 +++++++++++++-------- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 66de5e026..8b77e107b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -180,6 +180,7 @@ jobs: filesystem: xfs - seal_state: "unsealed" filesystem: ext4 + boot_type: uki # we still want to test ext4 unsealed bls - bootloader: grub boot_type: "uki" diff --git a/Justfile b/Justfile index 7c85078ab..a33e9640f 100644 --- a/Justfile +++ b/Justfile @@ -114,7 +114,7 @@ test-tmt *ARGS: build [group('core')] test-container: build build-units podman run --rm --read-only localhost/bootc-units /usr/bin/bootc-units - podman run --rm --env=BOOTC_variant={{variant}} --env=BOOTC_base={{base}} {{base_img}} bootc-integration-tests container + podman run --rm --env=BOOTC_variant={{variant}} --env=BOOTC_base={{base}} --env=BOOTC_boot_type={{boot_type}} {{base_img}} bootc-integration-tests container [group('core')] test-composefs bootloader filesystem boot_type seal_state: @@ -133,7 +133,7 @@ test-composefs bootloader filesystem boot_type seal_state: filesystem={{filesystem}} \ boot_type={{boot_type}} \ seal_state={{seal_state}} \ - test-tmt-nobuild --composefs-backend \ + test-tmt --composefs-backend \ --bootloader={{bootloader}} \ --filesystem={{filesystem}} \ --seal-state={{seal_state}} \ diff --git a/crates/tests-integration/src/container.rs b/crates/tests-integration/src/container.rs index 032d9842c..52671fbbd 100644 --- a/crates/tests-integration/src/container.rs +++ b/crates/tests-integration/src/container.rs @@ -50,9 +50,12 @@ pub(crate) fn test_bootc_container_inspect() -> Result<()> { .expect("kernel.unified should be present") .as_bool() .expect("kernel.unified should be a boolean"); + + let is_uki = std::env::var("BOOTC_boot_type").is_ok_and(|var| var == "uki"); + if let Some(variant) = std::env::var("BOOTC_variant").ok() { - match variant.as_str() { - v @ "ostree" | v @ "composefs" => { + match (variant.as_str(), is_uki) { + (v @ "ostree", _) | (v @ "composefs", false) => { assert!(!unified, "Expected unified=false for variant {v}"); // For traditional kernels, version should look like a uname (contains digits) assert!( @@ -60,7 +63,7 @@ pub(crate) fn test_bootc_container_inspect() -> Result<()> { "version should contain version numbers for traditional kernel: {version}" ); } - "composefs-sealeduki-sdboot" => { + ("composefs", true) => { assert!(unified, "Expected unified=true for UKI variant"); // For UKI, version is the filename without .efi extension (should not end with .efi) assert!( @@ -70,7 +73,7 @@ pub(crate) fn test_bootc_container_inspect() -> Result<()> { // Version should be non-empty after stripping extension assert!(!version.is_empty(), "version should not be empty for UKI"); } - o => eprintln!("notice: Unhandled variant for kernel check: {o}"), + o => eprintln!("notice: Unhandled variant for kernel check: {o:?}"), } } @@ -155,17 +158,19 @@ fn test_system_reinstall_help() -> Result<()> { /// Verify that the values of `variant` and `base` from Justfile actually applied /// to this container image. fn test_variant_base_crosscheck() -> Result<()> { + let is_uki = std::env::var("BOOTC_boot_type").is_ok_and(|var| var == "uki"); + if let Some(variant) = std::env::var("BOOTC_variant").ok() { // TODO add this to `bootc status` or so? let boot_efi = Utf8Path::new("/boot/EFI"); - match variant.as_str() { - "composefs" | "ostree" => { + match (variant.as_str(), is_uki) { + ("composefs", false) | ("ostree", _) => { assert!(!boot_efi.try_exists()?); } - "composefs-sealeduki-sdboot" => { + ("composefs", true) => { assert!(boot_efi.try_exists()?); } - o => panic!("Unhandled variant: {o}"), + o => panic!("Unhandled variant: {o:?}"), } } if let Some(base) = std::env::var("BOOTC_base").ok() {