Ordinary Unix permissions let a process do whatever its user can do. If a web server running as nginx is compromised, it can read anything nginx can read. Mandatory access control (MAC) adds a second, stricter layer: a policy that says what each program may touch, whatever the user. Red Hat systems use SELinux; Ubuntu uses AppArmor.
Versions in use on this date. SELinux with the targeted policy on CentOS 7.7, and AppArmor 2.12 on Ubuntu 18.04.4 LTS (released 6 February 2020). Both are enabled by default on their distributions.
Two layers of checks
flowchart TB
P[Process nginx reads /srv/site/index.html] --> DAC{Unix permissions allow?}
DAC -->|no| D1[Permission denied]
DAC -->|yes| MAC{MAC policy allows?}
MAC -->|no| D2[Denied and logged<br/>audit log or kernel log]
MAC -->|yes| OK[Access granted]
D2 --> FIX[Fix label or boolean or profile<br/>do not disable MAC]
A file can have correct rwx bits and still be denied by MAC. If ls -l looks right but access fails, look at MAC next.
SELinux on CentOS
SELinux labels every file, process and port with a context (user:role:type:level). The policy allows a process type such as httpd_t to access only certain file types such as httpd_sys_content_t.
getenforce # Enforcing, Permissive or Disabled
sestatus
ls -Z /var/www/html # file labels
ps -eZ | grep nginx # process labels
ss -tlnZ # sockets with labels
Modes: Enforcing blocks and logs; Permissive only logs (good for diagnosis); Disabled is off and requires a reboot to re-enable and a relabel afterwards. sudo setenforce 0 switches to permissive until reboot. Do not leave a production server permissive “because it was easier”.
Typical fixes
# 1. Files in the wrong place carry the wrong label (moved with mv from /home)
sudo semanage fcontext -a -t httpd_sys_content_t "/srv/site(/.*)?"
sudo restorecon -Rv /srv/site
# 2. A service on a non-standard port
sudo semanage port -a -t http_port_t -p tcp 8081
sudo semanage port -l | grep http_port_t
# 3. A feature controlled by a boolean
getsebool -a | grep httpd
sudo setsebool -P httpd_can_network_connect on # nginx/Apache connecting to backends
sudo setsebool -P httpd_can_network_connect_db on
-P makes the change permanent. semanage comes from policycoreutils-python (sudo yum install policycoreutils-python setroubleshoot-server).
Read the denials
sudo ausearch -m avc -ts recent
sudo grep denied /var/log/audit/audit.log | tail
sudo ausearch -m avc -ts recent | audit2why
sudo sealert -a /var/log/audit/audit.log # human-readable advice (setroubleshoot)
audit2why explains whether a boolean or a label fixes it. Generate a custom module (audit2allow -M mymodule) only when you understand exactly what you are allowing.
AppArmor on Ubuntu
AppArmor confines programs by path-based profiles in /etc/apparmor.d/. Profiles can be in enforce or complain mode (log only).
sudo aa-status # loaded profiles and modes
sudo apt install apparmor-utils apparmor-profiles
sudo aa-complain /usr/sbin/mysqld # learn: log but do not block
sudo aa-enforce /usr/sbin/mysqld
sudo journalctl -k | grep -i apparmor # denials
sudo grep -i 'apparmor="DENIED"' /var/log/syslog | tail
Common case: moving MySQL’s data directory to /data/mysql. The default profile only allows /var/lib/mysql, so MySQL fails with “permission denied” despite correct ownership. Add the path to the local override file:
# /etc/apparmor.d/local/usr.sbin.mysqld
/data/mysql/ r,
/data/mysql/** rwk,
sudo apparmor_parser -r /etc/apparmor.d/usr.sbin.mysqld
sudo systemctl restart mysql
Docker also applies an AppArmor profile (docker-default) to containers on Ubuntu.
Comparing the two
| SELinux | AppArmor | |
|---|---|---|
| Model | Labels on files and processes | Paths in per-program profiles |
| Default on | RHEL, CentOS, Fedora | Ubuntu, SUSE, Debian (optional) |
| Strength | Very fine-grained, harder to learn | Simpler to read and write |
| Diagnose | ausearch, audit2why |
aa-status, dmesg, syslog |
| Learning mode | Permissive | Complain |
Rules of thumb
- Never disable MAC to “fix” an application; find the specific denial.
- Reproduce in permissive or complain mode, read the log, apply the narrowest rule, switch back to enforcing.
- Keep custom rules in version control or configuration management, so rebuilt servers get them.
- Document why each exception exists.