From fcd6b52d6fa283b02b2b7e20f34cb64ed832cd48 Mon Sep 17 00:00:00 2001 From: Kai Lueke Date: Tue, 16 Dec 2025 17:49:11 +0900 Subject: [PATCH] Revert "Revert PR #3534 "sysext: Add OS-dependent sysext compression"" This reverts commit d28ece4479024c651e514dbc6bbbde12ab326a6e. Signed-off-by: Kai Lueke --- build_library/prod_image_util.sh | 4 ++ build_library/sysext_prod_builder | 36 ++++++++++--- build_library/vm_image_util.sh | 6 ++- build_sysext | 52 ++++++++++++++----- .../2025-11-05-signed-os-dependent-sysexts.md | 1 + .../profiles/coreos/targets/sdk/package.use | 3 ++ sdk_lib/sdk_entry.sh | 37 +++++++++++++ 7 files changed, 118 insertions(+), 21 deletions(-) create mode 100644 changelog/changes/2025-11-05-signed-os-dependent-sysexts.md 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..8e57080630 100755 --- a/build_library/sysext_prod_builder +++ b/build_library/sysext_prod_builder @@ -63,11 +63,15 @@ 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" \ + # + # Built-in sysexts are stored in the compressed /usr partition, so we + # disable compression to avoid double-compression. + sudo -E "FLATCAR_BUILD_ID=$FLATCAR_BUILD_ID" "${SCRIPTS_DIR}/build_sysext" \ --board="${BOARD}" \ --image_builddir="${workdir}/sysext-build" \ --squashfs_base="${base_sysext}" \ --generate_pkginfo \ + --compression=none \ --install_root_basename="${name}-base-sysext-rootfs" \ "${build_sysext_opts[@]}" \ "${name}" "${grp_pkg[@]}" @@ -99,6 +103,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 +128,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 +142,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..73eb43b812 100644 --- a/build_library/vm_image_util.sh +++ b/build_library/vm_image_util.sh @@ -585,11 +585,15 @@ install_oem_sysext() { # important - it sets the name of a rootfs directory, which is # used to determine the package target in # coreos/base/profile.bashrc + # + # OEM sysexts are stored in the compressed partition, so we disable + # compression to avoid double-compression. local build_sysext_flags=( --board="${BOARD}" --squashfs_base="${VM_SRC_SYSEXT_IMG}" --image_builddir="${built_sysext_dir}" --metapkgs="${metapkg}" + --compression=none --install_root_basename="${VM_IMG_TYPE}-oem-sysext-rootfs" ) local overlay_path mangle_fs @@ -602,7 +606,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 c70723ff7e..806e9d0258 100755 --- a/build_sysext +++ b/build_sysext @@ -35,10 +35,10 @@ DEFINE_boolean generate_pkginfo "${FLAGS_FALSE}" \ "Generate an additional squashfs '_pkginfo.raw' with portage package meta-information (/var/db ...). Useful for creating sysext dependencies; see 'base_pkginfo' below." DEFINE_string base_pkginfo "" \ "Colon-separated list of pkginfo squashfs paths / files generated via 'generate_pkginfo' to base this sysext on. The corresponding base sysexts are expected to be merged with the sysext generated." -DEFINE_string compression "zstd" \ - "Compression to use for sysext squashfs. One of 'gzip', 'lzo', 'lz4', 'xz', or 'zstd'. Must be supported by the Flatcar squashfs kernel module in order for the sysext to work." -DEFINE_string mksquashfs_opts "" \ - "Additional command line options to pass to mksquashfs. See 'man 1 mksquashfs'. If is 'zstd' (the default), this option defaults to '-Xcompression-level 22 -b 512K'. Otherwise the default is empty." +DEFINE_string compression "lz4hc" \ + "Compression to use for sysext EROFS image. Options: 'lz4', 'lz4hc', 'zstd', or 'none'. Default is 'lz4hc'." +DEFINE_string mkerofs_opts "" \ + "Additional mkfs.erofs options to pass via SYSTEMD_REPART_MKFS_OPTIONS_EROFS. If not specified, defaults are used based on compression type." DEFINE_boolean ignore_version_mismatch "${FLAGS_FALSE}" \ "Ignore version mismatch between SDK board packages and base squashfs. DANGEROUS." DEFINE_string install_root_basename "${default_install_root_basename}" \ @@ -112,10 +112,6 @@ fi BUILD_DIR=$(realpath "${FLAGS_image_builddir}") mkdir -p "${BUILD_DIR}" -if [[ "${FLAGS_compression}" = "zstd" && -z "${FLAGS_mksquashfs_opts}" ]] ; then - FLAGS_mksquashfs_opts="-Xcompression-level 22 -b 512k" -fi - source "${BUILD_LIBRARY_DIR}/toolchain_util.sh" || exit 1 source "${BUILD_LIBRARY_DIR}/board_options.sh" || exit 1 source "${BUILD_LIBRARY_DIR}/reports_util.sh" || exit 1 @@ -248,7 +244,7 @@ if [[ "$FLAGS_generate_pkginfo" = "${FLAGS_TRUE}" ]] ; then mkdir -p "${BUILD_DIR}/img-pkginfo/var/db" cp -R "${BUILD_DIR}/${FLAGS_install_root_basename}/var/db/pkg" "${BUILD_DIR}/img-pkginfo/var/db/" mksquashfs "${BUILD_DIR}/img-pkginfo" "${BUILD_DIR}/${SYSEXTNAME}_pkginfo.raw" \ - -noappend -xattrs-exclude '^btrfs.' -comp "${FLAGS_compression}" ${FLAGS_mksquashfs_opts} + -noappend -xattrs-exclude '^btrfs.' -comp zstd -Xcompression-level 22 -b 512k fi info "Writing ${SYSEXTNAME}_packages.txt" @@ -305,14 +301,44 @@ 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} +# Set up EROFS compression options based on compression type +if [[ "${FLAGS_compression}" != "none" ]]; then + export SYSTEMD_REPART_MKFS_OPTIONS_EROFS="-z${FLAGS_compression}" + + if [[ -n "${FLAGS_mkerofs_opts}" ]]; then + # User provided custom options + export SYSTEMD_REPART_MKFS_OPTIONS_EROFS="${SYSTEMD_REPART_MKFS_OPTIONS_EROFS} ${FLAGS_mkerofs_opts}" + elif [[ "${FLAGS_compression}" = "lz4hc" ]]; then + # Default options for lz4hc + export SYSTEMD_REPART_MKFS_OPTIONS_EROFS="${SYSTEMD_REPART_MKFS_OPTIONS_EROFS},12 -C65536 -Efragments,ztailpacking" + elif [[ "${FLAGS_compression}" = "zstd" ]]; then + # Default options for zstd + export SYSTEMD_REPART_MKFS_OPTIONS_EROFS="${SYSTEMD_REPART_MKFS_OPTIONS_EROFS},level=22 -C524288 -Efragments,ztailpacking" + fi + info "Building sysext with ${FLAGS_compression} compression" +else + info "Building sysext without compression (built-in sysexts)" +fi + +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/changelog/changes/2025-11-05-signed-os-dependent-sysexts.md b/changelog/changes/2025-11-05-signed-os-dependent-sysexts.md new file mode 100644 index 0000000000..196b9266b1 --- /dev/null +++ b/changelog/changes/2025-11-05-signed-os-dependent-sysexts.md @@ -0,0 +1 @@ +- OS-dependent sysexts (e.g., docker-flatcar, containerd-flatcar) are now cryptographically signed using dm-verity roothash signatures. This enables stricter sysext policies via systemd-sysext and provides a foundation for verifying user-provided extensions in future releases. The format changed from squashfs to erofs-based Discoverable Disk Images (DDI). ([scripts#3162](https://github.com/flatcar/scripts/pull/3162)) diff --git a/sdk_container/src/third_party/coreos-overlay/profiles/coreos/targets/sdk/package.use b/sdk_container/src/third_party/coreos-overlay/profiles/coreos/targets/sdk/package.use index 4145922564..641b433bda 100644 --- a/sdk_container/src/third_party/coreos-overlay/profiles/coreos/targets/sdk/package.use +++ b/sdk_container/src/third_party/coreos-overlay/profiles/coreos/targets/sdk/package.use @@ -32,3 +32,6 @@ x11-libs/pixman static-libs # Get latest EDK2 firmware for Secure Boot on arm64. app-emulation/qemu -pin-upstream-blobs + +# Needed for signed sysexts using systemd-repart +sys-apps/systemd cryptsetup 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