Where to start First steps track · 15 stages
Linux Labs

AppArmor

8 min Ubuntu 24.04 · AppArmor

Mandatory access control on Ubuntu — service profiles, enforce/complain modes.

Cheat sheet

6 commands Printable version
  • sudo aa-status profiles and modes
  • journalctl -k | grep -i 'apparmor.*denied' what is being blocked
  • sudo aa-complain /etc/apparmor.d/usr.sbin.nginx log only
  • sudo aa-logprof extend a profile from the logs
  • sudo aa-enforce /etc/apparmor.d/usr.sbin.nginx block again
  • sudo apparmor_parser -r /etc/apparmor.d/usr.sbin.nginx reload

Overview

AppArmor is Ubuntu's mandatory access control (MAC) system. On top of regular permissions (permissions) it restricts which files/capabilities a specific program may touch, per profile. If a service behaves strangely with "Permission denied" even though the permissions look right, suspect AppArmor.

bash
sudo aa-status                    # profiles and their modes
sudo aa-status --json | jq .      # machine-readable (see jq)

On the lab machine AppArmor is on in the kernel (/sys/module/apparmor/parameters/enabled is Y), 116 profiles are loaded, 22 of them in enforce mode.

Profile and local file

A profile is a file in /etc/apparmor.d/: which program it belongs to and what the program may do. A lab profile for a copy of ls:

text
abi <abi/4.0>,
include <tunables/global>

profile demols /usr/local/bin/demols {
  include <abstractions/base>
  /srv/data/public/** r,
  include if exists <local/demols>
}

abstractions/base is the common set a program cannot start without: libraries, locales, /dev/null. The line include if exists <local/…> pulls in /etc/apparmor.d/local/<name> if it exists. Package profiles include such a file — put your rules there and leave the package's file alone, so an upgrade does not wipe them.

After an edit, load the profile into the kernel again; it applies at once to processes already running:

bash
echo '/srv/data/public/ r,' | sudo tee /etc/apparmor.d/local/demols
sudo apparmor_parser -r /etc/apparmor.d/demols

Path rules: /dir/ and /dir/**

Rule What it allows
/srv/data/public/ r, reading the directory itself — the file list
/srv/data/public/* r, files directly in the directory
/srv/data/public/** r, everything inside the directory at any depth, but not the directory itself

With only the ** rule a file can be read, but the directory cannot be listed:

text
student@ubuntu:~$ demols /srv/data/public/a.txt
/srv/data/public/a.txt
student@ubuntu:~$ demols /srv/data/public
demols: cannot open directory '/srv/data/public': Permission denied

After the line /srv/data/public/ r, in the local file, demols /srv/data/public prints a.txt. The file permissions are a+rX all along — plain ls lists the directory with no error.

The DENIED line

text
audit: type=1400 audit(1792561704.657:122): apparmor="DENIED"
  operation="open" class="file" profile="democat"
  name="/srv/data/secret/b.txt" pid=1337 comm="democat"
  requested_mask="r" denied_mask="r" fsuid=1000 ouid=0
Field Meaning
profile whose profile refused
operation the action: open, exec, mknod, connect
name the path the program asked for
requested_mask, denied_mask what was asked and what was refused: r, w, x, a
comm, pid the process
fsuid, ouid the process's user and the file's owner

A name ending in / is a directory: the /dir/ r, rule is missing.

Who is confined

text
student@ubuntu:~$ ps -eZ | grep demosleep
demosleep (enforce)                1358 ?        00:00:00 demosleep
student@ubuntu:~$ sudo aa-status | grep -A2 'processes are in enforce'
2 processes are in enforce mode.
   /usr/local/bin/demosleep (1358) demosleep
   /usr/sbin/rsyslogd (661) rsyslogd

The LABEL column of ps -eZ shows the profile and mode; unconfined means no profile. In complain mode (aa-complain) nothing is refused, and the log gets apparmor="ALLOWED" instead of DENIED.

Where to find denials

While the auditd service is off (as on the lab machine), denials go to the kernel log: sudo journalctl -k | grep DENIED. A running auditd takes audit messages for itself — new denials go to /var/log/audit/audit.log, and journalctl -k does not have them:

bash
sudo grep 'apparmor="DENIED"' /var/log/audit/audit.log | tail -3
sudo ausearch -m AVC -i | tail -3

ausearch -i adds a SYSCALL record with uid and exe and a PROCTITLE record with the whole command (see auditd).

Recipes

2

Modes and commands

Profiles live in /etc/apparmor.d/. Two modes:

  • enforce — violations are blocked;
  • complain — only logged (for debugging/rollout).
bash
sudo aa-status                                    # overview
sudo aa-complain /etc/apparmor.d/usr.sbin.nginx   # → complain
sudo aa-enforce  /etc/apparmor.d/usr.sbin.nginx   # back to enforce
sudo aa-disable  /etc/apparmor.d/usr.sbin.nginx   # disable the profile
sudo apparmor_parser -r /etc/apparmor.d/profile   # reload
sudo apparmor_parser -R /etc/apparmor.d/profile   # unload a profile
sudo systemctl reload apparmor

The apparmor-utils package provides the aa-* helpers; without it, only apparmor_parser is available.

Profiles and rollout

A profile's structure (simplified): the path to the binary plus access rules (capabilities, paths with r/w/ix), plus #include <abstractions/...> and #include <tunables/...>.

Semi-automatic tuning of a profile against real behavior:

bash
sudo aa-genprof /usr/bin/myapp    # profile from a test run
sudo aa-logprof                   # rules from denial logs

A typical cycle: aa-complain → run the workload → aa-logprof (accept the generated permissions) → aa-enforce.

Troubleshooting

5

"Permission denied" even though the permissions (rwx) look right

AppArmor is blocking it. Check the denials:

bash
sudo journalctl -k | grep -i 'apparmor.*denied'
sudo dmesg | grep -i 'apparmor.*DENIED'

A line like DENIED ... operation="open" ... name="/path" shows what the profile did not allow.

The service works, but cannot access a new directory

The profile does not allow that path. For debugging, use aa-complain, run the workload, then aa-logprof will extend the profile, followed by aa-enforce.

The profile is not reread after an edit

sudo apparmor_parser -r <profile> (or reload apparmor). A syntax error will be shown by the parser.

I want to temporarily disable it for one service

sudo aa-complain <profile> (soft) or aa-disable. Turning AppArmor off entirely in production is not a good idea — it is a real loss of protection.

A snap app and AppArmor

Snap uses its own profiles (confinement); denials are also visible in journalctl -k (see snap snap connections).

Terms

1
AppArmor profile
A file in /etc/apparmor.d listing what a program may do: paths with r, w, x rights and capabilities. In enforce mode the kernel denies the rest and logs apparmor="DENIED"; in complain mode it only logs.

See also

  • permissions — The Unix permission model — read/write/execute, owner/group/other, special bits.
  • journalctl — Reading the systemd journal (journald) — filters by service, time, priority.
  • nginx — Web server and reverse proxy. Config is checked with nginx -t; reload with no downtime.
Loading…