Use AppArmor
Every non-privileged container on a Syself Linux node already runs under an enforcing AppArmor profile. You do not turn this on.
AppArmor is a Linux kernel security module for mandatory access control (MAC). MAC means the kernel enforces a policy on a process no matter which user it runs as: even a root process cannot do what its profile forbids. That is exactly the property container isolation needs, because a process that breaks out of its container usually acts as root on the host, and normal file permissions no longer stop it. The profile is applied to every container rather than only the ones that request it: an opt-in control protects only the workloads whose authors already thought about the problem.
AppArmor runs on every node#
The Syself Linux kernel runs AppArmor as its mandatory access control system, alongside landlock, lockdown, and yama in the kernel's security-module stack.
SELinux is compiled out of the kernel. The two systems solve the same problem in different ways: SELinux attaches security labels to every file on disk, AppArmor attaches profiles to programs by path. On a sealed OS built from read-only dm-verity images and replaced on every update, there is no place for SELinux label management and nothing it would add, because container confinement comes from the runtime's profile either way. Shipping one MAC and removing the other also removes code that would otherwise sit unused. See Node and OS security for the rest of the kernel hardening.
For you this means AppArmor profiles work on every pod, and SELinux settings in a pod spec do nothing (details below).
What the default profile does#
containerd loads its AppArmor profile (cri-containerd.apparmor.d) when the node starts and applies it, in enforce mode, to every container that does not request something else. The profile blocks operations a normal application never performs but a container breakout does:
- Mounting filesystems inside the container
- Writing to most of
/procand to/sys - Tracing processes outside the container's own profile
This is a safety net, not a full sandbox. It stops well-known breakout techniques while still working with ordinary workloads, which is why it can be on by default. The seccomp RuntimeDefault profile is on by default too (the kubelet sets seccompDefault: true), and the controls you add per workload (dropped capabilities, non-root, read-only root) narrow the attack surface much further.
Two kinds of pods run without it:
- Privileged containers (
privileged: true) run unconfined. Privileged means full host access, so there is nothing left for a profile to protect. - Pods that request
Unconfined(below).
Verify it from any running pod:
$ kubectl exec <pod> -- cat /proc/1/attr/current
# Expected: cri-containerd.apparmor.d (enforce)
Set the profile per pod#
The appArmorProfile field in the security context controls the profile. It arrived in Kubernetes 1.30 and is GA since 1.31. Set it at the pod level to cover all containers, or on one container to override the pod setting:
apiVersion: v1
kind: Pod
metadata:
name: hello-apparmor
spec:
securityContext:
appArmorProfile:
type: RuntimeDefault
containers:
- name: app
image: your/image:tag
The types:
RuntimeDefault: containerd's profile, described above. Same as leaving the field unset on this platform. Set it anyway in manifests you ship to other clusters, because it turns "whatever the node does" into a declared guarantee.Localhost: a custom profile already loaded on the node, named in thelocalhostProfilefield. See the next section.Unconfined: no AppArmor confinement. Use it only to check whether a denial comes from AppArmor. A namespace at thebaselineorrestrictedPod Security level rejects it.
Older manifests use the container.apparmor.security.beta.kubernetes.io/<container> annotation. It still works but is deprecated since 1.30. Migrate to the field; when both are present they must agree.
Custom profiles (Localhost)#
A Localhost profile must already be loaded into the kernel of every node the pod can be scheduled on, or the container fails to start. On this platform the kubelet rejects a pod that names a missing profile, so a typo never leaves a workload silently unconfined.
The node ships apparmor_parser, the tool that loads a profile, but the sealed OS has no writable profile directory and no package manager. Load custom profiles with a privileged DaemonSet that runs apparmor_parser against profiles you mount from a ConfigMap. Nodes are replaced, not patched, so anything loaded at runtime disappears with the node; the DaemonSet model handles that, because it runs on every new node before your workloads land there.
Most workloads do not need a custom profile. Use one only when you must confine a specific application tighter than RuntimeDefault and a seccomp profile plus dropped capabilities is not enough.
SELinux is not active#
SELinux is compiled out of the Syself Linux kernel. The Kubernetes API still accepts SELinux fields, so nothing breaks at apply time, but they have no effect on the node:
securityContext.seLinuxOptionsis accepted and ignored.- SELinux-based volume relabeling does not happen. Volume access control comes from
fsGroupand file permissions. - Helm charts written for SELinux-based platforms run unchanged; their SELinux settings are no-ops.
This is a deliberate choice, not a gap. No compliance framework mandates SELinux by name: CIS asks for "SELinux or AppArmor", the Ubuntu STIG requires AppArmor, and managed Kubernetes services that use AppArmor nodes hold FedRAMP High and DoD IL5. If a requirement asks for mandatory access control, the enforced AppArmor profile satisfies it.
If you relied on SELinux multi-category isolation to separate hostile tenants, use the stronger tools this platform has instead: a user namespace (hostUsers: false) to strip host privileges, or the secure RuntimeClass to move the pod into its own VM. See Workload runtimes and isolation . If you have a contractual need for enforcing SELinux specifically, Syself can build a SELinux-enforcing node-image variant per nodepool; it is not something you toggle in a manifest. Contact support.
The full hardening stack#
AppArmor is one layer. The platform turns on the first two for you; the rest you set per workload. For each control, what it protects against:
| Control | You set | Protects against |
|---|---|---|
AppArmor RuntimeDefault | Nothing (on by default) | Known breakout techniques: mounts, /proc and /sys writes, cross-profile ptrace |
Seccomp RuntimeDefault | Nothing (on by default via seccompDefault) | Exploits that need rare system calls; around 44 dangerous syscalls are blocked |
restricted Pod Security level | One label per namespace | Anyone deploying a pod without the controls below |
runAsNonRoot + drop ALL capabilities | Security context | A compromised process holding root or kernel privileges |
readOnlyRootFilesystem | Security context | Malware persisting inside the container image |
User namespace (hostUsers: false) | Pod spec | An escape landing as host root |
secure RuntimeClass (Kata) | Pod spec, bare metal only | An escape reaching the shared host kernel at all |
The node's own hardening (sealed dm-verity layers, signed-modules-only, the hardened kernel build) protects the OS from the outside and from a compromised workload. The stack above protects your workloads from each other and shrinks what a compromised container can even attempt. Both are needed; neither replaces the other.
A pod spec that passes the restricted level with every layer declared:
apiVersion: v1
kind: Pod
metadata:
name: hardened-app
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
appArmorProfile:
type: RuntimeDefault
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: your/image:tag
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
Roll the namespace label out audit-first before enforcing; see Enforce Pod Security Standards . For what each security-context field does, see Set security contexts .
Related#
Set security contexts
How to set pod and container security contexts to limit what running containers can do, covering non-root users, privilege escalation, capabilities, and seccomp.
Enforce Pod Security Standards
Roll out Pod Security Standards on a Kubernetes namespace without breaking running workloads: audit first, then enforce the baseline or restricted level.