AppArmor: write the profile, load it, attach it
What AppArmor gives you
A per-program mandatory access control profile: path-based rules for file access,
network, capabilities and mount. It is loaded on the node (the kernel does the
enforcing) and attached to a container through the Pod spec.
The task, end to end
"Block network access for the container in Pod web, using an AppArmor profile
named deny-network."
1. Write the profile on the node
/etc/apparmor.d/deny-network:
#include <tunables/global>
profile deny-network flags=(attach_disconnected,mediate_deleted) {
#include <abstractions/base>
network,
deny /** w,
deny /tmp/** rwklmx,
}
For "no network at all" the profile simply omits the network, rule and the
container cannot create sockets. A profile that allows everything and denies one
path is the usual exam variant:
profile deny-write {
#include <tunables/global>
#include <abstractions/base>
file,
deny /tmp/** w,
}
2. Load it, on the node that runs the Pod
sudo apparmor_parser -r /etc/apparmor.d/deny-network
sudo aa-status | grep deny-network
# or: cat /sys/kernel/security/apparmor/profiles | grep deny-network
AppArmor requires the profile to be loaded on every node where the Pod can
land. If the task pins the Pod with nodeName or a nodeSelector, load it there.
3. Attach it — modern field first
apiVersion: v1
kind: Pod
metadata: {name: web, namespace: prod}
spec:
containers:
- name: web
image: nginx:1.27
securityContext:
appArmorProfile:
type: Localhost
localhostProfile: deny-network # file name in /etc/apparmor.d
type is RuntimeDefault, Localhost or Unconfined. GA since 1.31, so at
v1.35 this is the field the marker reads.
3b. The legacy form, still accepted
metadata:
annotations:
container.apparmor.security.beta.kubernetes.io/web: localhost/deny-network
The annotation is per container name. It still works but is deprecated —
use it only if the task explicitly shows it.
4. Prove it
kubectl exec -n prod web -- cat /proc/self/attr/current
# deny-network (enforce)
kubectl exec -n prod web -- sh -c 'echo hi > /tmp/x' # Permission denied
/proc/self/attr/current is the ground truth: it prints the profile actually
applied to that process. If it says docker-default (enforce) or unconfined,
your profile did not attach.
Traps
- Attaching a profile that is not loaded → the container fails to start with
apparmor: profile not found. - Attaching on the wrong node: the Pod runs elsewhere and the profile is absent.
localhostProfileis the profile name (file name in/etc/apparmor.d), not a path.- Editing
/etc/apparmor.d/*withoutapparmor_parserdoes nothing — the kernel has the old copy. aa-statusshows whether AppArmor is enabled at all; on a kernel booted withapparmor=0nothing you do will apply.