Skip to content
Merged
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
80 changes: 40 additions & 40 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -161,22 +161,37 @@ 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"]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as an aside we should probably have btrfs in this list too

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, but that will add a bunch more combinations to the matrix

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
boot_type: uki # we still want to test ext4 unsealed bls
- 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

Expand All @@ -194,35 +209,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
Expand All @@ -241,14 +234,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
Expand All @@ -259,7 +252,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
Expand Down
9 changes: 6 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand All @@ -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 <<EORUN
set -xeuo pipefail
if test "${variant}" = "composefs-sealeduki-sdboot"; then
if test "${boot_type}" = "uki"; then
/run/packaging/finalize-uki /run/sealed-uki/out
fi
EORUN
Expand Down
67 changes: 37 additions & 30 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,15 @@ base_img := "localhost/bootc"
# Synthetic upgrade image for testing
upgrade_img := base_img + "-upgrade"

# Build variant: ostree (default) or composefs-sealeduki-sdboot (sealed UKI)
# Build variant: ostree (default) or composefs
variant := env("BOOTC_variant", "ostree")
bootloader := env("BOOTC_bootloader", "grub")
# Only used for composefs tests
filesystem := env("BOOTC_filesystem", "ext4")
# Only used for composefs tests
boot_type := env("BOOTC_boot_type", "bls")
# Only used for composefs tests
seal_state := env("BOOTC_seal_state", "unsealed")
# Base container image to build from
base := env("BOOTC_base", "quay.io/centos-bootc/centos-bootc:stream10")
# Buildroot base image
Expand All @@ -45,6 +49,8 @@ base_buildargs := generic_buildargs + " " + _extra_src_args \
+ " --build-arg=base=" + base \
+ " --build-arg=variant=" + variant \
+ " --build-arg=bootloader=" + bootloader \
+ " --build-arg=boot_type=" + boot_type \
+ " --build-arg=seal_state=" + seal_state \
+ " --build-arg=filesystem=" + filesystem # required for bootc container ukify to allow missing fsverity
buildargs := base_buildargs \
+ " --cap-add=all --security-opt=label=type:container_runtime_t --device /dev/fuse" \
Expand Down Expand Up @@ -75,15 +81,15 @@ list-variants:
Standard bootc image using ostree backend.
This is the traditional, production-ready configuration.

composefs-sealeduki-sdboot
Sealed composefs image with:
- Unified Kernel Image (UKI) containing kernel + initramfs + cmdline
- Secure Boot signing (using keys in target/test-secureboot/)
- systemd-boot bootloader
- composefs digest embedded in kernel cmdline for verified boot
composefs (bootloader, filesystem, boot_type, seal_state)
Build Composefs image with:
- The specified bootloader (grub/systemd)
- The specified filesystem (ext4,btrfs,xfs)
- The specified boot type (BLS/UKI)
- The specified seal state (sealed/unsealed) determining whether we sign the UKI and
use secure boot or not

Use `just build-sealed` as a shortcut, or:
just variant=composefs-sealeduki-sdboot build
Use `just build-sealed` as shortcut to build a sealed composefs image with systemd-boot as the bootloader

Current Configuration
=====================
Expand All @@ -96,7 +102,7 @@ list-variants:
# Build a sealed composefs image (alias for variant=composefs-sealeduki-sdboot)
[group('core')]
build-sealed:
@just --justfile {{justfile()}} variant=composefs-sealeduki-sdboot build
@just --justfile {{justfile()}} variant=composefs bootloader=systemd boot_type=uki seal_state=sealed build

# Run tmt integration tests in VMs (e.g. `just test-tmt readonly`)
[group('core')]
Expand All @@ -108,30 +114,31 @@ 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

# Build and test sealed composefs images
[group('core')]
test-composefs-sealeduki-sdboot filesystem:
just variant=composefs-sealeduki-sdboot filesystem={{filesystem}} test-tmt readonly local-upgrade-reboot
test-composefs bootloader filesystem boot_type seal_state:
@if [ "{{seal_state}}" = "sealed" ] && [ "{{filesystem}}" = "xfs" ]; then \
echo "Invalid combination: sealed requires filesystem that supports fs-verity (ext4, btrfs)"; \
exit 1; \
fi

[group('core')]
test-composefs bootloader filesystem:
just variant=composefs bootloader={{bootloader}} filesystem={{filesystem}} \
test-tmt --composefs-backend \
--bootloader {{bootloader}} \
--filesystem {{filesystem}} \
integration

# Build and test composefs images booted using Type1 boot entries and systemd-boot as the bootloader
[group('core')]
test-composefs-sdboot filesystem:
just test-composefs systemd {{filesystem}}
@if [ "{{seal_state}}" = "sealed" ] && [ "{{boot_type}}" != "uki" ]; then \
echo "Invalid combination: sealed requires boot_type=uki"; \
exit 1; \
fi

# Build and test composefs images booted using Type1 boot entries and grub as the bootloader
[group('core')]
test-composefs-grub filesystem:
just test-composefs grub {{filesystem}}
just variant=composefs \
bootloader={{bootloader}} \
filesystem={{filesystem}} \
boot_type={{boot_type}} \
seal_state={{seal_state}} \
test-tmt --composefs-backend \
--bootloader={{bootloader}} \
--filesystem={{filesystem}} \
--seal-state={{seal_state}} \
--boot-type={{boot_type}} \
$(if [ "{{boot_type}}" = "uki" ]; then echo "readonly"; else echo "integration"; fi)

# Run cargo fmt and clippy checks in container
[group('core')]
Expand Down
17 changes: 13 additions & 4 deletions contrib/packaging/seal-uki
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ secrets=$1
shift
allow_missing_verity=$1
shift
seal_state=$1
shift

if [[ $seal_state == "sealed" && $allow_missing_verity == "true" ]]; then
echo "Cannot have missing verity with 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')
Expand All @@ -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}")
Expand Down
21 changes: 13 additions & 8 deletions crates/tests-integration/src/container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,17 +50,20 @@ 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) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nonblocking but for followup: we could also validate the sealed/unsealed state here right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, we can

(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!(
version.chars().any(|c| c.is_ascii_digit()),
"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!(
Expand All @@ -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:?}"),
}
}

Expand Down Expand Up @@ -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() {
Expand Down
Loading