From 2e9d785edfe3ebc620b8d5326d8f70f9175476fd Mon Sep 17 00:00:00 2001 From: Dallas Delaney Date: Fri, 9 Oct 2026 17:47:09 +0000 Subject: [PATCH] Add a containerd config to set erofs when ipe is enabled --- acl/docs/BUILD_RPM_IMAGE_README.md | 7 ++ acl/sysexts.yaml | 1 + .../ipe/offline/test-ipe-loader-runtime.sh | 7 ++ acl/tests/ipe/run-ipe-audit-test.sh | 27 +++++++ .../containerd2/containerd-acl-erofs.toml | 11 +++ .../containerd2/containerd-acl-profile.conf | 9 +++ .../containerd2/containerd-acl-select-profile | 78 +++++++++++++++++++ .../dracut-acl-ipe-load/acl-ipe-load.sh | 13 ++++ .../dracut-acl-ipe-load/module-setup.sh | 3 + build_library/rpm/dracut_install.sh | 2 + .../rpm/sysext_mangle_containerd-flatcar.sh | 7 ++ 11 files changed, 165 insertions(+) create mode 100644 build_library/rpm/additional_files/containerd2/containerd-acl-erofs.toml create mode 100644 build_library/rpm/additional_files/containerd2/containerd-acl-profile.conf create mode 100755 build_library/rpm/additional_files/containerd2/containerd-acl-select-profile diff --git a/acl/docs/BUILD_RPM_IMAGE_README.md b/acl/docs/BUILD_RPM_IMAGE_README.md index 3ab2a3c3c0b..7b49bb4e3e0 100644 --- a/acl/docs/BUILD_RPM_IMAGE_README.md +++ b/acl/docs/BUILD_RPM_IMAGE_README.md @@ -176,6 +176,13 @@ conversion rejects active UKI addons (including the optional `EXTRA_KERNEL_CMDLINE` debug addon) that supply another `acl.ipe.policy_sha256` token. +IPE activation triggers a containerd systemd drop-in that imports +a base config (AgentBaker's on AKS nodes) merged with +`/usr/share/containerd/acl-erofs.toml`, which selects EROFS as the CRI +and snapshotter plugins and enables the flag to discover dm-verity OCI +referrers. This requires a matching containerd RPM with signed EROFS referrer +support. Disabled IPE leaves the base config selected. + **Build output location:** `__build__/images/images/amd64-usr/latest/` ### Phase 4: Build VM Image (Optional) diff --git a/acl/sysexts.yaml b/acl/sysexts.yaml index 36c0e9a3ea8..7c1795f6ca5 100644 --- a/acl/sysexts.yaml +++ b/acl/sysexts.yaml @@ -57,6 +57,7 @@ sysexts: mode: embedded packages: - app-containers/containerd + - erofs-utils # --- scenario-specific sysexts ----------------------------------------- - name: docker diff --git a/acl/tests/ipe/offline/test-ipe-loader-runtime.sh b/acl/tests/ipe/offline/test-ipe-loader-runtime.sh index ab589bc5d95..8200240d2af 100755 --- a/acl/tests/ipe/offline/test-ipe-loader-runtime.sh +++ b/acl/tests/ipe/offline/test-ipe-loader-runtime.sh @@ -6,6 +6,7 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../../.." && pwd)" LOADER="${SCRIPT_DIR}/build_library/rpm/additional_files/dracut-acl-ipe-load/acl-ipe-load.sh" PROFILE_HELPER="${SCRIPT_DIR}/build_library/rpm/additional_files/acl-node-security-profile.sh" +CONTAINERD_EROFS_PROFILE="${SCRIPT_DIR}/build_library/rpm/additional_files/containerd2/containerd-acl-profile.conf" TMP_DIR="$(mktemp -d)" trap 'rm -rf "${TMP_DIR}"' EXIT @@ -31,6 +32,7 @@ prepare_case() { mkdir -p "${IPE_DIR}" : > "${IPE_DIR}/new_policy" printf '9\n' > "${IPE_DIR}/enforce" + cp "${CONTAINERD_EROFS_PROFILE}" "${CASE_DIR}/containerd-erofs-profile.conf" command cat > "${CASE_DIR}/security-profile.sh" <<'EOF' acl_security_profile() { @@ -48,6 +50,8 @@ run_loader() { ACL_IPE_CMDLINE_FILE="${CASE_DIR}/cmdline" \ ACL_IPE_CREDENTIAL_PATH="${credential}" \ ACL_IPE_SECURITY_PROFILE_HELPER="${CASE_DIR}/security-profile.sh" \ + ACL_IPE_CONTAINERD_PROFILE="${CASE_DIR}/containerd-erofs-profile.conf" \ + ACL_IPE_CONTAINERD_DROPIN="${CASE_DIR}/dropins/90-acl-profile.conf" \ bash "${LOADER}" 2>&1 } @@ -105,6 +109,7 @@ EOF [[ -s "${IPE_DIR}/new_policy" ]] # enforce should remain unchanged (not set to 0) [[ "$(<"${IPE_DIR}/enforce")" == "9" ]] + [[ ! -e "${CASE_DIR}/dropins/90-acl-profile.conf" ]] } # ---- Test: the removed runtime alias must not activate IPE ---- @@ -149,6 +154,7 @@ EOF { echo "loader did not report requested mode '${imds_mode}'" >&2; return 1; } [[ "$(<"${IPE_DIR}/enforce")" == "0" ]] [[ "$(<"${IPE_DIR}/policies/acl_ipe_boot_policy/active")" == "1" ]] + cmp "${CASE_DIR}/containerd-erofs-profile.conf" "${CASE_DIR}/dropins/90-acl-profile.conf" ) # ---- Test: 'enforcing' is reserved; loader logs an explicit unsupported @@ -172,6 +178,7 @@ EOF # Verify policy was loaded but not activated, and boot was not blocked. [[ -s "${IPE_DIR}/new_policy" ]] [[ "$(<"${IPE_DIR}/enforce")" == "9" ]] + [[ ! -e "${CASE_DIR}/dropins/90-acl-profile.conf" ]] } diff --git a/acl/tests/ipe/run-ipe-audit-test.sh b/acl/tests/ipe/run-ipe-audit-test.sh index 65189601f7e..480b14168ef 100755 --- a/acl/tests/ipe/run-ipe-audit-test.sh +++ b/acl/tests/ipe/run-ipe-audit-test.sh @@ -97,6 +97,33 @@ if [[ "$(tr -d '[:space:]' < "${POLICY_DIR}/active")" != "1" ]]; then fail "policy ${POLICY_NAME} is not active" fi +systemctl is-active --quiet containerd || + fail "containerd is not running" +containerd_pid="$(systemctl show --property=MainPID --value containerd)" || + fail "could not get containerd's PID" +containerd_cmdline="$(tr '\0' ' ' < "/proc/${containerd_pid}/cmdline")" || + fail "could not read containerd's command line" +[[ " ${containerd_cmdline} " == *" --config /run/containerd/acl-config.toml "* ]] || + fail "containerd is not using the IPE-selected config" +if ! /usr/bin/containerd --config /run/containerd/acl-config.toml config dump | + awk ' + { + gsub(/\047/, "\"") + sub(/^[[:space:]]+/, "") + sub(/[[:space:]]+$/, "") + } + /^\[/ { section = $0 } + section == "[plugins.\"io.containerd.snapshotter.v1.erofs\"]" && + /^enable_dmverity_referrers =/ { referrers = ($0 == "enable_dmverity_referrers = true") } + section == "[plugins.\"io.containerd.cri.v1.images\"]" && + /^snapshotter =/ { snapshotter = ($0 == "snapshotter = \"erofs\"") } + section == "[plugins.\"io.containerd.service.v1.diff-service\"]" && + /^default =/ { differ = ($0 == "default = [\"erofs\", \"walking\"]") } + END { if (!referrers || !snapshotter || !differ) exit 1 } + '; then + fail "effective containerd config does not enable the expected EROFS profile" +fi + policy="$(cat "${POLICY_DIR}/policy")" grep -Fq "DEFAULT op=EXECUTE action=DENY" <<< "${policy}" || fail "active policy does not deny untrusted execution by default" diff --git a/build_library/rpm/additional_files/containerd2/containerd-acl-erofs.toml b/build_library/rpm/additional_files/containerd2/containerd-acl-erofs.toml new file mode 100644 index 00000000000..38c16c155fb --- /dev/null +++ b/build_library/rpm/additional_files/containerd2/containerd-acl-erofs.toml @@ -0,0 +1,11 @@ +# Imported when IPE is activated at boot +version = 4 + +[plugins."io.containerd.snapshotter.v1.erofs"] + enable_dmverity_referrers = true + +[plugins."io.containerd.cri.v1.images"] + snapshotter = "erofs" + +[plugins."io.containerd.service.v1.diff-service"] + default = ["erofs", "walking"] diff --git a/build_library/rpm/additional_files/containerd2/containerd-acl-profile.conf b/build_library/rpm/additional_files/containerd2/containerd-acl-profile.conf new file mode 100644 index 00000000000..3f96adfac30 --- /dev/null +++ b/build_library/rpm/additional_files/containerd2/containerd-acl-profile.conf @@ -0,0 +1,9 @@ +# Installed by the IPE loader only after policy activation. +[Unit] +Wants=modprobe@erofs.service modprobe@dm_verity.service +After=modprobe@erofs.service modprobe@dm_verity.service + +[Service] +ExecStartPre=/usr/libexec/containerd/acl-select-profile ${CONTAINERD_CONFIG} +ExecStart= +ExecStart=/usr/bin/containerd --config /run/containerd/acl-config.toml diff --git a/build_library/rpm/additional_files/containerd2/containerd-acl-select-profile b/build_library/rpm/additional_files/containerd2/containerd-acl-select-profile new file mode 100755 index 00000000000..d47ed6edebb --- /dev/null +++ b/build_library/rpm/additional_files/containerd2/containerd-acl-select-profile @@ -0,0 +1,78 @@ +#!/bin/bash + +set -euo pipefail + +base_config="${1:-}" +merged_config_path="${ACL_CONTAINERD_CONFIG_PATH:-/run/containerd/acl-config.toml}" +erofs_config="${ACL_CONTAINERD_EROFS_CONFIG:-/usr/share/containerd/acl-erofs.toml}" + +fail() { echo "ERROR: $*" >&2; exit 1; } + +[[ -n "${base_config}" && -r "${base_config}" ]] || + fail "containerd base config is not readable: ${base_config}" +[[ -r "${erofs_config}" ]] || + fail "containerd EROFS profile is not readable: ${erofs_config}" + +# Convert a given path to an absolute path and escape it for use in a TOML file. +get_absolute_path() { + local path="$1" + [[ "${path}" == /* ]] || path="$(pwd)/${path}" + path="${path//\\/\\\\}" + path="${path//\"/\\\"}" + printf '"%s"' "${path}" +} + +# Merge the base containerd config with the EROFS profile. +mkdir -p "$(dirname "${merged_config_path}")" +temporary_merged_config="${merged_config_path}.tmp.$$" +trap 'rm -f -- "${temporary_merged_config}"' EXIT +cat > "${temporary_merged_config}" <&2; } skip() { log "WARNING: $*; continuing boot without activating IPE."; exit 0; } @@ -110,3 +112,14 @@ log "Activated policy ${POLICY_NAME}." policy_active="$(cat "${active_file}" 2>/dev/null || true)" [[ "${policy_active}" == "1" ]] || skip "policy ${POLICY_NAME} activation verification failed" + +if [[ ! -r "${CONTAINERD_PROFILE_SRC}" ]]; then + log "ERROR: active IPE policy has no containerd profile at ${CONTAINERD_PROFILE_SRC}." + exit 1 +fi +if ! mkdir -p "${CONTAINERD_PROFILE_DEST%/*}" || + ! cat "${CONTAINERD_PROFILE_SRC}" > "${CONTAINERD_PROFILE_DEST}"; then + log "ERROR: failed to install the containerd profile at ${CONTAINERD_PROFILE_DEST}." + exit 1 +fi +log "Enabled the EROFS containerd profile at ${CONTAINERD_PROFILE_DEST}." diff --git a/build_library/rpm/additional_files/dracut-acl-ipe-load/module-setup.sh b/build_library/rpm/additional_files/dracut-acl-ipe-load/module-setup.sh index 2946abe3814..ff577b5d937 100644 --- a/build_library/rpm/additional_files/dracut-acl-ipe-load/module-setup.sh +++ b/build_library/rpm/additional_files/dracut-acl-ipe-load/module-setup.sh @@ -13,6 +13,9 @@ install() { inst_script "${moddir}/acl-ipe-load.sh" \ "/usr/bin/acl-ipe-load" + inst_simple "${moddir}/containerd-erofs-profile.conf" \ + "/usr/lib/acl/containerd-erofs-profile.conf" + inst_simple "${moddir}/acl-ipe-load.service" \ "${systemdsystemunitdir}/acl-ipe-load.service" diff --git a/build_library/rpm/dracut_install.sh b/build_library/rpm/dracut_install.sh index 5ce8ac22d08..83da2f410ab 100644 --- a/build_library/rpm/dracut_install.sh +++ b/build_library/rpm/dracut_install.sh @@ -323,6 +323,8 @@ SETUP_EOF "${ipe_load_module}/acl-ipe-load.sh" sudo cp "${BUILD_LIBRARY_DIR}/rpm/additional_files/dracut-acl-ipe-load/acl-ipe-load.service" \ "${ipe_load_module}/acl-ipe-load.service" + sudo cp "${BUILD_LIBRARY_DIR}/rpm/additional_files/containerd2/containerd-acl-profile.conf" \ + "${ipe_load_module}/containerd-erofs-profile.conf" sudo cp "${BUILD_LIBRARY_DIR}/rpm/additional_files/acl-node-security-profile.sh" \ "${ipe_load_module}/acl-node-security-profile.sh" sudo chmod +x "${ipe_load_module}/module-setup.sh" diff --git a/build_library/rpm/sysext_mangle_containerd-flatcar.sh b/build_library/rpm/sysext_mangle_containerd-flatcar.sh index 3f18c9fc002..13fe17e2169 100755 --- a/build_library/rpm/sysext_mangle_containerd-flatcar.sh +++ b/build_library/rpm/sysext_mangle_containerd-flatcar.sh @@ -21,6 +21,7 @@ acl_cgroupfs_config="${rootfs}/usr/share/containerd/config-cgroupfs.toml" unit="${rootfs}/usr/lib/systemd/system/containerd.service" dropin_dir="${rootfs}/usr/lib/systemd/system/containerd.service.d" wants_dir="${rootfs}/usr/lib/systemd/system/multi-user.target.wants" +profile_dir="$(dirname "${BASH_SOURCE[0]}")/additional_files/containerd2" if [[ ! -f "${unit}" ]]; then echo ">>> ERROR: $0: Azure Linux containerd unit not found at ${unit}" >&2 @@ -55,6 +56,12 @@ sed -E 's/^([[:space:]]*)SystemdCgroup[[:space:]]*=.*/\1SystemdCgroup = false/' "${acl_config}" > "${acl_cgroupfs_config}" chmod 0644 "${acl_cgroupfs_config}" +# Add the EROFS containerd configuration. The IPE loader activates it at boot. +install -Dpm 0644 "${profile_dir}/containerd-acl-erofs.toml" \ + "${rootfs}/usr/share/containerd/acl-erofs.toml" +install -Dpm 0755 "${profile_dir}/containerd-acl-select-profile" \ + "${rootfs}/usr/libexec/containerd/acl-select-profile" + # The RPM enables the unit from %post, which does not run when the payload is # unpacked into a sysext, so create the enablement symlink here. echo ">>> NOTICE: $0: enabling containerd.service"