Skip to content
Draft
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
101 changes: 82 additions & 19 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,6 @@ WORKDIR /src
# First we download all of our Rust dependencies
RUN --mount=type=cache,target=/src/target --mount=type=cache,target=/var/roothome cargo fetch

FROM buildroot as sdboot-content
# Writes to /out
RUN /src/contrib/packaging/configure-systemdboot download

# We always do a "from scratch" build
# https://docs.fedoraproject.org/en-US/bootc/building-from-scratch/
# because this fixes https://github.com/containers/composefs-rs/issues/132
Expand Down Expand Up @@ -65,6 +61,11 @@ ENV container=oci
STOPSIGNAL SIGRTMIN+3
CMD ["/sbin/init"]

# This layer contains things which aren't in the default image and may
# be used for sealing images in particular.
FROM base as tools
RUN --mount=type=bind,from=packaging,target=/run/packaging /run/packaging/initialize-sealing-tools

# -------------
# external dependency cutoff point:
# NOTE: Every RUN instruction past this point should use `--network=none`; we want to ensure
Expand All @@ -81,14 +82,35 @@ ENV SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH}
# Build RPM directly from source, using cached target directory
RUN --network=none --mount=type=cache,target=/src/target --mount=type=cache,target=/var/roothome RPM_VERSION="${pkgversion}" /src/contrib/packaging/build-rpm

FROM buildroot as sdboot-signed
# This image signs systemd-boot using our key, and writes the resulting binary into /out
FROM tools as sdboot-signed
# The secureboot key and cert are passed via Justfile
# We write the signed binary into /out
# Note: /out already contains systemd-boot-unsigned RPM from initialize-sealing-tools
RUN --network=none \
--mount=type=bind,from=sdboot-content,target=/run/sdboot-package \
--mount=type=secret,id=secureboot_key \
--mount=type=secret,id=secureboot_cert \
/src/contrib/packaging/configure-systemdboot sign
--mount=type=secret,id=secureboot_cert <<EORUN
set -xeuo pipefail

