Skip to content

Powered by Grav

AppArmor confinement profiles

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:

TXT
/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.cnf as its record of the decision (nothing reads that record back), then runs the install path: it installs the auditd, apparmor, apparmor-utils, apparmor-notify and apparmor-profiles packages 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 with apt-get remove, and the grub drop-in is rewritten to apparmor=0. When aa-status is 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.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 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

SH
# 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:
    SH
    grep apparmor /var/log/syslog | grep 'apparmor="DENIED"' | grep php
    
  2. Edit the relevant profile in /etc/apparmor.d/:
    SH
    nano /etc/apparmor.d/opt.php84.sbin.php-fpm
    # Add the path with appropriate permissions:
    #   /custom/path/** rw,
    
  3. 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

SH
# 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.

© 2026 BOA Documentation. All rights reserved.