Skip to content

Commit d46fca8

Browse files
committed
build-sys: Rework sealing to be one build step
Now that we're doing a "from scratch" build we don't have the mtime issue, and so we can change our build system to do everything in a single step. Assisted-by: OpenCode (Opus 4.5) Signed-off-by: Colin Walters <[email protected]>
1 parent 49d753f commit d46fca8

File tree

13 files changed

+242
-228
lines changed

13 files changed

+242
-228
lines changed

Dockerfile

Lines changed: 82 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,6 @@ WORKDIR /src
3232
# First we download all of our Rust dependencies
3333
RUN --mount=type=cache,target=/src/target --mount=type=cache,target=/var/roothome cargo fetch
3434

35-
FROM buildroot as sdboot-content
36-
# Writes to /out
37-
RUN /src/contrib/packaging/configure-systemdboot download
38-
3935
# We always do a "from scratch" build
4036
# https://docs.fedoraproject.org/en-US/bootc/building-from-scratch/
4137
# because this fixes https://github.com/containers/composefs-rs/issues/132
@@ -65,6 +61,11 @@ ENV container=oci
6561
STOPSIGNAL SIGRTMIN+3
6662
CMD ["/sbin/init"]
6763

64+
# This layer contains things which aren't in the default image and may
65+
# be used for sealing images in particular.
66+
FROM base as tools
67+
RUN --mount=type=bind,from=packaging,target=/run/packaging /run/packaging/initialize-sealing-tools
68+
6869
# -------------
6970
# external dependency cutoff point:
7071
# NOTE: Every RUN instruction past this point should use `--network=none`; we want to ensure
@@ -81,14 +82,35 @@ ENV SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH}
8182
# Build RPM directly from source, using cached target directory
8283
RUN --network=none --mount=type=cache,target=/src/target --mount=type=cache,target=/var/roothome RPM_VERSION="${pkgversion}" /src/contrib/packaging/build-rpm
8384

