Skip to content

sec(vm): confine Linux VM sidecar job containers with AppArmor - #145

Draft
luthermonson wants to merge 1 commit into
mainfrom
fix/sidecar-apparmor
Draft

sec(vm): confine Linux VM sidecar job containers with AppArmor#145
luthermonson wants to merge 1 commit into
mainfrom
fix/sidecar-apparmor

Conversation

@luthermonson

Copy link
Copy Markdown
Contributor

Problem

Native Linux hosts (coyotes/sharks) confine job containers with the ephemerd-default AppArmor profile (#124). The Linux VM sidecar that runs Linux jobs on Windows and macOS hosts did not: apparmorOpts fail-opened inside the VM, so Linux jobs ran under seccomp + the reduced capability set + read-only /proc,/sys — but with no AppArmor. Everything else (seccomp, cap-drop incl. CAP_MKNOD, device-cgroup deny, masked/RO proc+sys, mount/PID isolation, egress) already matched; AppArmor was the sole containment-parity gap.

Confirmed on a metal test: inside a real sidecar container Process.ApparmorProfile="", /proc/1/attr/current→EINVAL, /sys/module/apparmor/parameters/enabled=N.

Root cause

The Alpine linux-virt 6.12 kernel ships AppArmor compiled in but disabled at boot — its builtin CONFIG_LSM is landlock,lockdown,yama,loadpin,safesetid,integrity (no apparmor), securityfs is not mounted, and the cmdline lacked the enable flags. The sidecar rootfs also had no apparmor_parser. No kernel rebuild is required (verified via the AppArmor symbols/strings and module params in the embedded vmlinuz).

Fix

  • Kernel cmdline (linuxvm_windows.go + linuxvm_darwin.go): add apparmor=1 security=apparmor. Neither cmdline sets lsm=, so on 6.12 security=apparmor is honored (not superseded) and appends apparmor to the LSM order.
  • Init script (both initrd scripts in mage/download/download.go): mount securityfs at /sys/kernel/security before the daemon starts.
  • Rootfs (download.go): add Alpine apparmor (→ /sbin/apparmor_parser) + libintl (the only lib the parser links), and ship /etc/apparmor.d/abi/3.0 (the ABI the containerd-generated profile's abi <abi/3.0>, include resolves against). The full apparmor-profiles package is intentionally avoided — it would also drop tunables/global + abstractions/base into /etc/apparmor.d and flip containerd's macroExists, pulling glibc-oriented includes into the generated profile. Keeping /etc/apparmor.d minimal keeps the profile self-contained.
  • The ephemerd-default profile itself is generated + loaded in-VM by the existing pkg/runtime/apparmor.go (containerd contrib/apparmor) — no static profile file needed.

Validation (Windows Hyper-V sidecar, real dispatch path)

Booted the sidecar via vm.StartLinuxVM and dispatched a container through runtime.Create:

  • Kernel: LSM: initializing lsm=lockdown,capability,landlock,apparmor, AppArmor initialized, AppArmor Filesystem Enabled.
  • In-VM resolver: job containers are AppArmor confined profile=ephemerd-default mode=enforce (was running WITHOUT AppArmor confinement).
  • Dispatched container OCI spec: Process.ApparmorProfile=ephemerd-default.
  • No regressions: seccomp SCMP_ACT_ERRNO (12 rules), 9 caps with CAP_MKNOD dropped, device-cgroup deny-all, MaskedPaths=11, ReadonlyPaths=5.

Follow-ups

  • The macOS (Apple Vz) sidecar path (linuxvm_darwin.go) gets the same cmdline + shares the init/rootfs changes, but could not be validated on this Windows box — needs a smoke test on a Mac host.
  • Embedded VM assets (ephemerd-linux, vmlinuz, initrd, rootfs) are gitignored/built by mage build, so this PR is source-only; CI must rebuild them.

Native Linux hosts confine job containers with the ephemerd-default
AppArmor profile (#124), but the Linux VM sidecar that runs Linux jobs on
Windows and macOS hosts did not: apparmorOpts fail-opened inside the VM,
so Linux jobs ran under seccomp + the reduced capability set + the
read-only /proc,/sys mounts, but with no AppArmor — a containment-parity
gap versus the native Linux pools.

Three things were missing in the guest; none needs a kernel rebuild (the
Alpine linux-virt kernel has AppArmor compiled in, only disabled at boot):

- Kernel cmdline: add `apparmor=1 security=apparmor` on both the Hyper-V
  (linuxvm_windows.go) and Apple Vz (linuxvm_darwin.go) boots. The
  kernel's builtin CONFIG_LSM is
  landlock,lockdown,yama,loadpin,safesetid,integrity (no apparmor) and
  neither cmdline sets lsm=, so security=apparmor is honored and appends
  apparmor to the LSM order rather than being superseded by it.

- Init script: mount securityfs at /sys/kernel/security before the daemon
  starts, in both initrd init scripts, so the loaded-profile list can be
  read and the profile loaded.

- Rootfs: add the Alpine apparmor (apparmor_parser) + libintl packages and
  ship /etc/apparmor.d/abi/3.0, the feature ABI that the containerd-
  generated profile's `abi <abi/3.0>,` include resolves against. The full
  apparmor-profiles package is deliberately NOT added: it would also
  install tunables/global + abstractions/base, flipping containerd's
  macroExists and pulling glibc-oriented includes into the generated
  profile. Keeping /etc/apparmor.d minimal leaves the profile
  self-contained (apparmor_parser links only libintl, not libapparmor).

Validated on a Windows Hyper-V sidecar: the kernel now boots
lsm=lockdown,capability,landlock,apparmor with "AppArmor initialized";
the in-VM resolver logs "job containers are AppArmor confined
profile=ephemerd-default mode=enforce" (previously "running WITHOUT
AppArmor confinement"); and a container dispatched through the real
runtime.Create path gets an OCI spec with
Process.ApparmorProfile=ephemerd-default while seccomp, the dropped
CAP_MKNOD, the device-cgroup deny-all, and the masked/RO proc,sys mounts
all remain in place.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant