CoursesAdvanced container securityHardening the container

AppArmor & SELinux

Mandatory access control on files and capabilities.

Advanced14 min · lesson 14 of 25

seccomp filters which syscalls a process may make; it says nothing about which files that process may touch. A Linux Security Module — AppArmor on Debian/Ubuntu, SELinux on RHEL/Fedora — adds mandatory access control: a policy the kernel enforces regardless of file permissions or user, confining a container to a declared set of paths, capabilities, and operations. It is the layer that turns “this process can open any file” into “this process can open these files.”

terminal
# Docker applies a default AppArmor profile (docker-default) automatically
$ docker run --rm alpine cat /proc/self/attr/current
docker-default (enforce) # the container is confined by AppArmor
# load and apply a tighter custom profile:
$ sudo apparmor_parser -r -W /etc/apparmor.d/docker-myapp
$ docker run --rm --security-opt apparmor=docker-myapp myapp:1.0

A profile is an allowlist of paths and caps

An AppArmor profile enumerates what the process may do: read these paths, write those, use these capabilities, deny everything else. A tight web-server profile might allow reading its content directory and binding a socket, permit writes only to a cache path, and explicitly deny writing to binary directories or reading /etc/shadow — so even a fully compromised process is boxed into the behavior you declared.

/etc/apparmor.d/docker-myapp
#include <tunables/global>
profile docker-myapp flags=(attach_disconnected,mediate_deleted) {
#include <abstractions/base>
network inet tcp,
/app/** r, # read app files
/var/cache/app/** rw, # one writable path
deny /etc/shadow rwkl, # never touch credentials
deny /bin/** wl, # no writing into binary dirs
deny mount, # no mounting (a common escape primitive)
}

SELinux does the same with labels

On RHEL-family hosts the equivalent is SELinux, which labels every process and file and enforces type-based rules — container_t processes may only touch container_file_t content, so a container simply cannot read host files that carry a different label. The concepts map: AppArmor is path-based, SELinux is label-based, both are mandatory access control the kernel enforces beneath the app. Run a new profile in complain/permissive mode first, tune from the denials, then enforce.

Do not disable the LSM to "make it work"
When an app hits an AppArmor or SELinux denial, the lazy fix is --security-opt apparmor=unconfined or setenforce 0 — which removes a whole defensive layer. The right fix is to read the denial (dmesg / audit log), add the specific path or capability the app legitimately needs, and keep the profile enforcing. Complain mode exists precisely so you can tune without turning protection off.