diff --git a/Dockerfile b/Dockerfile index ce5170955..6cd2df42a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -57,10 +57,31 @@ FROM scratch as base COPY --from=target-base /target-rootfs/ / # SKIP_CONFIGS=1 skips LBIs, test kargs, and install configs (for FCOS testing) ARG SKIP_CONFIGS +ARG boot_type +ARG seal_state # Use tmpfs for /run and /tmp with bind mounts inside to avoid leaking mount stubs into the image RUN --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp \ - --mount=type=bind,from=src,src=/src/hack,target=/run/hack \ + --mount=type=bind,from=src,src=/src/hack,target=/run/hack <<-EOF + set -ex + cd /run/hack/ && SKIP_CONFIGS="${SKIP_CONFIGS}" ./provision-derived.sh + + pkgs_to_install=() + if [[ "${seal_state}" == "sealed" ]]; then + pkgs_to_install+=(sbsigntools) + fi + + # Install systemd-ukify and systemd-boot for UKIs + # This also installs systemd-boot for the grub UKI case which is not ideal... + if [[ "${boot_type}" == "uki" ]]; then + pkgs_to_install+=(systemd-ukify) + fi + + if [[ ${#pkgs_to_install[@]} -gt 0 ]]; then + dnf install -y "${pkgs_to_install[@]}" + fi +EOF + # Note we don't do any customization here yet # Mark this as a test image LABEL bootc.testimage="1" @@ -165,14 +186,24 @@ RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp FROM base as base-penultimate ARG variant ARG bootloader +ARG boot_type + # Switch to a signed systemd-boot, if configured 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=sdboot-signed,src=/,target=/run/sdboot-signed </dev/null; then fi # First install the unsigned systemd-boot RPM to get the package in place -rpm -Uvh "${src}"/*.rpm +rpm -Uvh --replacepkgs "${src}"/*.rpm # Now find where it installed the binary and override with our signed version sdboot=$(ls /usr/lib/systemd/boot/efi/systemd-boot*.efi) diff --git a/crates/lib/src/bootc_composefs/status.rs b/crates/lib/src/bootc_composefs/status.rs index 58a93638a..f5a17ab8c 100644 --- a/crates/lib/src/bootc_composefs/status.rs +++ b/crates/lib/src/bootc_composefs/status.rs @@ -450,6 +450,7 @@ async fn boot_entry_from_composefs_deployment( storage: &Storage, origin: tini::Ini, verity: &str, + missing_verity_allowed: bool, ) -> Result { let image = match origin.get::("origin", ORIGIN_CONTAINER) { Some(img_name_from_config) => { @@ -502,6 +503,7 @@ async fn boot_entry_from_composefs_deployment( boot_type, bootloader: get_bootloader()?, boot_digest, + missing_verity_allowed, }), soft_reboot_capable: false, }; @@ -784,8 +786,15 @@ async fn composefs_deployment_status_from( let ini = tini::Ini::from_string(&config) .with_context(|| format!("Failed to parse file {verity_digest}.origin as ini"))?; - let mut boot_entry = - boot_entry_from_composefs_deployment(storage, ini, &verity_digest).await?; + let mut boot_entry = boot_entry_from_composefs_deployment( + storage, + ini, + &verity_digest, + // We will either have verity enforced or not (possible but we don't allow it) + // There won't be two deployments with one enforcing verity and one not + cmdline.allow_missing_fsverity, + ) + .await?; // SAFETY: boot_entry.composefs will always be present let boot_type_from_origin = boot_entry.composefs.as_ref().unwrap().boot_type; diff --git a/crates/lib/src/cli.rs b/crates/lib/src/cli.rs index b1752c2c2..9866a0346 100644 --- a/crates/lib/src/cli.rs +++ b/crates/lib/src/cli.rs @@ -438,6 +438,10 @@ pub(crate) enum ContainerOpts { #[clap(long)] allow_missing_verity: bool, + /// Write a dumpfile to this path + #[clap(long)] + write_dumpfile_to: Option, + /// Additional arguments to pass to ukify (after `--`). #[clap(last = true)] args: Vec, @@ -1791,8 +1795,15 @@ async fn run_from_opt(opt: Opt) -> Result<()> { rootfs, kargs, allow_missing_verity, + write_dumpfile_to, args, - } => crate::ukify::build_ukify(&rootfs, &kargs, &args, allow_missing_verity), + } => crate::ukify::build_ukify( + &rootfs, + &kargs, + &args, + allow_missing_verity, + write_dumpfile_to.as_deref(), + ), ContainerOpts::Export { format, target, diff --git a/crates/lib/src/spec.rs b/crates/lib/src/spec.rs index 15428d451..f06d056d1 100644 --- a/crates/lib/src/spec.rs +++ b/crates/lib/src/spec.rs @@ -270,6 +270,8 @@ pub struct BootEntryComposefs { /// The sha256sum of vmlinuz + initrd /// Only `Some` for Type1 boot entries pub boot_digest: Option, + /// Whether fs-verity validation is optional + pub missing_verity_allowed: bool, } /// A bootable entry diff --git a/crates/lib/src/status.rs b/crates/lib/src/status.rs index ec3cd4e1d..c6c3e8232 100644 --- a/crates/lib/src/status.rs +++ b/crates/lib/src/status.rs @@ -606,6 +606,27 @@ fn write_download_only( Ok(()) } +fn write_fsverity_enforcement( + mut out: impl Write, + entry: &crate::spec::BootEntry, + prefix_len: usize, +) -> Result<()> { + if let Some(cfs) = &entry.composefs { + write_row_name(&mut out, "FsVerity", prefix_len)?; + writeln!( + out, + "{}", + if cfs.missing_verity_allowed { + "Not Enforced" + } else { + "Enforced" + } + )?; + }; + + Ok(()) +} + /// Render cached update information, showing what update is available. /// /// This is populated by a previous `bootc upgrade --check` that found @@ -734,6 +755,8 @@ fn human_render_slot( // Show soft-reboot capability write_soft_reboot(&mut out, entry, prefix_len)?; + write_fsverity_enforcement(&mut out, entry, prefix_len)?; + // Show download-only lock status write_download_only(&mut out, slot, entry, prefix_len)?; } diff --git a/crates/lib/src/ukify.rs b/crates/lib/src/ukify.rs index 8e3b82b67..40ad491b3 100644 --- a/crates/lib/src/ukify.rs +++ b/crates/lib/src/ukify.rs @@ -31,6 +31,7 @@ pub(crate) fn build_ukify( extra_kargs: &[String], args: &[OsString], allow_missing_fsverity: bool, + write_dumpfile_to: Option<&Utf8Path>, ) -> Result<()> { // Warn if --karg is used (temporary workaround) if !extra_kargs.is_empty() { @@ -78,7 +79,7 @@ pub(crate) fn build_ukify( } // Compute the composefs digest - let composefs_digest = compute_composefs_digest(rootfs, None)?; + let composefs_digest = compute_composefs_digest(rootfs, write_dumpfile_to)?; // Get kernel arguments from kargs.d let mut cmdline = crate::bootc_kargs::get_kargs_in_root(&root, std::env::consts::ARCH)?; @@ -131,7 +132,7 @@ mod tests { let tempdir = tempfile::tempdir().unwrap(); let path = Utf8Path::from_path(tempdir.path()).unwrap(); - let result = build_ukify(path, &[], &[], false); + let result = build_ukify(path, &[], &[], false, None); assert!(result.is_err()); let err = format!("{:#}", result.unwrap_err()); assert!( @@ -149,7 +150,7 @@ mod tests { fs::create_dir_all(tempdir.path().join("boot/EFI/Linux")).unwrap(); fs::write(tempdir.path().join("boot/EFI/Linux/test.efi"), b"fake uki").unwrap(); - let result = build_ukify(path, &[], &[], false); + let result = build_ukify(path, &[], &[], false, None); assert!(result.is_err()); let err = format!("{:#}", result.unwrap_err()); assert!( diff --git a/crates/xtask/src/tmt.rs b/crates/xtask/src/tmt.rs index 2fc9b9db8..94136fc07 100644 --- a/crates/xtask/src/tmt.rs +++ b/crates/xtask/src/tmt.rs @@ -23,6 +23,7 @@ const FIELD_SUMMARY: &str = "summary"; const FIELD_ADJUST: &str = "adjust"; const FIELD_FIXME_SKIP_IF_COMPOSEFS: &str = "fixme_skip_if_composefs"; +const FIELD_FIXME_SKIP_IF_UKI: &str = "fixme_skip_if_uki"; // bcvk options const BCVK_OPT_BIND_STORAGE_RO: &str = "--bind-storage-ro"; @@ -246,6 +247,7 @@ fn verify_ssh_connectivity(sh: &Shell, port: u16, key_path: &Utf8Path) -> Result struct PlanMetadata { try_bind_storage: bool, skip_if_composefs: bool, + skip_if_uki: bool, } /// Parse integration.fmf to extract extra-try_bind_storage for all plans @@ -286,6 +288,7 @@ fn parse_plan_metadata( .and_modify(|m| m.try_bind_storage = b) .or_insert(PlanMetadata { try_bind_storage: b, + skip_if_uki: false, skip_if_composefs: false, }); } @@ -301,6 +304,23 @@ fn parse_plan_metadata( .and_modify(|m| m.skip_if_composefs = b) .or_insert(PlanMetadata { skip_if_composefs: b, + skip_if_uki: false, + try_bind_storage: false, + }); + } + } + + if let Some(skip_if_uki) = plan_data.get(&serde_yaml::Value::String(format!( + "extra-{}", + FIELD_FIXME_SKIP_IF_UKI + ))) { + if let Some(b) = skip_if_uki.as_bool() { + plan_metadata + .entry(plan_name.to_string()) + .and_modify(|m| m.skip_if_uki = b) + .or_insert(PlanMetadata { + skip_if_uki: b, + skip_if_composefs: false, try_bind_storage: false, }); } @@ -407,6 +427,16 @@ pub(crate) fn run_tmt(sh: &Shell, args: &RunTmtArgs) -> Result<()> { }); } + if matches!(args.boot_type, crate::BootType::Uki) { + plans.retain(|plan| { + !plan_metadata + .iter() + .find(|(key, _)| plan.ends_with(key.as_str())) + .map(|(_, v)| v.skip_if_uki) + .unwrap_or(false) + }); + } + if plans.len() < original_plans_count { println!( "Filtered from {} to {} plan(s) based on arguments: {:?}", @@ -910,6 +940,8 @@ struct TestDef { try_bind_storage: bool, /// Whether to skip this test for composefs backend skip_if_composefs: bool, + /// Whether to skip this test for images with UKI + skip_if_uki: bool, /// TMT fmf attributes to pass through (summary, duration, adjust, etc.) tmt: serde_yaml::Value, } @@ -1011,12 +1043,24 @@ pub(crate) fn update_integration() -> Result<()> { .and_then(|v| v.as_bool()) .unwrap_or(false); + let skip_if_uki = metadata + .extra + .as_mapping() + .and_then(|m| { + m.get(&serde_yaml::Value::String( + FIELD_FIXME_SKIP_IF_UKI.to_string(), + )) + }) + .and_then(|v| v.as_bool()) + .unwrap_or(false); + tests.push(TestDef { number: metadata.number, name: display_name, test_command, try_bind_storage, skip_if_composefs, + skip_if_uki, tmt: metadata.tmt, }); } @@ -1154,6 +1198,13 @@ pub(crate) fn update_integration() -> Result<()> { ); } + if test.skip_if_uki { + plan_value.insert( + serde_yaml::Value::String(format!("extra-{}", FIELD_FIXME_SKIP_IF_UKI)), + serde_yaml::Value::Bool(true), + ); + } + plans_mapping.insert( serde_yaml::Value::String(plan_key), serde_yaml::Value::Mapping(plan_value), diff --git a/docs/src/host-v1.schema.json b/docs/src/host-v1.schema.json index 53d6580a8..eee565acd 100644 --- a/docs/src/host-v1.schema.json +++ b/docs/src/host-v1.schema.json @@ -143,6 +143,10 @@ "description": "Whether we boot using systemd or grub", "$ref": "#/$defs/Bootloader" }, + "missingVerityAllowed": { + "description": "Whether fs-verity validation is optional", + "type": "boolean" + }, "verity": { "description": "The erofs verity", "type": "string" @@ -151,7 +155,8 @@ "required": [ "verity", "bootType", - "bootloader" + "bootloader", + "missingVerityAllowed" ] }, "BootEntryOstree": { diff --git a/docs/src/man/bootc-container-ukify.8.md b/docs/src/man/bootc-container-ukify.8.md index 83b24a9c0..bad9e10cc 100644 --- a/docs/src/man/bootc-container-ukify.8.md +++ b/docs/src/man/bootc-container-ukify.8.md @@ -31,6 +31,10 @@ Any additional arguments after `--` are passed through to ukify unchanged. Make fs-verity validation optional in case the filesystem doesn't support it +**--write-dumpfile-to**=*WRITE_DUMPFILE_TO* + + Write a dumpfile to this path + # EXAMPLES diff --git a/tmt/plans/integration.fmf b/tmt/plans/integration.fmf index ce7469453..0f0eb6abb 100644 --- a/tmt/plans/integration.fmf +++ b/tmt/plans/integration.fmf @@ -135,6 +135,7 @@ execute: how: fmf test: - /tmt/tests/tests/test-29-soft-reboot-selinux-policy + extra-fixme_skip_if_uki: true /plan-30-install-unified-flag: summary: Test bootc install with experimental unified storage flag @@ -246,4 +247,11 @@ execute: test: - /tmt/tests/tests/test-40-install-karg-delete extra-fixme_skip_if_composefs: true + +/plan-41-composefs-gc-uki: + summary: Test composefs garbage collection for UKI + discover: + how: fmf + test: + - /tmt/tests/tests/test-41-composefs-gc-uki # END GENERATED PLANS diff --git a/tmt/tests/booted/tap.nu b/tmt/tests/booted/tap.nu index 34a5fc085..b8b0b3f7e 100644 --- a/tmt/tests/booted/tap.nu +++ b/tmt/tests/booted/tap.nu @@ -74,3 +74,41 @@ rm -vrf /usr/lib/bootc/bound-images.d ($cmd) " } + +export def make_uki_containerfile [containerfile: string] { + let is_cfs = (is_composefs) + + if not $is_cfs { + return $containerfile + } + + let st = bootc status --json | from json + let is_uki = ($st.status.booted.composefs.bootType | str downcase) == "uki" + + if not $is_uki { + return $containerfile + } + + let allow_missing_verity = $st.status.booted.composefs.missingVerityAllowed + # TODO: Handle sealed UKI + let seal_state = "unsealed" + + let uki_stuff = $" + FROM base as base-final + RUN rm -rf /boot/EFI/Linux/*.efi + + FROM base as sealed-uki + RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp \\ + --mount=type=bind,from=base-final,src=/,target=/run/target \\ + /usr/bin/seal-uki /run/target /out /run/secrets ($allow_missing_verity) ($seal_state) + + FROM base-final + + # 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=sealed-uki,src=/,target=/run/sealed-uki \\ + /usr/bin/finalize-uki /run/sealed-uki/out + " + + return $"($containerfile)\n($uki_stuff)" +} diff --git a/tmt/tests/booted/test-composefs-gc-uki.nu b/tmt/tests/booted/test-composefs-gc-uki.nu new file mode 100644 index 000000000..b82c449bd --- /dev/null +++ b/tmt/tests/booted/test-composefs-gc-uki.nu @@ -0,0 +1,167 @@ +# number: 41 +# tmt: +# summary: Test composefs garbage collection for UKI +# duration: 30m + +use std assert +use tap.nu + +if not (tap is_composefs) { + exit 0 +} + +# bootc status +let st = bootc status --json | from json +let booted = $st.status.booted.image + +let uki_prefix = "bootc_composefs-" + +let is_uki = (($st.status.booted.composefs.bootType | str downcase) == "uki") + +if not $is_uki { + exit 0 +} + +# Create a large file in a new container image, then bootc switch to the image +def first_boot [] { + bootc image copy-to-storage + + mut containerfile = $" + FROM localhost/bootc as base + RUN dd if=/dev/zero of=/usr/share/large-test-file bs=1k count=1337 + RUN echo 'large-file-marker' | dd of=/usr/share/large-test-file conv=notrunc + " + + $containerfile = (tap make_uki_containerfile $containerfile) + + echo $containerfile | podman build -t localhost/bootc-first . -f - + + let current_time = (date now) + + bootc switch --transport containers-storage localhost/bootc-first + + # Find the large file's verity and save it + # nu has its own built in find which sucks, so we use the other one + # TODO: Replace this with some concrete API + # See: https://github.com/composefs/composefs-rs/pull/236 + let file_path = ( + /usr/bin/find /sysroot/composefs/objects -type f -size 1337k -newermt ($current_time | format date "%Y-%m-%d %H:%M:%S") + | xargs grep -lx "large-file-marker" + ) + + echo $file_path | save /var/large-file-marker-objpath + cat /var/large-file-marker-objpath + + echo $st.status.booted.composefs.verity | save /var/boot0-verity + + tmt-reboot +} + +# Create a container image derived from the first boot image +def second_boot [] { + mkdir /var/tmp/efi + mount /dev/disk/by-partlabel/EFI-SYSTEM /var/tmp/efi + + assert equal $booted.image.image "localhost/bootc-first" + assert ($"/var/tmp/efi/EFI/Linux/bootc/($uki_prefix)(cat /var/boot0-verity).efi" | path exists) + + echo $st.status.booted.composefs.verity | save /var/boot1-verity + + let path = cat /var/large-file-marker-objpath + + assert ($path | path exists) + + mut containerfile = echo " + FROM localhost/bootc as base + RUN echo 'second' > /usr/share/second + " + + $containerfile = (tap make_uki_containerfile $containerfile) + + echo $containerfile | podman build -t localhost/bootc-second . -f - + + bootc switch --transport containers-storage localhost/bootc-second + + tmt-reboot +} + +def third_boot [] { + mkdir /var/tmp/efi + mount /dev/disk/by-partlabel/EFI-SYSTEM /var/tmp/efi + + assert equal $booted.image.image "localhost/bootc-second" + assert ($"/var/tmp/efi/EFI/Linux/bootc/($uki_prefix)(cat /var/boot0-verity).efi" | path exists) + assert ($"/var/tmp/efi/EFI/Linux/bootc/($uki_prefix)(cat /var/boot1-verity).efi" | path exists) + + echo $st.status.booted.composefs.verity | save /var/boot2-verity + + # this is not deleted yet + let path = cat /var/large-file-marker-objpath + assert ($path | path exists) + + mut containerfile = echo " + FROM localhost/bootc as base + RUN echo 'third' > /usr/share/third + " + + $containerfile = (tap make_uki_containerfile $containerfile) + + echo $containerfile | podman build -t localhost/bootc-third . -f - + + bootc switch --transport containers-storage localhost/bootc-third + + tmt-reboot +} + + +def fourth_boot [] { + mkdir /var/tmp/efi + mount /dev/disk/by-partlabel/EFI-SYSTEM /var/tmp/efi + + assert equal $booted.image.image "localhost/bootc-third" + assert (not ($"/var/tmp/efi/EFI/Linux/bootc/($uki_prefix)(cat /var/boot0-verity).efi" | path exists)) + assert ($"/var/tmp/efi/EFI/Linux/bootc/($uki_prefix)(cat /var/boot1-verity).efi" | path exists) + assert ($"/var/tmp/efi/EFI/Linux/bootc/($uki_prefix)(cat /var/boot2-verity).efi" | path exists) + + echo $st.status.booted.composefs.verity | save /var/boot3-verity + + mut containerfile = " + FROM localhost/bootc as base + RUN echo 'another file' > /usr/share/another-one + " + + $containerfile = (tap make_uki_containerfile $containerfile) + + echo $containerfile | podman build -t localhost/bootc-final . -f - + + bootc switch --transport containers-storage localhost/bootc-final + tap ok +} + +def fifth_boot [] { + mkdir /var/tmp/efi + mount /dev/disk/by-partlabel/EFI-SYSTEM /var/tmp/efi + + assert equal $booted.image.image "localhost/bootc-final" + + assert (not ($"/var/tmp/efi/EFI/Linux/bootc/($uki_prefix)(cat /var/boot1-verity).efi" | path exists)) + assert ($"/var/tmp/efi/EFI/Linux/bootc/($uki_prefix)(cat /var/boot2-verity).efi" | path exists) + assert ($"/var/tmp/efi/EFI/Linux/bootc/($uki_prefix)(cat /var/boot3-verity).efi" | path exists) + + # We had this in boot1 (second boot) + let path = cat /var/large-file-marker-objpath + assert (not ($path | path exists)) + tap ok +} + +def main [] { + match $env.TMT_REBOOT_COUNT? { + null | "0" => first_boot, + "1" => second_boot, + "2" => third_boot, + "3" => fourth_boot, + "4" => fifth_boot, + $o => { error make { msg: $"Invalid TMT_REBOOT_COUNT ($o)" } }, + } +} + diff --git a/tmt/tests/booted/test-composefs-gc.nu b/tmt/tests/booted/test-composefs-gc.nu index c8f93fea9..c62fdccfc 100644 --- a/tmt/tests/booted/test-composefs-gc.nu +++ b/tmt/tests/booted/test-composefs-gc.nu @@ -6,13 +6,17 @@ use std assert use tap.nu +if not (tap is_composefs) { + exit 0 +} + # bootc status let st = bootc status --json | from json let booted = $st.status.booted.image let dir_prefix = "bootc_composefs-" -if not (tap is_composefs) or ($st.status.booted.composefs.bootType | str downcase) == "uki" { +if ($st.status.booted.composefs.bootType | str downcase) == "uki" { exit 0 } @@ -52,11 +56,9 @@ def second_boot [] { assert equal $booted.image.image "localhost/bootc-derived" let path = cat /var/large-file-marker-objpath - assert ($path | path exists) # Create another image with a different initrd so we can test kernel + initrd cleanup - echo " FROM localhost/bootc @@ -89,13 +91,9 @@ def second_boot [] { tmt-reboot } -# The large file should've been GC'd as we switched to an image derived from the original one def third_boot [] { assert equal $booted.image.image "localhost/bootc-derived-initrd" - let path = cat /var/large-file-marker-objpath - assert (not ($"/sysroot/composefs/objects/($path)" | path exists)) - # Also assert we have two different kernel + initrd pairs let booted_verity = (bootc status --json | from json).status.booted.composefs.verity @@ -158,6 +156,10 @@ def fifth_boot [] { mount /dev/disk/by-partlabel/EFI-SYSTEM /var/tmp/efi } + # The large file should be GC'd in the previous switch + let path = cat /var/large-file-marker-objpath + assert (not ($path | path exists)) + assert equal $booted.image.image "localhost/bootc-final" assert (not ((cat /var/to-be-deleted-kernel | path exists))) diff --git a/tmt/tests/booted/test-download-only-upgrade.nu b/tmt/tests/booted/test-download-only-upgrade.nu index 3f2bd7611..59dcdc3f5 100644 --- a/tmt/tests/booted/test-download-only-upgrade.nu +++ b/tmt/tests/booted/test-download-only-upgrade.nu @@ -46,9 +46,10 @@ def initial_build [] { "v1" | save testing-bootc-upgrade-apply # A simple derived container (v1) that adds a file - "FROM localhost/bootc + let dockerfile = $"FROM localhost/bootc as base COPY testing-bootc-upgrade-apply /usr/share/testing-bootc-upgrade-apply -" | save Dockerfile +" + (tap make_uki_containerfile $dockerfile) | save Dockerfile # Build it podman build -t $imgsrc . @@ -74,9 +75,10 @@ def second_boot [] { # Create test file v2 on host "v2" | save --force testing-bootc-upgrade-apply - "FROM localhost/bootc + let dockerfile = $"FROM localhost/bootc as base COPY testing-bootc-upgrade-apply /usr/share/testing-bootc-upgrade-apply -" | save --force Dockerfile +" + (tap make_uki_containerfile $dockerfile) | save --force Dockerfile podman build -t $imgsrc . # Now upgrade with --download-only (should set deployment to download-only mode) diff --git a/tmt/tests/booted/test-image-pushpull-upgrade.nu b/tmt/tests/booted/test-image-pushpull-upgrade.nu index aa79374e9..457349db6 100644 --- a/tmt/tests/booted/test-image-pushpull-upgrade.nu +++ b/tmt/tests/booted/test-image-pushpull-upgrade.nu @@ -44,10 +44,11 @@ def initial_build [] { mkdir usr/lib/bootc/kargs.d { kargs: $kargsv0 } | to toml | save usr/lib/bootc/kargs.d/05-testkargs.toml # A simple derived container that adds a file, but also injects some kargs - "FROM localhost/bootc + let dockerfile = $"FROM localhost/bootc as base COPY usr/ /usr/ RUN echo test content > /usr/share/blah.txt -" | save Dockerfile +" + (tap make_uki_containerfile $dockerfile) | save Dockerfile # Build it podman build -t localhost/bootc-derived . # Just sanity check it @@ -165,10 +166,11 @@ def second_boot [] { mkdir usr/lib/bootc/kargs.d { kargs: $kargsv1 } | to toml | save usr/lib/bootc/kargs.d/05-testkargs.toml - "FROM localhost/bootc + let dockerfile = $"FROM localhost/bootc as base COPY usr/ /usr/ RUN echo test content2 > /usr/share/blah.txt -" | save Dockerfile +" + (tap make_uki_containerfile $dockerfile) | save Dockerfile # Build it podman build -t localhost/bootc-derived . let booted_digest = $booted.imageDigest diff --git a/tmt/tests/booted/test-image-upgrade-reboot.nu b/tmt/tests/booted/test-image-upgrade-reboot.nu index 4343aa3c7..4fea5fea9 100644 --- a/tmt/tests/booted/test-image-upgrade-reboot.nu +++ b/tmt/tests/booted/test-image-upgrade-reboot.nu @@ -48,9 +48,12 @@ def initial_build [] { bootc image copy-to-storage # A simple derived container that adds a file - "FROM localhost/bootc -RUN touch /usr/share/testing-bootc-upgrade-apply -" | save Dockerfile + ( + tap make_uki_containerfile " + FROM localhost/bootc as base + RUN touch /usr/share/testing-bootc-upgrade-apply + ") | save Dockerfile + # Build it podman build -t $imgsrc . } diff --git a/tmt/tests/booted/test-install-to-filesystem-var-mount.sh b/tmt/tests/booted/test-install-to-filesystem-var-mount.sh index 4499b5be5..5025b0a76 100644 --- a/tmt/tests/booted/test-install-to-filesystem-var-mount.sh +++ b/tmt/tests/booted/test-install-to-filesystem-var-mount.sh @@ -26,9 +26,35 @@ bootc image copy-to-storage # Build a derived image that removes LBIs cat > /tmp/Containerfile.drop-lbis <<'EOF' -FROM localhost/bootc +FROM localhost/bootc as base RUN rm -rf /usr/lib/bootc/bound-images.d/* EOF + +is_composefs=$(bootc status --json | jq '.status.booted.composefs') +boot_type=$(bootc status --json | jq -r '.status.booted.composefs.bootType' | tr '[:upper:]' '[:lower:]') + +if [[ $is_composefs != "null" && $boot_type == "uki" ]]; then + allow_missing_verity=$(bootc status --json | jq -r '.status.booted.composefs.missingVerityAllowed') + seal_state="unsealed" + + cat >> /tmp/Containerfile.drop-lbis <<-EOF + FROM base as base-final + RUN rm -rf /boot/EFI/Linux/*.efi + + FROM base as sealed-uki + RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp \ + --mount=type=bind,from=base-final,src=/,target=/run/target \ + /usr/bin/seal-uki /run/target /out /run/secrets $allow_missing_verity $seal_state + + FROM base-final + + # 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=sealed-uki,src=/,target=/run/sealed-uki \ + /usr/bin/finalize-uki /run/sealed-uki/out +EOF +fi + podman build -t "$TARGET_IMAGE" -f /tmp/Containerfile.drop-lbis # Create a 15GB sparse disk image in /var/tmp (not /tmp which may be tmpfs) @@ -116,13 +142,16 @@ mount | grep /var/mnt/target || true df -h /var/mnt/target /var/mnt/target/boot /var/mnt/target/boot/efi /var/mnt/target/var COMPOSEFS_BACKEND=() - -is_composefs=$(bootc status --json | jq '.status.booted.composefs') +KARGS=("--karg=root=UUID=$ROOT_UUID") if [[ $is_composefs != "null" ]]; then COMPOSEFS_BACKEND+=("--composefs-backend") tune2fs -O verity /dev/BL/var02 tune2fs -O verity /dev/BL/root02 + + if [[ $boot_type == "uki" ]]; then + KARGS=() + fi fi # Run bootc install to-filesystem from within the container image under test @@ -136,7 +165,7 @@ podman run \ bootc install to-filesystem \ --disable-selinux \ "${COMPOSEFS_BACKEND[@]}" \ - --karg=root=UUID="$ROOT_UUID" \ + "${KARGS[@]}" \ --root-mount-spec=UUID="$ROOT_UUID" \ --boot-mount-spec=UUID="$BOOT_UUID" \ /target diff --git a/tmt/tests/booted/test-rollback.nu b/tmt/tests/booted/test-rollback.nu index 0f2e2ee89..0afe377ac 100644 --- a/tmt/tests/booted/test-rollback.nu +++ b/tmt/tests/booted/test-rollback.nu @@ -42,9 +42,10 @@ def initial_switch [] { bootc image copy-to-storage print "Building derived container" - "FROM localhost/bootc + let dockerfile = $"FROM localhost/bootc as base RUN echo 'This is the rollback target image' > /usr/share/bootc-rollback-marker -" | save Dockerfile +" + (tap make_uki_containerfile $dockerfile) | save Dockerfile podman build -t $imgsrc . print $"Built derived image: ($imgsrc)" diff --git a/tmt/tests/booted/test-soft-reboot-selinux-policy.nu b/tmt/tests/booted/test-soft-reboot-selinux-policy.nu index c8645f592..4e2706804 100644 --- a/tmt/tests/booted/test-soft-reboot-selinux-policy.nu +++ b/tmt/tests/booted/test-soft-reboot-selinux-policy.nu @@ -2,8 +2,11 @@ # tmt: # summary: Test soft reboot with SELinux policy changes # duration: 30m +# extra: +# fixme_skip_if_uki: true # # Verify that soft reboot is blocked when SELinux policies differ + use std assert use tap.nu @@ -88,8 +91,8 @@ gpgkey=($gpgkey) # Installing a policy module will change the compiled policy checksum # Following Colin's suggestion and the composefs-rs example # We create a minimal policy module and install it - $" -FROM localhost/bootc + (tap make_uki_containerfile $" +FROM localhost/bootc as base ($repo_copy) # Install tools needed to build and install SELinux policy modules @@ -117,7 +120,7 @@ RUN < /usr/share/testfile-for-soft-reboot.txt -" | save Dockerfile +" + (tap make_uki_containerfile $dockerfile) | save Dockerfile # Build it podman build -t localhost/bootc-derived . @@ -59,10 +60,11 @@ def second_boot [] { #assert equal (systemctl show -P SoftRebootsCount) "1" # A new derived with new kargs which should stop the soft reboot. - "FROM localhost/bootc + let dockerfile = $"FROM localhost/bootc as base RUN echo test content > /usr/share/testfile-for-soft-reboot.txt -RUN echo 'kargs = ["foo1=bar2"]' | tee /usr/lib/bootc/kargs.d/00-foo1bar2.toml > /dev/null -" | save Dockerfile +RUN echo 'kargs = [\"foo1=bar2\"]' | tee /usr/lib/bootc/kargs.d/00-foo1bar2.toml > /dev/null +" + (tap make_uki_containerfile $dockerfile) | save Dockerfile # Build it podman build -t localhost/bootc-derived . diff --git a/tmt/tests/booted/test-upgrade-tag.nu b/tmt/tests/booted/test-upgrade-tag.nu index 18fdb3505..2125762c8 100644 --- a/tmt/tests/booted/test-upgrade-tag.nu +++ b/tmt/tests/booted/test-upgrade-tag.nu @@ -26,9 +26,10 @@ def initial_build [] { bootc image copy-to-storage # Build v1 image - "FROM localhost/bootc + let dockerfile = $"FROM localhost/bootc as base RUN echo v1 content > /usr/share/bootc-tag-test.txt -" | save Dockerfile +" + (tap make_uki_containerfile $dockerfile) | save Dockerfile podman build -t localhost/bootc-tag-test:v1 . # Verify v1 content @@ -39,9 +40,10 @@ RUN echo v1 content > /usr/share/bootc-tag-test.txt bootc switch --transport containers-storage localhost/bootc-tag-test:v1 # Build v2 image (different content) - use --force to overwrite Dockerfile - "FROM localhost/bootc + let dockerfile = $"FROM localhost/bootc as base RUN echo v2 content > /usr/share/bootc-tag-test.txt -" | save --force Dockerfile +" + (tap make_uki_containerfile $dockerfile) | save --force Dockerfile podman build -t localhost/bootc-tag-test:v2 . # Verify v2 content diff --git a/tmt/tests/tests.fmf b/tmt/tests/tests.fmf index 0d081d5ef..53476f784 100644 --- a/tmt/tests/tests.fmf +++ b/tmt/tests/tests.fmf @@ -153,3 +153,8 @@ check: summary: Test bootc install --karg-delete duration: 30m test: nu booted/test-install-karg-delete.nu + +/test-41-composefs-gc-uki: + summary: Test composefs garbage collection for UKI + duration: 30m + test: nu booted/test-composefs-gc-uki.nu