diff --git a/build_image b/build_image index c446b7e6583..aa0e57f1580 100755 --- a/build_image +++ b/build_image @@ -60,10 +60,12 @@ different forms. This scripts can be used to build the following: prod - Production image for CoreOS. This image is for booting (default if no argument is given). prodtar - Production container tar ball (implies prod). This can e.g. be used to run the Flatcar production image as a container (run machinectl import-tar or docker import). container - Developer image with single filesystem, bootable by nspawn. +sysext - Build extra sysexts (podman, python, zfs, etc.). +oem_sysext - Build OEM sysexts for all supported platforms. Examples: -build_image --board= [prod] [prodtar] [container] - builds developer and production images/tars. +build_image --board= [prod] [prodtar] [container] [sysext] [oem_sysext] - builds developer and production images/tars. ... " show_help_if_requested "$@" @@ -81,7 +83,7 @@ DEFINE_string version "" \ # Parse command line. FLAGS "$@" || exit 1 -eval set -- "${FLAGS_ARGV:-prod}" +eval set -- "${FLAGS_ARGV:-prod oem_sysext}" # Only now can we die on error. shflags functions leak non-zero error codes, # so will die prematurely if 'switch_to_strict_mode' is specified before now. @@ -103,17 +105,20 @@ fi . "${BUILD_LIBRARY_DIR}/test_image_content.sh" || exit 1 . "${BUILD_LIBRARY_DIR}/vm_image_util.sh" || exit 1 . "${BUILD_LIBRARY_DIR}/extra_sysexts.sh" || exit 1 +. "${BUILD_LIBRARY_DIR}/oem_sysexts.sh" || exit 1 PROD_IMAGE=0 PROD_TAR=0 CONTAINER=0 SYSEXT=0 +OEM_SYSEXT=0 for arg in "$@"; do case "${arg}" in prod) PROD_IMAGE=1 ;; prodtar) PROD_IMAGE=1 PROD_TAR=1 ;; container) CONTAINER=1 ;; sysext) SYSEXT=1 ;; + oem_sysext) OEM_SYSEXT=1 ;; *) die_notrace "Unknown image type ${arg}" ;; esac done @@ -187,6 +192,9 @@ fi if [[ "${SYSEXT}" -eq 1 ]]; then create_prod_sysexts "${FLATCAR_PRODUCTION_IMAGE_NAME}" fi +if [[ "${OEM_SYSEXT}" -eq 1 ]]; then + create_oem_sysexts "${FLATCAR_PRODUCTION_IMAGE_NAME}" +fi if [[ ${FLAGS_extract_update} -eq ${FLAGS_TRUE} ]]; then zip_update_tools diff --git a/build_library/oem_sysexts.sh b/build_library/oem_sysexts.sh new file mode 100644 index 00000000000..37734412986 --- /dev/null +++ b/build_library/oem_sysexts.sh @@ -0,0 +1,39 @@ +# OEM sysexts table mapping OEM IDs to their packages and USE flags. +# Format: "name|metapackage|useflag|arches" +# +# VM types that use each OEM sysext: +# oem-akamai -> akamai +# oem-ami -> ami, ami_vmdk +# oem-azure -> azure +# oem-digitalocean -> digitalocean +# oem-gce -> gce +# oem-hetzner -> hetzner +# oem-hyperv -> hyperv, hyperv_vhdx +# oem-kubevirt -> kubevirt +# oem-nutanix -> nutanix +# oem-openstack -> openstack, openstack_mini +# oem-packet -> packet +# oem-proxmoxve -> proxmoxve +# oem-qemu -> qemu_uefi +# oem-scaleway -> scaleway +# oem-stackit -> stackit +# oem-vmware -> vmware, vmware_ova, vmware_raw + +OEM_SYSEXTS=( + "oem-akamai|coreos-base/oem-akamai|akamai|amd64,arm64" + "oem-ami|coreos-base/oem-ami|ami|amd64,arm64" + "oem-azure|coreos-base/oem-azure|azure|amd64,arm64" + "oem-digitalocean|coreos-base/oem-digitalocean|digitalocean|amd64" + "oem-gce|coreos-base/oem-gce|gce|amd64" + "oem-hetzner|coreos-base/oem-hetzner|hetzner|amd64,arm64" + "oem-hyperv|coreos-base/oem-hyperv|hyperv|amd64" + "oem-kubevirt|coreos-base/oem-kubevirt|kubevirt|amd64,arm64" + "oem-nutanix|coreos-base/oem-nutanix|nutanix|amd64" + "oem-openstack|coreos-base/oem-openstack|openstack|amd64,arm64" + "oem-packet|coreos-base/oem-packet|packet|amd64,arm64" + "oem-proxmoxve|coreos-base/oem-proxmoxve|proxmoxve|amd64,arm64" + "oem-qemu|coreos-base/oem-qemu|qemu|amd64,arm64" + "oem-scaleway|coreos-base/oem-scaleway|scaleway|amd64,arm64" + "oem-stackit|coreos-base/oem-stackit|stackit|amd64,arm64" + "oem-vmware|coreos-base/oem-vmware|vmware|amd64" +) diff --git a/build_library/prod_image_util.sh b/build_library/prod_image_util.sh index 7463f26d402..dbaefaee6ce 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). @@ -269,6 +273,67 @@ create_prod_sysexts() { done } +create_oem_sysexts() { + local image_name="$1" + local image_sysext_base="${image_name%.bin}_sysext.squashfs" + local overlay_path + overlay_path=$(portageq get_repo_path / coreos-overlay) + + for sysext in "${OEM_SYSEXTS[@]}"; do + local name metapkg useflags arches + IFS="|" read -r name metapkg useflags arches <<< "$sysext" + + if [[ "${name}" != oem-* ]]; then + die "OEM sysext name must start with 'oem-', got '${name}'" + fi + + local arch_array=(${arches//,/ }) + + if [[ -n "$arches" ]]; then + local should_skip=1 + for arch in "${arch_array[@]}"; do + if [[ $arch == "$ARCH" ]]; then + should_skip=0 + fi + done + if [[ $should_skip -eq 1 ]]; then + continue + fi + fi + + # Check for manglefs script in the package's files directory + local mangle_script="${overlay_path}/${metapkg}/files/manglefs.sh" + if [[ ! -x "${mangle_script}" ]]; then + mangle_script= + fi + + sudo rm -f "${BUILD_DIR}/${name}.raw" \ + "${BUILD_DIR}/flatcar_test_update-${name}.gz" \ + "${BUILD_DIR}/${name}_"* + + info "Building OEM sysext ${name} with USE=${useflags}" + # The --install_root_basename="${name}-oem-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 + # + # OEM sysexts use no compression here since they will be stored + # in a compressed OEM partition. + USE="${useflags}" sudo -E "${SCRIPT_ROOT}/build_sysext" --board="${BOARD}" \ + --squashfs_base="${BUILD_DIR}/${image_sysext_base}" \ + --image_builddir="${BUILD_DIR}" \ + --metapkgs="${metapkg}" \ + --install_root_basename="${name}-oem-sysext-rootfs" \ + --compression=none \ + ${mangle_script:+--manglefs_script="${mangle_script}"} \ + "${name}" + delta_generator \ + -private_key "/usr/share/update_engine/update-payload-key.key.pem" \ + -new_image "${BUILD_DIR}/${name}.raw" \ + -out_file "${BUILD_DIR}/flatcar_test_update-${name}.gz" + done +} + sbsign_prod_image() { local image_name="$1" local disk_layout="$2" diff --git a/build_library/sysext_prod_builder b/build_library/sysext_prod_builder index d90fb4a1da2..8e570806303 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 ac83929cfa6..ba715812195 100644 --- a/build_library/vm_image_util.sh +++ b/build_library/vm_image_util.sh @@ -568,7 +568,8 @@ install_oem_package() { sudo rm -rf "${oem_tmp}" } -# Write the OEM sysext file into the OEM partition. +# Install the prebuilt OEM sysext file into the OEM partition. +# The sysext should have been built by 'build_image oem_sysext'. install_oem_sysext() { local oem_sysext=$(_get_vm_opt OEM_SYSEXT) @@ -576,59 +577,24 @@ install_oem_sysext() { return 0 fi - local built_sysext_dir="${FLAGS_to}/${oem_sysext}-sysext" - local built_sysext_filename="${oem_sysext}.raw" - local built_sysext_path="${built_sysext_dir}/${built_sysext_filename}" + local prebuilt_sysext_filename="${oem_sysext}.raw" + local prebuilt_sysext_path="${FLAGS_from}/${prebuilt_sysext_filename}" local version="${FLATCAR_VERSION}" - local metapkg="coreos-base/${oem_sysext}" - # The --install_root_basename="${name}-oem-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 - local build_sysext_flags=( - --board="${BOARD}" - --squashfs_base="${VM_SRC_SYSEXT_IMG}" - --image_builddir="${built_sysext_dir}" - --metapkgs="${metapkg}" - --install_root_basename="${VM_IMG_TYPE}-oem-sysext-rootfs" - ) - local overlay_path mangle_fs - overlay_path=$(portageq get_repo_path / coreos-overlay) - mangle_fs="${overlay_path}/${metapkg}/files/manglefs.sh" - if [[ -x "${mangle_fs}" ]]; then - build_sysext_flags+=( - --manglefs_script="${mangle_fs}" - ) - fi - mkdir -p "${built_sysext_dir}" - sudo "${build_sysext_env[@]}" "${SCRIPT_ROOT}/build_sysext" "${build_sysext_flags[@]}" "${oem_sysext}" + if [[ ! -f "${prebuilt_sysext_path}" ]]; then + die "Prebuilt OEM sysext not found at ${prebuilt_sysext_path}. Run 'build_image oem_sysext' first." + fi local installed_sysext_oem_dir='/oem/sysext' local installed_sysext_file_prefix="${oem_sysext}-${version}" local installed_sysext_filename="${installed_sysext_file_prefix}.raw" local installed_sysext_abspath="${installed_sysext_oem_dir}/${installed_sysext_filename}" - info "Installing ${oem_sysext} sysext" + + info "Installing ${oem_sysext} sysext from prebuilt image" sudo install -Dpm 0644 \ - "${built_sysext_path}" \ + "${prebuilt_sysext_path}" \ "${VM_TMP_ROOT}${installed_sysext_abspath}" || die "Could not install ${oem_sysext} sysext" - # Move sysext image and reports to a destination directory to - # upload them, thus making them available as separate artifacts to - # download. - local upload_dir to_move - upload_dir="$(_dst_dir)" - for to_move in "${built_sysext_dir}/${oem_sysext}"*; do - mv "${to_move}" "${upload_dir}/${to_move##*/}" - done - # Generate dev-key-signed update payload for testing - delta_generator \ - -private_key "/usr/share/update_engine/update-payload-key.key.pem" \ - -new_image "${upload_dir}/${built_sysext_filename}" \ - -out_file "${upload_dir}/flatcar_test_update-${oem_sysext}.gz" - # Remove sysext_dir if building sysext and installing it - # succeeded. - rm -rf "${built_sysext_dir}" # Mark the installed sysext as active. sudo touch "${VM_TMP_ROOT}${installed_sysext_oem_dir}/active-${oem_sysext}" diff --git a/build_packages b/build_packages index d300edff828..c55bac450cb 100755 --- a/build_packages +++ b/build_packages @@ -118,6 +118,7 @@ fi . "${BUILD_LIBRARY_DIR}/board_options.sh" || exit 1 . "${BUILD_LIBRARY_DIR}/test_image_content.sh" || exit 1 . "${BUILD_LIBRARY_DIR}/extra_sysexts.sh" || exit 1 +. "${BUILD_LIBRARY_DIR}/oem_sysexts.sh" || exit 1 # Setup all the emerge command/flags. EMERGE_FLAGS=( --update --deep --newuse --verbose --backtrack=30 --select ) @@ -288,50 +289,62 @@ fi export KBUILD_BUILD_USER="${BUILD_USER:-build}" export KBUILD_BUILD_HOST="${BUILD_HOST:-pony-truck.infra.kinvolk.io}" -info "Merging board packages now" -sudo -E "${EMERGE_CMD[@]}" "${EMERGE_FLAGS[@]}" "$@" - -info "Merging sysext packages now" -for sysext in "${EXTRA_SYSEXTS[@]}"; do - IFS="|" read -r SYSEXT_NAME PACKAGE_ATOMS USEFLAGS ARCHES <<< "$sysext" - - arch_array=("${ARCHES//,/ }") - if [[ -n $ARCHES ]]; then - should_skip=1 - for arch in "${arch_array[@]}"; do - if [[ $arch == "$ARCH" ]]; then - should_skip=0 +# Build sysext packages from an array of sysext definitions. +# Usage: build_sysext_packages "description" "${SYSEXT_ARRAY[@]}" +# Array format: "name|packages|useflags|arches" +build_sysext_packages() { + local description="$1" + shift + local sysexts=("$@") + + info "Merging ${description} packages now" + for sysext in "${sysexts[@]}"; do + local sysext_name package_atoms useflags arches + IFS="|" read -r sysext_name package_atoms useflags arches <<< "$sysext" + + local arch_array=(${arches//,/ }) + if [[ -n $arches ]]; then + local should_skip=1 + for arch in "${arch_array[@]}"; do + if [[ $arch == "$ARCH" ]]; then + should_skip=0 + fi + done + if [[ $should_skip -eq 1 ]]; then + continue fi - done - if [[ $should_skip -eq 1 ]]; then - continue fi - fi - - info "Building packages for $SYSEXT_NAME sysext with USE=$USEFLAGS" - IFS=, - for package in $PACKAGE_ATOMS; do - # --buildpkgonly does not install dependencies, so we install them - # separately before building the binary package - sudo --preserve-env=MODULES_SIGN_KEY,MODULES_SIGN_CERT \ - env USE="$USEFLAGS" FEATURES="-ebuild-locks binpkg-multi-instance" "${EMERGE_CMD[@]}" \ - "${EMERGE_FLAGS[@]}" \ - --quiet \ - --onlydeps \ - --binpkg-respect-use=y \ - "${package}" - - sudo --preserve-env=MODULES_SIGN_KEY,MODULES_SIGN_CERT \ - env USE="$USEFLAGS" FEATURES="-ebuild-locks binpkg-multi-instance" "${EMERGE_CMD[@]}" \ - "${EMERGE_FLAGS[@]}" \ - --quiet \ - --buildpkgonly \ - --binpkg-respect-use=y \ - "${package}" + info "Building packages for $sysext_name sysext with USE=$useflags" + IFS=, + for package in $package_atoms; do + # --buildpkgonly does not install dependencies, so we install them + # separately before building the binary package + sudo --preserve-env=MODULES_SIGN_KEY,MODULES_SIGN_CERT \ + env USE="$useflags" FEATURES="-ebuild-locks binpkg-multi-instance" "${EMERGE_CMD[@]}" \ + "${EMERGE_FLAGS[@]}" \ + --quiet \ + --onlydeps \ + --binpkg-respect-use=y \ + "${package}" + + sudo --preserve-env=MODULES_SIGN_KEY,MODULES_SIGN_CERT \ + env USE="$useflags" FEATURES="-ebuild-locks binpkg-multi-instance" "${EMERGE_CMD[@]}" \ + "${EMERGE_FLAGS[@]}" \ + --quiet \ + --buildpkgonly \ + --binpkg-respect-use=y \ + "${package}" + done + unset IFS done - unset IFS -done +} + +info "Merging board packages now" +sudo -E "${EMERGE_CMD[@]}" "${EMERGE_FLAGS[@]}" "$@" + +build_sysext_packages "extra sysexts" "${EXTRA_SYSEXTS[@]}" +build_sysext_packages "OEM sysexts" "${OEM_SYSEXTS[@]}" info "Removing obsolete packages" # The return value of emerge is not clearly reliable. It may fail with diff --git a/build_sysext b/build_sysext index 92d6abc4009..4a0d450e0b8 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 @@ -220,7 +216,7 @@ if [[ ${#} -lt 1 ]]; then show_help_if_requested -h fi -info "Building '${SYSEXTNAME}' squashfs with (meta-)packages '${@}' in '${BUILD_DIR}' using '${FLAGS_compression}' compression". +info "Building '${SYSEXTNAME}' sysext with (meta-)packages '${@}' in '${BUILD_DIR}' using '${FLAGS_compression}' compression". for package; do echo "Installing package into sysext image: $package" @@ -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" @@ -304,14 +300,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 00000000000..3de3637f9cd --- /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, podman, zfs, nvidia) 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). OEM sysexts (e.g., oem-azure, oem-gce) are now also signed and built during the image phase to ensure consistent signing with the same ephemeral key. ([scripts#3162](https://github.com/flatcar/scripts/pull/3162)) diff --git a/ci-automation/image.sh b/ci-automation/image.sh index 09ca5e904fc..552c382e042 100644 --- a/ci-automation/image.sh +++ b/ci-automation/image.sh @@ -103,7 +103,7 @@ function _image_build_impl() { --base_sysexts="${base_sysexts_param}" \ --output_root="${CONTAINER_IMAGE_ROOT}" \ --only_store_compressed \ - prodtar container sysext + prodtar container sysext oem_sysext # copy resulting images + push to buildcache local images_out="images/" diff --git a/ci-automation/vms.sh b/ci-automation/vms.sh index b882d19a04e..bb7961d00cf 100644 --- a/ci-automation/vms.sh +++ b/ci-automation/vms.sh @@ -116,6 +116,25 @@ function _vm_build_impl() { for file in flatcar_production_image.bin.bz2 flatcar_production_image_sysext.squashfs flatcar_production_image.vmlinuz version.txt; do copy_from_buildcache "images/${arch}/${vernum}/${file}" "${images_in}" done + + # Download prebuilt OEM sysexts + source build_library/oem_sysexts.sh + local sysext name arches arch_array + for sysext in "${OEM_SYSEXTS[@]}"; do + IFS="|" read -r name _ _ arches <<< "$sysext" + # Skip if sysext doesn't support this architecture + if [[ -n "$arches" ]]; then + arch_array=(${arches//,/ }) + local should_skip=1 + local a + for a in "${arch_array[@]}"; do + [[ "$a" == "$arch" ]] && should_skip=0 + done + [[ $should_skip -eq 1 ]] && continue + fi + copy_from_buildcache "images/${arch}/${vernum}/${name}.raw" "${images_in}" + done + lbunzip2 "${images_in}/flatcar_production_image.bin.bz2" ./run_sdk_container -x ./ci-cleanup.sh -n "${vms_container}" -C "${packages_image}" \ -v "${vernum}" \ 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 41459225643..641b433bda1 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 6458bf8271a..262de3751b1 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