AppArmor confinement profiles

AppArmor is a Linux Mandatory Access Control (MAC) layer that restricts what each program can do, even if that program runs as root. BOA ships 46 AppArmor profiles for the binaries under its control.

The profiles live in aegir/conf/apparmor/.

AppArmor is off by default. BOA ships the profile files but does not activate them: a default host has AppArmor stopped and removed from boot, and the SSH profiles are deliberately kept disabled. Turning it on is an opt-in step that needs a control file and a reboot — see Lifecycle for exactly what activates it and in which mode.

How it works (briefly)

For each confined binary, an AppArmor profile lists which files/directories it can read, which it can write, which network sockets it can open, and which POSIX capabilities it can use. The kernel enforces these limits. Even if the binary is compromised (via a buffer overflow, etc.) it cannot reach files outside its profile.

The profiles BOA deploys

46 profiles in total: 24 PHP profiles plus 22 others.

PHP — every supported version

opt.php56.bin.php, opt.php70.bin.php, … opt.php85.bin.php (12 versions × CLI binary).

opt.php56.sbin.php-fpm, … opt.php85.sbin.php-fpm (12 versions × FPM binary).

That is 24 PHP profiles spanning the 12 supported versions (56, 70, 71, 72, 73, 74, 80, 81, 82, 83, 84, 85). Each confines its specific PHP binary to:

  • Read access to the relevant /opt/phpNN/ paths.
  • Read/write to /data/disk/oN/sites/ for the tenant.
  • Network sockets only to Percona, Redis/Valkey, Solr.
  • No access to /etc/shadow, /root/, other tenants' homes.

The 12 CLI profiles (opt.phpNN.bin.php) — and only those, not the FPM profiles — additionally carry mrix exec edges for the native files-symlinking tools:

/opt/local/bin/autosymlink mrix,
/usr/local/bin/fix-drupal-site-symlinks.sh mrix,

These edges are designed to let confined PHP CLI (the Provision backend running tasks) exec the hardened sudo wrapper that converts a site's files/ and private/ into per-account static-store symlinks. The exec permission is only half of the wiring: the wrapper itself is deployed 700 root:root to /usr/local/bin/fix-drupal-site-symlinks.sh, so unprivileged callers reach it through sudo/etc/sudoers.d/fix-drupal-site-symlinks (mode 0440) carries an aegir ALL=NOPASSWD entry plus one per oN account, both maintained idempotently on install/upgrade. See the files-symlinking tools reference for what the wrapper does.

The 22 non-PHP profiles

These cover the SSH/SFTP stack, the database, the web server, the resolver, and the supporting daemons:

  • SSH / SFTP: usr.local.sbin.sshd, usr.local.bin.ssh, usr.bin.mysecureshell, usr.local.sbin.pure-ftpd, usr.local.bin.lshell. Note: the two SSH profiles (usr.local.sbin.sshd, usr.local.bin.ssh) ship but are copied into /etc/apparmor.d/disable/ at deploy, so they never load — sshd is intentionally left unconfined (see Lifecycle).
  • Database: usr.sbin.mysqld, usr.bin.mysqld_safe, usr.bin.mysql.
  • Web / cache / DNS: usr.sbin.nginx, usr.bin.redis-server, usr.bin.valkey-server, usr.sbin.unbound.
  • System daemons / utilities: usr.sbin.rsyslogd, usr.bin.freshclam, usr.sbin.clamd, usr.bin.newrelic-daemon, usr.bin.node, usr.local.bin.wkhtmltopdf, usr.local.bin.wkhtmltoimage, usr.bin.man, usr.bin.chromium, sbin.dhclient.

The full list is on disk under aegir/conf/apparmor/.

Lifecycle