84-
FROM buildroot as sdboot-signed
85+
# This image signs systemd-boot using our key, and writes the resulting binary into /out
86+
FROM tools as sdboot-signed
8587
# The secureboot key and cert are passed via Justfile
8688
# We write the signed binary into /out
89+
# Note: /out already contains systemd-boot-unsigned RPM from initialize-sealing-tools
8790
RUN --network=none \
88-
--mount=type=bind,from=sdboot-content,target=/run/sdboot-package \
8991
--mount=type=secret,id=secureboot_key \
90-
--mount=type=secret,id=secureboot_cert \
91-
/src/contrib/packaging/configure-systemdboot sign
92+
--mount=type=secret,id=secureboot_cert <<EORUN
93+
set -xeuo pipefail
94+
95+
# Extract the unsigned systemd-boot binary from the downloaded RPM
96+
cd /tmp
97+
rpm2cpio /out/*.rpm | cpio -idmv
98+
# Find the extracted unsigned binary
99+
sdboot_unsigned=$(ls ./usr/lib/systemd/boot/efi/systemd-boot*.efi)
100+
sdboot_bn=$(basename ${sdboot_unsigned})
101+
# Sign with sbsign using db certificate and key
102+
sbsign --key /run/secrets/secureboot_key \
103+
--cert /run/secrets/secureboot_cert \
104+
--output /out/${sdboot_bn} \
105+
${sdboot_unsigned}
106+
ls -al /out/${sdboot_bn}
107+
EORUN
108+
109+
# ----
110+
# Unit and integration tests
111+
# The section here (up until the last `FROM` line which acts as the default target)
112+
# is non-default images for unit and source code validation.
113+
# ----
92114

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

104-
# Common base for final images: configures variant, rootfs, and injects extra content
105-
FROM base as final-common
126+
# ----
127+
# Stages for the final image
128+
# ----
129+
130+
# Perform all filesystem transformations except generating the sealed UKI (if configured)
131+
FROM base as base-penultimate
106132
ARG variant
133+
# Switch to a signed systemd-boot, if configured
107134
RUN --network=none --mount=type=bind,from=packaging,target=/run/packaging \
108-
--mount=type=bind,from=sdboot-content,target=/run/sdboot-content \
109-
--mount=type=bind,from=sdboot-signed,target=/run/sdboot-signed \
110-
/run/packaging/configure-variant "${variant}"
135+
--mount=type=bind,from=sdboot-signed,target=/run/sdboot-signed <<EORUN
136+
set -xeuo pipefail
137+
if test "${variant}" = "composefs-sealeduki-sdboot"; then
138+
/run/packaging/switch-to-sdboot /run/sdboot-signed
139+
fi
140+
EORUN
141+
# Configure the rootfs
111142
ARG rootfs=""
112-
RUN --network=none --mount=type=bind,from=packaging,target=/run/packaging /run/packaging/configure-rootfs "${variant}" "${rootfs}"
113-
COPY --from=packaging /usr-extras/ /usr/
114-
115-
# Final target: installs pre-built packages from /run/packages volume mount.
116-
# Use with: podman build --target=final -v path/to/packages:/run/packages:ro
117-
FROM final-common as final
118143
RUN --network=none --mount=type=bind,from=packaging,target=/run/packaging \
144+
/run/packaging/configure-rootfs "${variant}" "${rootfs}"
145+
# Override with our built package
146+
RUN --network=none \
147+
--mount=type=bind,from=packaging,target=/run/packaging \
119148
/run/packaging/install-rpm-and-setup /run/packages
149+
# Inject some other configuration
150+
COPY --from=packaging /usr-extras/ /usr/
151+
152+
# Generate the sealed UKI in a separate stage
153+
# This computes the composefs digest from base-penultimate and creates a signed UKI
154+
# We need our newly-built bootc for the compute-composefs-digest command
155+
FROM tools as sealed-uki
156+
ARG variant
157+
# Install our bootc package (only needed for the compute-composefs-digest command)
158+
RUN --network=none rpm -Uvh --oldpackage /run/packages/bootc-*.rpm
159+
RUN --network=none \
160+
--mount=type=secret,id=secureboot_key \
161+
--mount=type=secret,id=secureboot_cert \
162+
--mount=type=bind,from=packaging,target=/run/packaging \
163+
--mount=type=bind,from=base-penultimate,target=/run/target <<EORUN
164+
set -xeuo pipefail
165+
if test "${variant}" = "composefs-sealeduki-sdboot"; then
166+
/run/packaging/seal-uki /run/target /out /run/secrets
167+
fi
168+
EORUN
169+
170+
# And now the final image
171+
FROM base-penultimate
172+
ARG variant
173+
# Copy the sealed UKI and finalize the image (remove raw kernel, create symlinks)
174+
RUN --network=none \
175+
--mount=type=bind,from=packaging,target=/run/packaging \
176+
--mount=type=bind,from=sealed-uki,target=/run/sealed-uki <<EORUN
177+
set -xeuo pipefail
178+
if test "${variant}" = "composefs-sealeduki-sdboot"; then
179+
/run/packaging/finalize-uki /run/sealed-uki/out
180+
fi
181+
EORUN
182+
# And finally, test our linting
120183
RUN --network=none bootc container lint --fatal-warnings

Dockerfile.cfsuki

Lines changed: 0 additions & 67 deletions
This file was deleted.

Justfile

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,6 @@ base_buildargs := generic_buildargs + " --build-arg=base=" + base + " --build-ar
4646
buildargs := base_buildargs \
4747
+ " --cap-add=all --security-opt=label=type:container_runtime_t --device /dev/fuse" \
4848
+ " --secret=id=secureboot_key,src=target/test-secureboot/db.key --secret=id=secureboot_cert,src=target/test-secureboot/db.crt"
49-
# Args for build-sealed (no base arg, it sets that itself)
50-
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"
51-
5249
# The default target: build the container image from current sources.
5350
# Note commonly you might want to override the base image via e.g.
5451
# `just build --build-arg=base=quay.io/fedora/fedora-bootc:42`
@@ -62,8 +59,7 @@ build: package _keygen && _pull-lbi-images
6259
# Resolve to absolute path for podman volume mount
6360
# Use :z for SELinux relabeling
6461
pkg_path=$(realpath target/packages)
65-
podman build --target=final -v "${pkg_path}":/run/packages:ro,z -t {{base_img}}-bin {{buildargs}} .
66-
./hack/build-sealed {{variant}} {{base_img}}-bin {{base_img}} {{sealed_buildargs}}
62+
podman build -v "${pkg_path}":/run/packages:ro,z -t {{base_img}} {{buildargs}} .
6763

6864
# Pull images used by hack/lbi
6965
_pull-lbi-images:
@@ -156,8 +152,7 @@ test-tmt *ARGS: build
156152

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

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

178172
# Run test bootc install on FCOS
179173
# BOOTC_target is `bootc-coreos`, it will be used for bootc install.

contrib/packaging/configure-systemdboot

Lines changed: 0 additions & 29 deletions
This file was deleted.

contrib/packaging/configure-variant

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,7 @@ fi
1414
# Handle variant-specific configuration
1515
case "${VARIANT}" in
1616
*-sdboot)
17-
# Install systemd-boot and remove bootupd;
18-
# We downloaded this in an earlier phase
19-
sdboot="usr/lib/systemd/boot/efi/systemd-bootx64.efi"
20-
sdboot_bn=$(basename ${sdboot})
21-
rpm -Uvh /run/sdboot-content/out/*.rpm
22-
# And override with our signed binary
23-
install -m 0644 /run/sdboot-signed/out/${sdboot_bn} /${sdboot}
2417

25-
# Uninstall bootupd
26-
rpm -e bootupd
27-
rm -rf /usr/lib/bootupd/updates
28-
# Clean up package manager caches
29-
dnf clean all
30-
rm -rf /var/cache /var/lib/{dnf,rhsm} /var/log/*
3118
;;
3219
# Future variants can be added here
3320
# For Debian support, this could check package manager type and use apt instead

contrib/packaging/fedora-extra.txt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,3 @@ git-core
77
jq
88
# We now always build a package in the container build
99
rpm-build
10-
# Used for signing
11-
sbsigntools

contrib/packaging/finalize-uki

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#!/bin/bash
2+
# Finalize UKI installation: copy to /boot, remove raw kernel/initramfs, create symlinks
3+
#
4+
# For sealed UKI images, the kernel and initramfs are embedded inside the signed
5+
# UKI PE binary. We remove the standalone vmlinuz/initramfs.img to:
6+
# - Avoid duplication (they're inside the UKI)
7+
# - Ensure tools use the UKI path
8+
# - Make it clear this is a UKI-only boot configuration
9+
#
10+
# NOTE: The old Dockerfile.cfsuki had a bug where the final-final stage started
11+
# FROM base instead of FROM final, then only copied /boot. This meant the
12+
# vmlinuz/initramfs removal in the final stage was lost. Running this script
13+
# in the actual final image stage fixes that issue.
14+
#
15+
# IMPORTANT: bcvk needs to be updated to find .efi files inside kernel version
16+
# subdirectories (e.g., /usr/lib/modules/<kver>/<kver>.efi) rather than at the
17+
# top level of /usr/lib/modules/. See https://github.com/bootc-dev/bcvk/pull/144
18+
set -xeuo pipefail
19+
20+
# Path to directory containing the generated UKI
21+
uki_src=$1
22+
shift
23+
24+
# Find the kernel version from the current system
25+
kver=$(cd /usr/lib/modules && echo *)
26+
if [ -z "$kver" ] || [ "$kver" = "*" ]; then
27+
echo "Error: No kernel found" >&2
28+
exit 1
29+
fi
30+
31+
# Create the EFI directory structure
32+
mkdir -p /boot/EFI/Linux
33+
34+
# The UKI in /boot is outside the composefs-verified tree, which is fine
35+
# because the UKI itself is signed and verified by Secure Boot
36+
target=/boot/EFI/Linux/${kver}.efi
37+
cp "${uki_src}/${kver}.efi" "${target}"
38+
39+
# Remove the raw kernel and initramfs since we're using a UKI now.
40+
# NOTE: We intentionally keep these for now until bcvk is updated to extract
41+
# kernel/initramfs from UKIs in subdirectories. Once bcvk PR #144 is fixed
42+
# to look for .efi files in /usr/lib/modules/<kver>/, we can uncomment this.
43+
# rm -v "/usr/lib/modules/${kver}/vmlinuz" "/usr/lib/modules/${kver}/initramfs.img"
44+
45+
# NOTE: We used to create a symlink from /usr/lib/modules/${kver}/${kver}.efi to the UKI
46+
# for tooling compatibility. However, composefs-boot's find_uki_components() doesn't
47+
# handle symlinks correctly and fails with "is not a regular file". The UKI is already
48+
# found in /boot/EFI/Linux/, so the symlink is not needed.
49+
# See: https://github.com/containers/composefs-rs/issues/XXX
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/bin/bash
2+
set -xeuo pipefail
3+
. /usr/lib/os-release
4+
case "${ID}${ID_LIKE:-}" in
5+
*centos*|*rhel*)
6+
# Enable EPEL for sbsigntools
7+
dnf -y install epel-release
8+
;;
9+
esac
10+
dnf -y install systemd-ukify sbsigntools
11+
# And in the sealing case, we're going to inject and sign systemd-boot
12+
# into the target image.
13+
mkdir -p /out
14+
cd /out
15+
dnf -y download systemd-boot-unsigned

contrib/packaging/install-buildroot

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,18 @@
33
set -xeuo pipefail
44
cd $(dirname $0)
55
. /usr/lib/os-release
6-
case $ID in
7-
centos|rhel)
6+
case "${ID}${ID_LIKE:-}" in
7+
*centos*|*rhel*)
8+
# We'll use crb at build time
89
dnf config-manager --set-enabled crb
9-
# Enable EPEL for sbsigntools
10-
dnf -y install epel-release
1110
;;
12-
fedora) dnf -y install dnf-utils 'dnf5-command(builddep)';;
1311
esac
12+
# Deal with dnf4 vs dnf5
13+
if test -x /usr/bin/dnf5; then
14+
dnf -y install 'dnf5-command(builddep)'
15+
else
16+
dnf -y install 'dnf-command(builddep)'
17+
fi
1418
# Handle version skew, xref https://gitlab.com/redhat/centos-stream/containers/bootc/-/issues/1174
1519
dnf -y distro-sync ostree{,-libs} systemd
1620
# Install base build requirements

0 commit comments

Comments
 (0)