From 4a2154feb275303258216c40f261e91e37c8e4f3 Mon Sep 17 00:00:00 2001 From: Daniel Zatovic Date: Wed, 30 Jul 2025 10:24:18 +0200 Subject: [PATCH] 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 --- build_library/prod_image_util.sh | 4 ++++ build_library/sysext_prod_builder | 32 ++++++++++++++++++++------ build_library/vm_image_util.sh | 2 +- build_sysext | 19 ++++++++++++---- sdk_lib/sdk_entry.sh | 37 +++++++++++++++++++++++++++++++ 5 files changed, 82 insertions(+), 12 deletions(-) diff --git a/build_library/prod_image_util.sh b/build_library/prod_image_util.sh index 7463f26d40..7a0c34b50c 100755 --- a/build_library/prod_image_util.sh +++ b/build_library/prod_image_util.sh @@ -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). diff --git a/build_library/sysext_prod_builder b/build_library/sysext_prod_builder index d90fb4a1da..87599feb80 100755 --- a/build_library/sysext_prod_builder +++ b/build_library/sysext_prod_builder @@ -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="${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" diff --git a/build_library/vm_image_util.sh b/build_library/vm_image_util.sh index ac83929cfa..159fede04e 100644 --- a/build_library/vm_image_util.sh +++ b/build_library/vm_image_util.sh @@ -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}" diff --git a/build_sysext b/build_sysext index 92d6abc400..be7e504774 100755 --- a/build_sysext +++ b/build_sysext @@ -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" diff --git a/sdk_lib/sdk_entry.sh b/sdk_lib/sdk_entry.sh index 6458bf8271..262de3751b 100755 --- a/sdk_lib/sdk_entry.sh +++ b/sdk_lib/sdk_entry.sh @@ -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