SELinux & AppArmor: Confining Linux with MAC
Add Mandatory Access Control to Linux with SELinux and AppArmor. Keep SELinux enforcing, read denials, and fix policy instead of disabling it.
Standard Linux permissions are discretionary: the owner of a file decides who may read or write it, and a compromised process running as a user inherits everything that user can touch. Mandatory Access Control (MAC) adds a second, system-wide policy layer that even root cannot casually override, so a breached web server stays boxed into exactly what its policy allows. This article covers the two production implementations — SELinux and AppArmor — and how to keep them enforcing rather than turning them off.
What MAC Adds Over DAC
Discretionary Access Control (DAC) — the classic rwx bits and ownership covered in filesystem permissions — is decided per object by its owner. MAC instead enforces a central policy the administrator defines, independent of file ownership. If an attacker hijacks nginx, DAC lets that process read anything the nginx user can read; MAC additionally requires the action to match the policy for the httpd_t domain (SELinux) or the /usr/sbin/nginx profile (AppArmor). The policy wins, so confinement holds even against a root-level exploit in the confined service.
Two implementations dominate. SELinux (RHEL, Fedora, CentOS, Amazon Linux) labels every process and file with a security context and enforces type enforcement between them. AppArmor (Ubuntu, Debian, SUSE) attaches path-based profiles to individual binaries. Pick the one native to your distribution rather than swapping it out.
SELinux: Keep It Enforcing
Check the current state first:
getenforce # Enforcing, Permissive, or Disabled
sestatus # full status, policy, and mount info
Make enforcing permanent in the config, then activate it live without a reboot:
# /etc/selinux/config
SELINUX=enforcing
SELINUXTYPE=targeted
setenforce 1 # switch to enforcing now
Never "fix" a broken service with setenforce 0 or by setting SELINUX=permissive. That disables protection globally. Instead, read the denial and adjust policy. SELinux denials appear as AVC records in the audit log:
ausearch -m AVC,USER_AVC -ts recent
sealert -a /var/log/audit/audit.log # human-readable analysis
Most legitimate denials are solved one of three ways. Toggle a boolean if the behavior is policy-supported:
getsebool -a | grep httpd
setsebool -P httpd_can_network_connect on # -P persists across reboots
Fix a mislabeled file with the right context:
semanage fcontext -a -t httpd_sys_content_t "/srv/web(/.*)?"
restorecon -Rv /srv/web
Only as a last resort, generate a targeted policy module from the actual denials:
ausearch -m AVC -ts recent | audit2allow -M mymodule
semodule -i mymodule.pp
Always review what audit2allow proposes before loading it — blindly allowing every denial defeats the point of MAC.
AppArmor: Profiles and Modes
AppArmor confines binaries through profiles in /etc/apparmor.d/. Check status:
aa-status # loaded profiles, enforce vs complain counts
Each profile runs in enforce mode (violations blocked and logged) or complain mode (violations logged only). Move a profile between modes:
aa-complain /etc/apparmor.d/usr.sbin.nginx # log only, while tuning
aa-enforce /etc/apparmor.d/usr.sbin.nginx # block once tuned
Run the service in complain mode, exercise it, then refine the profile interactively from the logged events:
aa-logprof # walk through events, accept/deny each rule
Install the extra community profiles to cover more daemons out of the box:
apt install apparmor-profiles apparmor-profiles-extra apparmor-utils
As with SELinux, the rule is the same: do not unload a profile to make an app work. Switch it to complain, capture what it needs, encode that with aa-logprof, and re-enforce.
Operational Guidance
Treat a denial as a signal, not a nuisance. Forward AVC and AppArmor events to your pipeline as described in logging and auditing so confinement failures are visible. When you confine systemd services, MAC composes with sandboxing directives for defense in depth. Most security baselines in compliance benchmarks explicitly require MAC to be installed and enforcing — disabling it is a guaranteed audit finding.
Verify
Confirm MAC is active and enforcing:
# SELinux
getenforce # must print: Enforcing
grep '^SELINUX=' /etc/selinux/config # must be: SELINUX=enforcing
# AppArmor
aa-status --enforced # count of profiles in enforce mode
systemctl is-active apparmor # active
If getenforce returns Permissive or Disabled, or AppArmor reports zero enforced profiles, your MAC layer is not protecting the system — fix the policy, do not lower the mode.