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.sshdprofile (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 ownusr.local.sbin.sshdandusr.local.bin.ssh— is copied into thatdisable/directory. BOA deliberately keeps the SSH/SFTP profiles disabled: the profile files ship inaegir/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:
- Identify the specific denied access:
grep apparmor /var/log/syslog | grep 'apparmor="DENIED"' | grep php - 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, - 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
- Security model — the broader model AppArmor sits in.
- CSF + LFD firewall, SYNPROXY DDoS protection — the network-edge layers AppArmor sits behind.
- SSH + SFTP hardening — SSH server-side config. Note the
usr.local.sbin.sshdprofile ships but is deliberately kept disabled (copied into/etc/apparmor.d/disable/at deploy), so sshd is not confined by AppArmor. - PHP-FPM performance — the PHP-FPM pools confined by the per-version AppArmor profiles.