Happy Dussehra! May good always win. Happy Diwali! Wishing you a bright year ahead. Merry Christmas! Warm wishes from Vizag. Happy New Year! Here’s to a great year ahead.

Archive · Linux

SELinux and AppArmor in Plain Terms

How mandatory access control adds a second layer behind file permissions, how to diagnose denials on CentOS and Ubuntu, and fix them without turning protection off.

3 min read

  • CentOS 7.7
  • SELinux
  • Ubuntu 18.04.4 LTS
  • AppArmor 2.12
  • audit2why

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

  1. Never disable MAC to “fix” an application; find the specific denial.
  2. Reproduce in permissive or complain mode, read the log, apply the narrowest rule, switch back to enforcing.
  3. Keep custom rules in version control or configuration management, so rebuilt servers get them.
  4. Document why each exception exists.