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 a default host never
loads them. With no /root/.keep_apparmor_on.cnf master switch present, every
barracuda run takes the removal path instead, and never copies the shipped profiles
into /etc/apparmor.d/ at all. That removal path only does anything when AppArmor
tooling is present on the box — it is skipped entirely when aa-status is not
installed — and then it tears AppArmor down, removes the AppArmor and auditd packages
if they are installed, and rewrites the grub drop-in to apparmor=0.
Turning confinement on is an opt-in step that needs the master switch, a mode control
file and a reboot — see Lifecycle for the exact sequence.
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
The master switch — /root/.keep_apparmor_on.cnf
Nothing in the AppArmor family happens unless both of these hold when a
barracuda install or barracuda upgrade reaches its AppArmor step:
/root/.keep_apparmor_on.cnf exists, and /root/.deny.apparmor.cnf does not. That
single test decides the whole family — the mode markers further down are not even read
when it fails.
- Both conditions met — BOA touches
/root/.allow.apparmor.cnfas its record of the decision (nothing reads that record back), then runs the install path: it installs theauditd,apparmor,apparmor-utils,apparmor-notifyandapparmor-profilespackages if they are missing, writes the grub drop-in, and syncs the profiles. - Either condition failed (the shipped default) — BOA removes
/root/.allow.apparmor.cnf, creates/root/.deny.apparmor.cnf, and runs the removal path: when AppArmor tooling is present on the box, AppArmor is torn down, the AppArmor and auditd packages are removed withapt-get remove, and the grub drop-in is rewritten toapparmor=0. Whenaa-statusis not installed the whole removal body is skipped, grub rewrite included.
On stretch and jessie the install and removal calls are skipped, though the marker
bookkeeping still runs; the install path is also skipped while a distro-hop marker
(/root/.run-to-*.cnf) is in flight.
/root/.deny.apparmor.cnf is created by the removal arm and nothing in BOA ever
removes it, which makes it a one-way latch. On a host that has already run
barracuda without the master switch, creating .keep_apparmor_on.cnf on its own can
no longer switch AppArmor on: delete /root/.deny.apparmor.cnf as well, then run
barracuda upgrade and reboot.
The same marker is read outside barracuda. autoupboa (the system self-update agent,
run every five minutes by clear.sh as well as on the weekly upgrade) and autoinit
(the Debian-to-Devuan converter, which runs before BOA is
installed at all) both tear a live AppArmor down whenever the file is absent, so on a
converted or auto-updated box the switch has to be in place before those tools run. The
file is operator-created — BOA never writes or removes it.
Profiles are synced only on the install path, and only once the kernel itself
reports AppArmor as live (which needs the grub flag plus a reboot — see below). The
sync copies aegir/conf/apparmor/* to /etc/apparmor.d/ and normalises the profiles
to mode 644. Syncing the files is not the same as activating them: which profiles
load, and in which mode, depends on the mode markers 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 needs the master switch, a mode marker and a reboot
AppArmor is not active by default, and no profile is enforced by default. The mode
markers below are read only inside the profile sync — that is, only on a host where
the master switch has enabled the family and the kernel already reports AppArmor as
live. On the shipped default they are never read at all. Once the family is on, a host
with no mode marker gets AppArmor torn down on every deploy: aa-complain on the
profile set, then service apparmor stop, update-rc.d -f apparmor remove,
service auditd stop, update-rc.d -f auditd remove and aa-teardown. The mode is
chosen 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.
auditd log writing — /root/.disable.auditd.logs.cnf
The same profile sync also owns auditd's own logging switch. Whenever
/etc/audit/auditd.conf exists, BOA rewrites its write_logs line on every pass:
| Control file | Resulting write_logs |
|---|---|
/root/.disable.auditd.logs.cnf present |
no |
| (absent — the default) | yes, re-asserted on every pass |
The polarity is worth spelling out, because the absent case is active, not passive.
Deleting the marker does not merely stop suppressing auditd logs, it switches them back
on at the next barracuda upgrade, and a hand-edit of auditd.conf is reverted the
same way on every run. The file is operator-created (BOA never writes or removes it)
and, like everything else in this section, it is read only inside the profile sync — so
it has no effect at all on a host where the master switch leaves the AppArmor family
off.
One edge case: the rewrite only replaces a line that already begins with
write_logs =. If auditd.conf mentions write_logs nowhere, BOA appends
write_logs = yes regardless of the marker.
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:
SH
grep apparmor /var/log/syslog | grep 'apparmor="DENIED"' | grep php - Edit the relevant profile in
/etc/apparmor.d/:SHnano /etc/apparmor.d/opt.php84.sbin.php-fpm # Add the path with appropriate permissions: # /custom/path/** rw, - Reload:
SH
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 creates the /root/.keep_apparmor_on.cnf master switch (with
/root/.deny.apparmor.cnf removed), 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.