LinuxXXVIII · SELinux and AppArmorAppArmor
AppArmor concepts - path-based MAC for Debian-family systems
What you'll learn
- Describe AppArmor profiles and modes
- Read and write simple profiles
- Use aa-status and aa-complain for diagnosis
- Recognise when AppArmor is the right choice
Prerequisites
Verified against Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL 9.x · Rocky Linux 9.x · AlmaLinux 9.x · Linux kernel 6.1 LTS / 6.6 LTS · systemd 255+ · OpenSSH 8.7p1 (RHEL 9) / 9.6p1 (Ubuntu 24.04) · nftables 1.0.x · chrony 4.x · Pacemaker 2.1.x · Corosync 3.1.x · 2026-08-09
AppArmor is the default MAC on Ubuntu and Debian. It is path-based (uses file paths) rather than label-based (like SELinux). This lesson covers profiles, modes, and daily use.
Profiles
An AppArmor profile is a text file in /etc/apparmor.d/
that defines what a binary can do:
#include <tunables/global>
/usr/sbin/nginx {
#include <abstractions/base>
#include <abstractions/nameservice>
capability dac_override,
capability net_bind_service,
/etc/nginx/** r,
/var/log/nginx/** w,
/var/www/** r,
/run/nginx.pid w,
network inet stream,
network inet6 stream,
deny /etc/shadow r,
deny /root/** rwx,
}
The profile is enforced for the specified binary path.
Modes
| Mode | Behaviour |
|---|---|
enforce | Profile is enforced. Violations are denied and logged. |
complain | Profile is not enforced. Violations are logged only. |
unconfined | No profile. Process runs with DAC only. |
sudo aa-status # all profiles
sudo aa-status --json # machine-readable
cat /sys/kernel/security/apparmor/profiles # kernel view
A profile in complain mode lets you capture the accesses
the application wants without denying them. Useful for
developing a profile.
Switch mode
sudo aa-complain /usr/sbin/nginx # complain mode
sudo aa-enforce /usr/sbin/nginx # enforce mode
sudo aa-disable /usr/sbin/nginx # remove profile (unconfined)
Generate a profile
sudo aa-genprof /usr/sbin/nginx
Runs the program (when you start it manually) and observes
its accesses. You choose which accesses to allow or deny
interactively. Saves the profile to
/etc/apparmor.d/usr.sbin.nginx.
For a service that runs continuously, set it to complain mode, restart the service, observe, then enforce:
sudo aa-complain /usr/sbin/nginx
sudo systemctl restart nginx
# Wait for the service to do all its normal operations
sudo aa-enforce /usr/sbin/nginx
Common profile building blocks
#include <abstractions/base> # base permissions
#include <abstractions/nameservice> # DNS, /etc/nsswitch
#include <abstractions/openssl> # OpenSSL library and config
#include <abstractions/ssl_certs> # the CA bundle under /etc/ssl/certs
#include <abstractions/web-data> # common document-root paths
Abstractions are reusable rule sets. Use them instead of listing every file the application needs.
There is no abstractions/nginx. Abstractions are the set of
files shipped in /etc/apparmor.d/abstractions/, not a naming
convention you can extend by guessing — and an #include of one
that does not exist is a hard parse failure, not a warning:
$ apparmor_parser -Q ./myprofile
AppArmor parser error for ./myprofile in profile ./myprofile at
line 5: Could not open 'abstractions/nginx'
Check what your system actually provides, and validate every profile before loading it:
ls /etc/apparmor.d/abstractions/
sudo apparmor_parser -Q /etc/apparmor.d/usr.sbin.nginx # parse only, loads nothing
Capability rules
capability dac_override, # bypass read/write permissions
capability net_bind_service, # bind to ports < 1024
capability sys_admin, # mount, etc.
capability sys_chroot, # chroot
Capabilities are granular Linux privileges. Each one allows a specific operation.
File rules
/etc/nginx/** r, # recursive read
/var/log/nginx/** w, # recursive write
/run/nginx.pid w, # single file write
deny /etc/shadow r, # explicit deny
Modes: r (read), w (write), a (append), m (map
executable), k (lock), l (link).
Execute is different: bare x is a syntax error. Every exec
rule must carry a qualifier saying which profile the new process
runs under, because that decision is the whole security question.
/usr/bin/helper ix, # inherit: child runs under THIS profile - safest default
/usr/bin/helper Px, # child runs under its own profile; fails if none exists
/usr/bin/helper Cx, # child runs under a child profile defined inline
/usr/bin/helper Ux, # UNCONFINED - drops confinement entirely. Avoid.
Omit the qualifier and the parser refuses the profile:
$ apparmor_parser -Q ./myprofile
AppArmor parser error ... at line 5: Invalid perms, 'x' must be
preceded by exec qualifier 'i', 'p', or 'u'
Network rules
network inet stream, # TCP IPv4
network inet6 stream, # TCP IPv6
network inet dgram, # UDP IPv4
Network rules restrict what protocols and families a process can use.
Audit
sudo journalctl -k | grep audit
sudo ausearch -m appArmor --interpret # human-readable
AppArmor denials appear in the kernel log with audit: and
apparmor="DENIED".
AppArmor vs SELinux
| Feature | AppArmor | SELinux |
|---|---|---|
| Policy by | Path | Label |
| Default on | Debian, Ubuntu | RHEL, Fedora |
| Profile syntax | Easier | Harder |
| Granularity | Path-based | Label-based (more granular) |
| Default policy | Profiles shipped for common services | Targeted policy |
| Tooling | aa-genprof, aa-logprof | audit2allow, semodule |
Both achieve the same goal. The choice is usually the distribution default: AppArmor on Debian-family, SELinux on RHEL-family.
Knowledge check
Knowledge check · 3 questions
Q1. What command switches an AppArmor profile to enforce mode?
Q2. AppArmor profiles are label-based like SELinux.
Q3. Which of the following are AppArmor rule types? Select all that apply.
Passing score: 75%. Answers are checked in this browser.