Simplicity over Granularity
AppArmor (Application Armor) is the default LSM on Ubuntu, Debian, and SUSE systems. It was designed with a different philosophy than SELinux: instead of labeling every object in the filesystem, AppArmor defines security profiles per application using filesystem paths.
An AppArmor profile is a plain text file stored in /etc/apparmor.d/, named after the absolute path of the binary it confines. A profile for Nginx, for example, would list the paths the nginx binary is allowed to touch — its own binary and shared libraries, the web content directory as read-only, its log directory as write-only, its config directory as read-only, its PID file as read-write — plus a line permitting it to bind TCP ports. The permission letters after each path follow a simple scheme: r (read), w (write), x (execute), m (memory map), k (lock), l (link). This is far more readable than SELinux type enforcement rules for most administrators. Anything not explicitly listed in the profile is denied by default.
AppArmor modes: aa-status reports which profiles are loaded and whether each is in enforce or complain mode. aa-enforce puts a profile into enforce mode (violations are blocked); aa-complain puts it into complain mode (violations are logged but allowed — the AppArmor equivalent of SELinux's Permissive mode, useful for testing a new profile before trusting it). Editing a profile requires reloading it via apparmor_parser for the change to take effect.
The critical trade-off: AppArmor's path-based model is easier to write and understand, but it has a fundamental weakness — if a binary is moved or symlinked to a different path, the profile no longer applies. SELinux labels are attached to the inode, so they survive renames and moves. In environments where file paths are stable and predictable (most production servers), this rarely matters. In environments with complex symlink structures or frequently relocated binaries, SELinux's label model is more robust.
AppArmor also cannot confine processes that are already running when a profile is loaded — the profile only applies to new executions of the binary. SELinux applies to all processes with the matching label, regardless of when they started.