# Extract the unsigned systemd-boot binary from the downloaded RPM
cd /tmp
rpm2cpio /out/*.rpm | cpio -idmv
# Find the extracted unsigned binary
sdboot_unsigned=$(ls ./usr/lib/systemd/boot/efi/systemd-boot*.efi)
sdboot_bn=$(basename ${sdboot_unsigned})
Comment on lines +99 to +100
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The use of ls with a glob to find the systemd-boot EFI binary can be fragile. If the glob matches more than one file, the sdboot_unsigned variable will contain a multi-line string, which could cause sbsign to behave unexpectedly. It's safer to ensure exactly one file is found.

sdboot_unsigned_files=(./usr/lib/systemd/boot/efi/systemd-boot*.efi)
if [ ${#sdboot_unsigned_files[@]} -ne 1 ]; then
    echo "Error: Expected 1 systemd-boot EFI file, but found ${#sdboot_unsigned_files[@]}" >&2
    ls -l ./usr/lib/systemd/boot/efi/
    exit 1
fi
sdboot_unsigned="${sdboot_unsigned_files[0]}"
sdboot_bn=$(basename "${sdboot_unsigned}")

# Sign with sbsign using db certificate and key
sbsign --key /run/secrets/secureboot_key \
--cert /run/secrets/secureboot_cert \
--output /out/${sdboot_bn} \
${sdboot_unsigned}
ls -al /out/${sdboot_bn}
EORUN

# ----
# Unit and integration tests
# The section here (up until the last `FROM` line which acts as the default target)
# is non-default images for unit and source code validation.
# ----

# This "build" includes our unit tests
FROM build as units
Expand All @@ -101,20 +123,61 @@ RUN --network=none --mount=type=cache,target=/src/target --mount=type=cache,targ
FROM buildroot as validate
RUN --network=none --mount=type=cache,target=/src/target --mount=type=cache,target=/var/roothome make validate

# Common base for final images: configures variant, rootfs, and injects extra content
FROM base as final-common
# ----
# Stages for the final image
# ----

# Perform all filesystem transformations except generating the sealed UKI (if configured)
FROM base as base-penultimate
ARG variant
# Switch to a signed systemd-boot, if configured
RUN --network=none --mount=type=bind,from=packaging,target=/run/packaging \
--mount=type=bind,from=sdboot-content,target=/run/sdboot-content \
--mount=type=bind,from=sdboot-signed,target=/run/sdboot-signed \
/run/packaging/configure-variant "${variant}"
--mount=type=bind,from=sdboot-signed,target=/run/sdboot-signed <<EORUN
set -xeuo pipefail
if test "${variant}" = "composefs-sealeduki-sdboot"; then
/run/packaging/switch-to-sdboot /run/sdboot-signed
fi
EORUN
# Configure the rootfs
ARG rootfs=""
RUN --network=none --mount=type=bind,from=packaging,target=/run/packaging /run/packaging/configure-rootfs "${variant}" "${rootfs}"
COPY --from=packaging /usr-extras/ /usr/

# Final target: installs pre-built packages from /run/packages volume mount.
# Use with: podman build --target=final -v path/to/packages:/run/packages:ro
FROM final-common as final
RUN --network=none --mount=type=bind,from=packaging,target=/run/packaging \
/run/packaging/configure-rootfs "${variant}" "${rootfs}"
# Override with our built package
RUN --network=none \
--mount=type=bind,from=packaging,target=/run/packaging \
/run/packaging/install-rpm-and-setup /run/packages
# Inject some other configuration
COPY --from=packaging /usr-extras/ /usr/

# Generate the sealed UKI in a separate stage
# This computes the composefs digest from base-penultimate and creates a signed UKI
# We need our newly-built bootc for the compute-composefs-digest command
FROM tools as sealed-uki
ARG variant
# Install our bootc package (only needed for the compute-composefs-digest command)
RUN --network=none rpm -Uvh --oldpackage /run/packages/bootc-*.rpm
RUN --network=none \
--mount=type=secret,id=secureboot_key \
--mount=type=secret,id=secureboot_cert \
--mount=type=bind,from=packaging,target=/run/packaging \
--mount=type=bind,from=base-penultimate,target=/run/target <<EORUN
set -xeuo pipefail
if test "${variant}" = "composefs-sealeduki-sdboot"; then
/run/packaging/seal-uki /run/target /out /run/secrets
fi
EORUN

# And now the final image
FROM base-penultimate
ARG variant
# Copy the sealed UKI and finalize the image (remove raw kernel, create symlinks)
RUN --network=none \
--mount=type=bind,from=packaging,target=/run/packaging \
--mount=type=bind,from=sealed-uki,target=/run/sealed-uki <<EORUN
set -xeuo pipefail
if test "${variant}" = "composefs-sealeduki-sdboot"; then
/run/packaging/finalize-uki /run/sealed-uki/out
fi
EORUN
# And finally, test our linting
RUN --network=none bootc container lint --fatal-warnings
67 changes: 0 additions & 67 deletions Dockerfile.cfsuki

This file was deleted.

14 changes: 4 additions & 10 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,6 @@ base_buildargs := generic_buildargs + " --build-arg=base=" + base + " --build-ar
buildargs := base_buildargs \
+ " --cap-add=all --security-opt=label=type:container_runtime_t --device /dev/fuse" \
+ " --secret=id=secureboot_key,src=target/test-secureboot/db.key --secret=id=secureboot_cert,src=target/test-secureboot/db.crt"
# Args for build-sealed (no base arg, it sets that itself)
sealed_buildargs := "--build-arg=variant=" + variant + " --secret=id=secureboot_key,src=target/test-secureboot/db.key --secret=id=secureboot_cert,src=target/test-secureboot/db.crt"

# The default target: build the container image from current sources.
# Note commonly you might want to override the base image via e.g.
# `just build --build-arg=base=quay.io/fedora/fedora-bootc:42`
Expand All @@ -62,8 +59,7 @@ build: package _keygen && _pull-lbi-images
# Resolve to absolute path for podman volume mount
# Use :z for SELinux relabeling
pkg_path=$(realpath target/packages)
podman build --target=final -v "${pkg_path}":/run/packages:ro,z -t {{base_img}}-bin {{buildargs}} .
./hack/build-sealed {{variant}} {{base_img}}-bin {{base_img}} {{sealed_buildargs}}
podman build -v "${pkg_path}":/run/packages:ro,z -t {{base_img}} {{buildargs}} .

# Pull images used by hack/lbi
_pull-lbi-images:
Expand Down Expand Up @@ -156,8 +152,7 @@ test-tmt *ARGS: build

# Generate a local synthetic upgrade
_build-upgrade-image:
cat tmt/tests/Dockerfile.upgrade | podman build -t {{upgrade_img}}-bin --from={{base_img}}-bin -
./hack/build-sealed {{variant}} {{upgrade_img}}-bin {{upgrade_img}} {{sealed_buildargs}}
cat tmt/tests/Dockerfile.upgrade | podman build -t {{upgrade_img}} --from={{base_img}} -

# Assume the localhost/bootc image is up to date, and just run tests.
# Useful for iterating on tests quickly.
Expand All @@ -170,10 +165,9 @@ build-testimage-coreos PATH: _keygen
#!/bin/bash
set -xeuo pipefail
pkg_path=$(realpath "{{PATH}}")
podman build --target=final -v "${pkg_path}":/run/packages:ro,z \
podman build -v "${pkg_path}":/run/packages:ro,z \
--build-arg SKIP_CONFIGS=1 \
-t {{base_img}}-coreos-bin {{buildargs}} .
./hack/build-sealed {{variant}} {{base_img}}-coreos-bin {{base_img}}-coreos {{sealed_buildargs}}
-t {{base_img}}-coreos {{buildargs}} .

# Run test bootc install on FCOS
# BOOTC_target is `bootc-coreos`, it will be used for bootc install.
Expand Down
29 changes: 0 additions & 29 deletions contrib/packaging/configure-systemdboot

This file was deleted.

13 changes: 0 additions & 13 deletions contrib/packaging/configure-variant
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,7 @@ fi
# Handle variant-specific configuration
case "${VARIANT}" in
*-sdboot)
# Install systemd-boot and remove bootupd;
# We downloaded this in an earlier phase
sdboot="usr/lib/systemd/boot/efi/systemd-bootx64.efi"
sdboot_bn=$(basename ${sdboot})
rpm -Uvh /run/sdboot-content/out/*.rpm
# And override with our signed binary
install -m 0644 /run/sdboot-signed/out/${sdboot_bn} /${sdboot}

# Uninstall bootupd
rpm -e bootupd
rm -rf /usr/lib/bootupd/updates
# Clean up package manager caches
dnf clean all
rm -rf /var/cache /var/lib/{dnf,rhsm} /var/log/*
;;
# Future variants can be added here
# For Debian support, this could check package manager type and use apt instead
Expand Down
2 changes: 0 additions & 2 deletions contrib/packaging/fedora-extra.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,3 @@ git-core
jq
# We now always build a package in the container build
rpm-build
# Used for signing
sbsigntools
49 changes: 49 additions & 0 deletions contrib/packaging/finalize-uki
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/bin/bash
# Finalize UKI installation: copy to /boot, remove raw kernel/initramfs, create symlinks
#
# For sealed UKI images, the kernel and initramfs are embedded inside the signed
# UKI PE binary. We remove the standalone vmlinuz/initramfs.img to:
# - Avoid duplication (they're inside the UKI)
# - Ensure tools use the UKI path
# - Make it clear this is a UKI-only boot configuration
#
# NOTE: The old Dockerfile.cfsuki had a bug where the final-final stage started
# FROM base instead of FROM final, then only copied /boot. This meant the
# vmlinuz/initramfs removal in the final stage was lost. Running this script
# in the actual final image stage fixes that issue.
#
# IMPORTANT: bcvk needs to be updated to find .efi files inside kernel version
# subdirectories (e.g., /usr/lib/modules/<kver>/<kver>.efi) rather than at the
# top level of /usr/lib/modules/. See https://github.com/bootc-dev/bcvk/pull/144
set -xeuo pipefail

# Path to directory containing the generated UKI
uki_src=$1
shift

# Find the kernel version from the current system
kver=$(cd /usr/lib/modules && echo *)
if [ -z "$kver" ] || [ "$kver" = "*" ]; then
echo "Error: No kernel found" >&2
exit 1
fi
Comment on lines +25 to +29
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The command kver=$(cd /usr/lib/modules && echo *) is not robust if multiple kernel version directories exist under /usr/lib/modules. In such a case, kver would contain a space-separated list of versions, which would break subsequent commands. It's safer to ensure exactly one kernel directory is found and fail otherwise.

Suggested change
kver=$(cd /usr/lib/modules && echo *)
if [ -z "$kver" ] || [ "$kver" = "*" ]; then
echo "Error: No kernel found" >&2
exit 1
fi
kver_paths=(/usr/lib/modules/*)
if [ "${#kver_paths[@]}" -ne 1 ]; then
echo "Error: Expected 1 kernel version directory, but found ${#kver_paths[@]}" >&2
ls -l /usr/lib/modules/
exit 1
fi
kver=$(basename "${kver_paths[0]}")


# Create the EFI directory structure
mkdir -p /boot/EFI/Linux

# The UKI in /boot is outside the composefs-verified tree, which is fine
# because the UKI itself is signed and verified by Secure Boot
target=/boot/EFI/Linux/${kver}.efi
cp "${uki_src}/${kver}.efi" "${target}"

# Remove the raw kernel and initramfs since we're using a UKI now.
# NOTE: We intentionally keep these for now until bcvk is updated to extract
# kernel/initramfs from UKIs in subdirectories. Once bcvk PR #144 is fixed
# to look for .efi files in /usr/lib/modules/<kver>/, we can uncomment this.
# rm -v "/usr/lib/modules/${kver}/vmlinuz" "/usr/lib/modules/${kver}/initramfs.img"

# NOTE: We used to create a symlink from /usr/lib/modules/${kver}/${kver}.efi to the UKI
# for tooling compatibility. However, composefs-boot's find_uki_components() doesn't
# handle symlinks correctly and fails with "is not a regular file". The UKI is already
# found in /boot/EFI/Linux/, so the symlink is not needed.
# See: https://github.com/containers/composefs-rs/issues/XXX
15 changes: 15 additions & 0 deletions contrib/packaging/initialize-sealing-tools
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/bash
set -xeuo pipefail
. /usr/lib/os-release
case "${ID}${ID_LIKE:-}" in
*centos*|*rhel*)
# Enable EPEL for sbsigntools
dnf -y install epel-release
;;
esac
dnf -y install systemd-ukify sbsigntools
# And in the sealing case, we're going to inject and sign systemd-boot
# into the target image.
mkdir -p /out
cd /out
dnf -y download systemd-boot-unsigned
14 changes: 9 additions & 5 deletions contrib/packaging/install-buildroot
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,18 @@
set -xeuo pipefail
cd $(dirname $0)
. /usr/lib/os-release
case $ID in
centos|rhel)
case "${ID}${ID_LIKE:-}" in
*centos*|*rhel*)
# We'll use crb at build time
dnf config-manager --set-enabled crb
# Enable EPEL for sbsigntools
dnf -y install epel-release
;;
fedora) dnf -y install dnf-utils 'dnf5-command(builddep)';;
esac
# Deal with dnf4 vs dnf5
if test -x /usr/bin/dnf5; then
dnf -y install 'dnf5-command(builddep)'
else
dnf -y install 'dnf-command(builddep)'
fi
# Handle version skew, xref https://gitlab.com/redhat/centos-stream/containers/bootc/-/issues/1174
dnf -y distro-sync ostree{,-libs} systemd
# Install base build requirements
Expand Down
Loading
Loading