Permissions in Linux answer the question "who may read this file". AppArmor answers a different one: "what is this program allowed to do at all". The difference shows during a break-in. A web shell runs as the web server's user and inherits all of its rights — which is read access to a good half of the system. An AppArmor profile restricts the process to the list of things it needs for its work, and an attempt to read /etc/shadow or to start /bin/bash ends in a refusal regardless of the user's permissions.

What is already enabled

sudo aa-status

The output answers three questions: how many profiles are loaded, how many of them are in enforce and complain mode, and which processes run with no profile at all. On a typical Ubuntu there will be a few dozen profiles, but nearly all of them are for supporting programs such as man and tcpdump. The key services — nginx, Apache, PHP-FPM — are usually absent from the confined list.

Three modes worth telling apart:

  • enforce — the rules are applied, anything extra is denied;
  • complain — violations are only recorded in the journal, nothing is blocked. The mode for tuning;
  • unconfined — there is no profile, the program runs without restrictions.

The most useful part of aa-status output is the last one: processes that listen on the network and are confined by nothing. That is the list to start with.

The tools

sudo apt install apparmor-utils

Without that package the commands aa-complain, aa-enforce and aa-logprof are not in the system, although AppArmor itself is running.

How to enable a profile without stopping the service

The order is fundamental. A profile switched straight into enforce will, with high probability, deny the service something it needs, and it will stop working — usually not immediately, but on some rare operation such as a file upload or sending mail.

The correct sequence:

sudo aa-complain /etc/apparmor.d/usr.sbin.nginx

A week of work in that mode, covering every unusual scenario: taking a backup, an update, uploading large files. Then go through what accumulated:

sudo aa-logprof

The command walks through the recorded violations and asks about each one whether to allow it. Attention is required here: allow what genuinely belongs to the service's work, not everything in sight — otherwise you end up with a profile that permits everything, and the point is lost.

And only then:

sudo aa-enforce /etc/apparmor.d/usr.sbin.nginx

Where to see the denials

All violations end up in the kernel journal:

sudo journalctl -k | grep -i apparmor
sudo grep 'apparmor="DENIED"' /var/log/audit/audit.log

The second file exists if auditd is installed — and with it the records are noticeably more detailed. A denial record shows the profile name, the requested operation and the path being accessed; that is enough to tell whether to fix the profile or be glad it fired.

One symptom is worth memorising: a service behaves strangely — it will not read a config, will not write into a directory, will not open a socket — while its own logs show a "permission denied" error with permissions that are plainly correct. That is almost always AppArmor, and the first thing to do is look in the kernel journal.

A practical measure, not a theoretical one

A profile for PHP-FPM or nginx pays off in a specific way. With the process confined, a web shell that reaches the site can neither read system files, nor start a shell, nor write anything outside the permitted directories. A hacked site stays a hacked site instead of turning into access to the whole machine — and it is that transition which accounts for the bulk of the damage.

A sensible order of adoption: first a profile for whatever faces the internet (the web server, PHP-FPM), then for the databases, then as needed. There is no need to enable everything at once, and no universal ready-made set exists: a profile depends on where your files live.

What to check regularly

Profiles fall back to unconfined more often than you would think: a package update can replace a profile file, manual debugging leaves a service in complain, and a new service arrives with no profile at all. Two figures are useful: how many profiles are in enforce, and how many network-facing processes are unconfined. A change in either is worth knowing about. What that looks like collected on one page is on the demo below.