sysext: Sign OS-dependent sysexts

Generate an ephemeral sysext signing key, that is injected into the
image's sysext root of trust. All OS-dependent sysexts will be signed by
this key and the private key (stored in /tmp) will be discarded on SDK
container exit.

Signed-off-by: Daniel Zatovic <daniel.zatovic@gmail.com>
This commit is contained in:
Daniel Zatovic 2025-07-30 10:24:18 +02:00
parent 89da3cc75c
commit 4a2154feb2
5 changed files with 82 additions and 12 deletions

View File

@ -170,6 +170,10 @@ EOF
# Remove source locale data, only need to ship the compiled archive.
sudo rm -rf ${root_fs_dir}/usr/share/i18n/
# Inject ephemeral sysext signing certificate
sudo mkdir -p "${root_fs_dir}/usr/lib/verity.d"
sudo cp "${SYSEXT_SIGNING_KEY_DIR}/sysexts.crt" "${root_fs_dir}/usr/lib/verity.d"
# Finish image will move files from /etc to /usr/share/flatcar/etc.
# Note that image filesystem contents generated by finish_image will not
# include sysext contents (only the sysext squashfs files themselves).

View File

@ -63,7 +63,7 @@ create_prod_sysext() {
# The --install_root_basename="${name}-base-sysext-rootfs" flag is
# important - it sets the name of a rootfs directory, which is used
# to determine the package target in coreos/base/profile.bashrc
sudo "FLATCAR_BUILD_ID=$FLATCAR_BUILD_ID" "${SCRIPTS_DIR}/build_sysext" \
sudo -E "FLATCAR_BUILD_ID=$FLATCAR_BUILD_ID" "${SCRIPTS_DIR}/build_sysext" \
--board="${BOARD}" \
--image_builddir="${workdir}/sysext-build" \
--squashfs_base="${base_sysext}" \
@ -99,6 +99,14 @@ sysext_mountdir="${BUILD_DIR}/prod-sysext-work/mounts"
sysext_base="${sysext_workdir}/base-os.squashfs"
function cleanup() {
IFS=':' read -r -a mounted_sysexts <<< "$sysext_lowerdirs"
# skip the rootfs
mounted_sysexts=("${mounted_sysexts[@]:1}")
for sysext in "${mounted_sysexts[@]}"; do
sudo systemd-dissect --umount --rmdir "$sysext"
done
sudo umount "${sysext_mountdir}"/* || true
rm -rf "${sysext_workdir}" || true
}
@ -116,6 +124,7 @@ sudo mksquashfs "${root_fs_dir}" "${sysext_base}" -noappend -xattrs-exclude '^bt
# for combined overlay later.
prev_pkginfo=""
sysext_lowerdirs="${sysext_mountdir}/rootfs-lower"
mkdir -p "${sysext_mountdir}"
for sysext in ${sysexts_list//,/ }; do
# format is "<name>:<group>/<package>"
name="${sysext%|*}"
@ -129,12 +138,21 @@ for sysext in ${sysexts_list//,/ }; do
"${grp_pkg}" \
"${prev_pkginfo}"
mkdir -p "${sysext_mountdir}/${name}" \
"${sysext_mountdir}/${name}_pkginfo"
sudo mount -rt squashfs -o loop,nodev "${sysext_output_dir}/${name}.raw" \
"${sysext_mountdir}/${name}"
sudo mount -rt squashfs -o loop,nodev "${sysext_output_dir}/${name}_pkginfo.raw" \
"${sysext_mountdir}/${name}_pkginfo"
sudo systemd-dissect \
--read-only \
--mount \
--mkdir \
--image-policy='root=encrypted+unprotected+absent:usr=encrypted+unprotected+absent' \
"${sysext_output_dir}/${name}.raw" \
"${sysext_mountdir}/${name}"
sudo systemd-dissect \
--read-only \
--mount \
--mkdir \
--image-policy='root=encrypted+unprotected+absent:usr=encrypted+unprotected+absent' \
"${sysext_output_dir}/${name}_pkginfo.raw" \
"${sysext_mountdir}/${name}_pkginfo"
sysext_lowerdirs="${sysext_lowerdirs}:${sysext_mountdir}/${name}"
sysext_lowerdirs="${sysext_lowerdirs}:${sysext_mountdir}/${name}_pkginfo"

View File

@ -602,7 +602,7 @@ install_oem_sysext() {
fi
mkdir -p "${built_sysext_dir}"
sudo "${build_sysext_env[@]}" "${SCRIPT_ROOT}/build_sysext" "${build_sysext_flags[@]}" "${oem_sysext}"
sudo -E "${build_sysext_env[@]}" "${SCRIPT_ROOT}/build_sysext" "${build_sysext_flags[@]}" "${oem_sysext}"
local installed_sysext_oem_dir='/oem/sysext'
local installed_sysext_file_prefix="${oem_sysext}-${version}"

View File

@ -304,14 +304,25 @@ if [[ -n "${invalid_files}" ]]; then
die "Invalid file ownership: ${invalid_files}"
fi
mksquashfs "${BUILD_DIR}/${FLAGS_install_root_basename}" "${BUILD_DIR}/${SYSEXTNAME}.raw" \
-noappend -xattrs-exclude '^btrfs.' -comp "${FLAGS_compression}" ${FLAGS_mksquashfs_opts}
systemd-repart \
--private-key="${SYSEXT_SIGNING_KEY_DIR}/sysexts.key" \
--certificate="${SYSEXT_SIGNING_KEY_DIR}/sysexts.crt" \
--make-ddi=sysext \
--copy-source="${BUILD_DIR}/${FLAGS_install_root_basename}" \
"${BUILD_DIR}/${SYSEXTNAME}.raw"
rm -rf "${BUILD_DIR}"/{fs-root,"${FLAGS_install_root_basename}",workdir}
# Generate reports
mkdir "${BUILD_DIR}/img-rootfs"
mount -rt squashfs -o loop,nodev "${BUILD_DIR}/${SYSEXTNAME}.raw" "${BUILD_DIR}/img-rootfs"
systemd-dissect --read-only \
--mount \
--mkdir \
--image-policy='root=encrypted+unprotected+absent:usr=encrypted+unprotected+absent' \
"${BUILD_DIR}/${SYSEXTNAME}.raw" \
"${BUILD_DIR}/img-rootfs"
write_contents "${BUILD_DIR}/img-rootfs" "${BUILD_DIR}/${SYSEXTNAME}_contents.txt"
write_contents_with_technical_details "${BUILD_DIR}/img-rootfs" "${BUILD_DIR}/${SYSEXTNAME}_contents_wtd.txt"
write_disk_space_usage_in_paths "${BUILD_DIR}/img-rootfs" "${BUILD_DIR}/${SYSEXTNAME}_disk_usage.txt"
umount "${BUILD_DIR}/img-rootfs"
systemd-dissect --umount --rmdir "${BUILD_DIR}/img-rootfs"

View File

@ -88,6 +88,43 @@ if ! grep -q 'export MODULE_SIGNING_KEY_DIR=' /home/sdk/.bashrc; then
fi
fi
# Ensure sysext signing keys exist; regenerate if directory or files missing
if grep -q 'export SYSEXT_SIGNING_KEY_DIR' /home/sdk/.bashrc; then
_existing_sysext_dir=$(source /home/sdk/.bashrc 2>/dev/null; echo "$SYSEXT_SIGNING_KEY_DIR")
if [[ -z "$_existing_sysext_dir" || ! -d "$_existing_sysext_dir" || ! -s "$_existing_sysext_dir/sysexts.key" || ! -s "$_existing_sysext_dir/sysexts.crt" ]]; then
# Drop stale export so block below regenerates
sed -i -e '/export SYSEXT_SIGNING_KEY_DIR=/d' /home/sdk/.bashrc
fi
fi
grep -q 'export SYSEXT_SIGNING_KEY_DIR' /home/sdk/.bashrc || {
if [[ ${COREOS_OFFICIAL:-0} -eq 1 ]]; then
SYSEXT_SIGNING_KEY_DIR=$(su sdk -c "mktemp -d")
else
SYSEXT_SIGNING_KEY_DIR="/home/sdk/.sysext-signing-keys"
su sdk -c "mkdir -p ${SYSEXT_SIGNING_KEY_DIR@Q}"
fi
if [[ ! "$SYSEXT_SIGNING_KEY_DIR" || ! -d "$SYSEXT_SIGNING_KEY_DIR" ]]; then
echo "Failed to create directory for sysext signing keys."
else
echo "export SYSEXT_SIGNING_KEY_DIR='$SYSEXT_SIGNING_KEY_DIR'" >> /home/sdk/.bashrc
fi
pushd "$SYSEXT_SIGNING_KEY_DIR" > /dev/null
build_id=$(source "/mnt/host/source/.repo/manifests/version.txt"; echo "$FLATCAR_BUILD_ID")
# Generate sysext signing key only if missing or empty
if [[ ! -s sysexts.key || ! -s sysexts.crt ]]; then
su sdk -c "openssl req -new -nodes -utf8 \
-x509 -batch -sha256 \
-days 36000 \
-outform PEM \
-out sysexts.crt \
-keyout sysexts.key \
-newkey 4096 \
-subj '/CN=Flatcar sysext key/OU=$build_id'" \
|| echo "Generating sysext signing key failed"
fi
popd > /dev/null
}
# This is ugly.
# We need to sudo su - sdk -c so the SDK user gets a fresh login.
# 'sdk' is member of multiple groups, and plain docker USER only