barracuda install and barracuda upgrade sync the profile files into place — they copy aegir/conf/apparmor/* to /etc/apparmor.d/ and normalise them to mode 644. Syncing the files is not the same as activating them: whether any profile is actually loaded and enforced depends on the control files below.

Two things are done unconditionally at sync time, regardless of activation state:

  • The distro's own usr.sbin.sshd profile (and a set of unrelated distro profiles — avahi, dnsmasq, dovecot, samba, …) are moved out to /var/backups/apparmor/, so they cannot load.
  • If /etc/apparmor.d/disable/ exists, every *ssh* profile — including BOA's own usr.local.sbin.sshd and usr.local.bin.ssh — is copied into that disable/ directory. BOA deliberately keeps the SSH/SFTP profiles disabled: the profile files ship in aegir/conf/apparmor/, but sshd is intentionally left unconfined.

Activation is off by default and requires a control file + reboot

AppArmor is not active by default, and no profile is enforced by default. With no control file present (the shipped default), BOA tears AppArmor down on every deploy: it runs aa-complain on the profile set, then service apparmor stop, update-rc.d -f apparmor remove, and aa-teardown. The mode is chosen entirely by which control file exists under /root/:

Control file Result
(none — the default) AppArmor torn down: stopped, removed from boot, aa-teardown
/root/.activate.apparmor.cnf Profiles loaded in complain mode (aa-complain — violations logged, not blocked)
/root/.enforce.apparmor.cnf Profiles loaded in enforce mode (aa-enforce — violations blocked + logged)
/root/.disable.apparmor.cnf Same as the default — torn down

Activation also needs kernel support. BOA writes a grub drop-in (/etc/default/grub.d/apparmor.cfg) adding apparmor=1 security=apparmor to the kernel command line, but that only takes effect after a reboot — until the host is rebooted with that flag, AppArmor stays off no matter which control file is set. Use boa reboot for an optimised reboot.

Checking AppArmor status

# List loaded profiles
aa-status

# Watch profile violations in real time
tail -f /var/log/syslog | grep apparmor
tail -f /var/log/audit/audit.log | grep apparmor

# Check confinement mode for a process
cat /proc/<pid>/attr/current

aa-status output shows enforce mode (violations blocked + logged) and complain mode (violations only logged). On a default BOA host aa-status may not even be reporting loaded profiles — AppArmor is off unless a control file activates it (see Lifecycle above). When activated, the mode follows the control file: .enforce.apparmor.cnf gives enforce mode, .activate.apparmor.cnf gives complain mode.

When AppArmor blocks something legitimate

Symptoms: a binary fails to do something it should be able to do, and syslog shows apparmor=DENIED lines for that binary.

Common BOA scenario: an operator installs a non-BOA-managed PHP extension and PHP-FPM is denied the access it needs (e.g. write to a non-standard path).

Workaround:

  1. Identify the specific denied access:
    grep apparmor /var/log/syslog | grep 'apparmor="DENIED"' | grep php
  2. Edit the relevant profile in /etc/apparmor.d/:
    nano /etc/apparmor.d/opt.php84.sbin.php-fpm
    # Add the path with appropriate permissions:
    #   /custom/path/** rw,
  3. Reload:
    apparmor_parser -r /etc/apparmor.d/opt.php84.sbin.php-fpm

Caveat: barracuda upgrade overwrites these files. To make changes survive upgrades, either file a PR against omega8cc/boa to add the path upstream (preferred for BOA-relevant patterns), or keep a local patch that re-applies after each upgrade (a custom-init script in /etc/rc.local or similar).

Temporarily disabling a profile

# Put profile in complain mode (logs but doesn't block)
aa-complain /etc/apparmor.d/opt.php84.sbin.php-fpm

# Disable entirely
aa-disable /etc/apparmor.d/opt.php84.sbin.php-fpm

# Re-enable
aa-enforce /etc/apparmor.d/opt.php84.sbin.php-fpm

Use complain mode while debugging a denial. Do not run BOA production hosts with AppArmor disabled.

What AppArmor adds on top of other layers

When activated, AppArmor is the last-line defence. By the time a request reaches a confined binary, SYNPROXY did not block it, CSF allowed the IP, Nginx accepted the HTTP request, and PHP-FPM started executing PHP. If that PHP code attempts something malicious (e.g. read /etc/shadow), AppArmor stops it. Without AppArmor, the only thing stopping the attack would be Linux file permissions — and a misconfigured host could let PHP-FPM running as www-data read sensitive files.

This layer only applies once AppArmor has been switched on. Because BOA ships with it off (see Lifecycle), a default host does not get this confinement until an operator sets .enforce.apparmor.cnf and reboots.

Related