From fb564d048618ad5b5d30fe7154044feae33539ca Mon Sep 17 00:00:00 2001 From: Benjamin Leggett Date: Fri, 17 Jul 2026 18:36:33 -0400 Subject: [PATCH 1/3] feat(apparmor): build+pack a default apparmor profile into the zone kernel --- configs/aarch64/zone.config | 2 +- configs/apparmor/edera-default | 51 ++++++++++++++++++++++++++++++++++ configs/x86_64/zone.config | 2 +- hack/build/apparmor.sh | 33 ++++++++++++++++++++++ hack/build/build.sh | 3 ++ 5 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 configs/apparmor/edera-default create mode 100644 hack/build/apparmor.sh diff --git a/configs/aarch64/zone.config b/configs/aarch64/zone.config index 96aefcd..011e4a4 100644 --- a/configs/aarch64/zone.config +++ b/configs/aarch64/zone.config @@ -1379,7 +1379,7 @@ CONFIG_SECURITY_LANDLOCK=y CONFIG_INTEGRITY=y CONFIG_INTEGRITY_AUDIT=y CONFIG_DEFAULT_SECURITY_DAC=y -CONFIG_LSM="landlock,lockdown,yama,loadpin,safesetid,integrity" +CONFIG_LSM="landlock,lockdown,yama,loadpin,safesetid,integrity,apparmor" CONFIG_INIT_STACK_ALL_ZERO=y diff --git a/configs/apparmor/edera-default b/configs/apparmor/edera-default new file mode 100644 index 0000000..56af3fd --- /dev/null +++ b/configs/apparmor/edera-default @@ -0,0 +1,51 @@ +# Edera RuntimeDefault AppArmor profile. +# +# Source: containerd's default container AppArmor profile, from +# contrib/apparmor/template.go at tag v2.3.3 (latest release) +# https://github.com/containerd/containerd/blob/v2.3.3/contrib/apparmor/template.go +# +# This is the policy template that the Edera runtime applies in-zone by default, when instructed +# to load and use the `RuntimeDefault` profile. The AppArmor profile is a binary, built against +# the zone kernel's ABI, compiled from this policy template by the apparmor tooling. +# We ship the default profile with the zone kernel, where it is available to be used within the zone +# for confining processes. + +abi , +#include + +profile edera-default flags=(attach_disconnected,mediate_deleted) { + #include + + network, + capability, + file, + umount, + + # The unconfined supervisor may signal workloads under this profile. + signal (receive) peer=unconfined, + # Processes running under this profile may signal each other (only). + signal (send,receive) peer=edera-default, + + deny @{PROC}/* w, # deny write for all files directly in /proc (not in a subdir) + # deny write to files not in /proc//** or /proc/sys/** + deny @{PROC}/{[^1-9/],[^1-9/][^0-9/],[^1-9s/][^0-9y/][^0-9s/],[^1-9/][^0-9/][^0-9/][^0-9/]*}/** w, + deny @{PROC}/sys/[^k]** w, # deny /proc/sys except /proc/sys/k* (effectively /proc/sys/kernel) + deny @{PROC}/sys/kernel/{?,??,[^s][^h][^m]**} w, # deny everything except shm* in /proc/sys/kernel/ + deny @{PROC}/sysrq-trigger rwklx, + deny @{PROC}/kcore rwklx, + + deny mount, + + deny /sys/[^f]*/** wklx, + deny /sys/f[^s]*/** wklx, + deny /sys/fs/[^c]*/** wklx, + deny /sys/fs/c[^g]*/** wklx, + deny /sys/fs/cg[^r]*/** wklx, + deny /sys/firmware/** rwklx, + deny /sys/devices/virtual/powercap/** rwklx, + deny /sys/kernel/security/** rwklx, + + # allow processes within the container to trace each other, + # provided all other LSM and similar settings allow it. + ptrace (trace,tracedby,read,readby) peer=edera-default, +} diff --git a/configs/x86_64/zone.config b/configs/x86_64/zone.config index 734acff..5379f75 100644 --- a/configs/x86_64/zone.config +++ b/configs/x86_64/zone.config @@ -1752,7 +1752,7 @@ CONFIG_SECURITY_LANDLOCK=y CONFIG_INTEGRITY=y CONFIG_INTEGRITY_AUDIT=y CONFIG_DEFAULT_SECURITY_DAC=y -CONFIG_LSM="landlock,lockdown,yama,loadpin,safesetid,integrity" +CONFIG_LSM="landlock,lockdown,yama,loadpin,safesetid,integrity,apparmor" CONFIG_INIT_STACK_ALL_ZERO=y diff --git a/hack/build/apparmor.sh b/hack/build/apparmor.sh new file mode 100644 index 0000000..620ff81 --- /dev/null +++ b/hack/build/apparmor.sh @@ -0,0 +1,33 @@ +#!/bin/sh +# Compiles the Edera 'RuntimeDefault' AppArmor profile against the zone kernel's ABI, +# and stages it in the addon overlay at apparmor/. +# +# Only zone kernels run workloads; the host kernel has no use for the profile, +# so it is skipped there. +# +# Requires apparmor_parser in the build environment. + +case "${KERNEL_FLAVOR}" in +zone*) + APPARMOR_OUTPUT_PATH="${ADDONS_OUTPUT_PATH}/apparmor" + PROFILE_SRC="${KERNEL_DIR}/configs/apparmor/edera-default" + + mkdir -p "${APPARMOR_OUTPUT_PATH}" + + # Compile to a loadable binary policy without attempting to load it into the current kernel. + # The cache file apparmor_parser writes is what we load into the guest kernel via its `.load` interface. + APPARMOR_CACHE_DIR="$(mktemp -d)" + apparmor_parser --skip-kernel-load --write-cache \ + --cache-loc="${APPARMOR_CACHE_DIR}" "${PROFILE_SRC}" + + # Drop the compiled policy under // + # alongside a shared `.features` file. + COMPILED="$(find "${APPARMOR_CACHE_DIR}" -type f ! -name '.features' | head -n1)" + if [ -z "${COMPILED}" ]; then + echo "ERROR: apparmor_parser produced no compiled policy for ${PROFILE_SRC}" >&2 + exit 1 + fi + cp "${COMPILED}" "${APPARMOR_OUTPUT_PATH}/edera-default" + rm -rf "${APPARMOR_CACHE_DIR}" + ;; +esac diff --git a/hack/build/build.sh b/hack/build/build.sh index 04c8449..dc9f0cc 100755 --- a/hack/build/build.sh +++ b/hack/build/build.sh @@ -30,6 +30,9 @@ mv "${MODULES_INSTALL_PATH}/lib/modules/${KERNEL_MODULES_VER}" "${MODULES_OUTPUT rm -rf "${MODULES_INSTALL_PATH}" [ -L "${MODULES_OUTPUT_PATH}/build" ] && unlink "${MODULES_OUTPUT_PATH}/build" +# shellcheck source-path=SCRIPTDIR source=apparmor.sh +. "${KERNEL_DIR}/hack/build/apparmor.sh" + mksquashfs "${ADDONS_OUTPUT_PATH}" "${ADDONS_SQUASHFS_PATH}" -all-root SQUASH_SIZE=$(stat -c %s "${ADDONS_SQUASHFS_PATH}") From 0ac58cdd49d75f4709c00e7cd8df4727a1b62917 Mon Sep 17 00:00:00 2001 From: Benjamin Leggett Date: Fri, 17 Jul 2026 19:36:27 -0400 Subject: [PATCH 2/3] fix(ci): bump builder sha --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 228f6e7..e3f9021 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,7 +17,7 @@ ADD ${NV_MODULES_TARBALL_URL} /nvidia-modules.tar.gz # reviewed digest bumps - see Dockerfile.buildenv and buildenv.yml. Dependabot # keeps the pin current, with buildenv-diff.yml summarizing the package # changes in each bump PR. -FROM --platform=$BUILDPLATFORM ghcr.io/edera-dev/kernel-buildenv:latest@sha256:5f1111cde2487436b4102ae876c1810c304760a3accbb4ba51ff3f725b7374eb AS buildenv +FROM --platform=$BUILDPLATFORM ghcr.io/edera-dev/kernel-buildenv:latest@sha256:00c0e67a93f8d3e6a1d71b03548c4b1157554e28ff117f2d12a8929335aad938 AS buildenv COPY --chown=build:build . /build USER build WORKDIR /build From 2e0b7f2e6962575b1e45d1418a472c9ba5e9d954 Mon Sep 17 00:00:00 2001 From: Benjamin Leggett Date: Sat, 18 Jul 2026 05:54:49 -0400 Subject: [PATCH 3/3] fix(apparmor): pin ABI --- hack/build/apparmor.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/build/apparmor.sh b/hack/build/apparmor.sh index 620ff81..eea2677 100644 --- a/hack/build/apparmor.sh +++ b/hack/build/apparmor.sh @@ -17,7 +17,7 @@ zone*) # Compile to a loadable binary policy without attempting to load it into the current kernel. # The cache file apparmor_parser writes is what we load into the guest kernel via its `.load` interface. APPARMOR_CACHE_DIR="$(mktemp -d)" - apparmor_parser --skip-kernel-load --write-cache \ + apparmor_parser --skip-kernel-load -M /etc/apparmor.d/abi/3.0 --write-cache \ --cache-loc="${APPARMOR_CACHE_DIR}" "${PROFILE_SRC}" # Drop the compiled policy under //