SELinux and AppArmor: Mandatory Access Control
Standard Linux permissions (rwx for owner/group/other) are discretionary — the file owner decides who else can read or write. That’s fine for most things. But when you run a process that handles untrusted input — a web server, a parser, a daemon facing the internet — you want a SECOND layer of defense that the process itself can’t change. That’s Mandatory Access Control (MAC). Linux has two: SELinux (Red Hat family) and AppArmor (Debian/Ubuntu/SUSE).
The mental model
Both work the same way: a policy says “process X can read file Y, but cannot write file Z, cannot bind to port 8000, cannot exec /bin/sh.” Even if the process is exploited and tries to do those things, the kernel blocks it.
This is why a vulnerable nginx with SELinux enforcing will fail to read /etc/shadow even if exploited — it doesn’t have the SELinux label that would allow it.
SELinux (RHEL, Fedora, Rocky, Alma)
Modes
# Show current mode
getenforce
# Enforcing | Permissive | Disabled
# Status with detail
sestatus
# Switch modes
sudo setenforce 1 # enforcing (real)
sudo setenforce 0 # permissive (logs only, doesn't block)
# Permanent change: edit /etc/selinux/config
SELINUX=enforcing
SELinux contexts
Every file and process has a security context. View with -Z:
ls -Z # show file contexts
ps -eZ | head # show process contexts
id -Z # your context
# Sample
# system_u:object_r:httpd_sys_content_t:s0 /var/www/html/index.html
# system_u:system_r:httpd_t:s0 nginx process
The third field (the “type”) is what matters most. httpd_sys_content_t means “content nginx can read.”
Common operations
# Set a file's context
sudo chcon -t httpd_sys_content_t /var/www/myapp/index.html
# Restore default context based on policy
sudo restorecon -Rv /var/www/
# Set file context permanently in policy
sudo semanage fcontext -a -t httpd_sys_content_t "/srv/www(/.*)?"
sudo restorecon -Rv /srv/www
# Allow nginx to bind to a non-standard port
sudo semanage port -a -t http_port_t -p tcp 8080
# Allow a boolean (common toggles)
sudo setsebool -P httpd_can_network_connect 1
sudo getsebool -a | grep httpd
Diagnose denials
# Watch for denials in real time
sudo journalctl -f | grep -i avc
# Show recent denials with explanations
sudo ausearch -m avc -ts recent
sudo sealert -a /var/log/audit/audit.log
# Generate a custom policy from a denial
sudo audit2allow -a -M mypolicy
sudo semodule -i mypolicy.pp
AppArmor (Debian, Ubuntu, SUSE)
Same idea as SELinux, simpler configuration — profiles are file paths, not labels.
Status
sudo aa-status # what's loaded and what mode
# Output sections:
# - profiles in enforce mode
# - profiles in complain mode
# - processes that have a profile
Profile modes
- enforce — block actions not in the profile.
- complain — log violations but allow them (good for testing).
- disabled — profile not active.
sudo aa-enforce /etc/apparmor.d/usr.sbin.nginx
sudo aa-complain /etc/apparmor.d/usr.sbin.nginx
sudo aa-disable /etc/apparmor.d/usr.sbin.nginx
sudo systemctl reload apparmor
Generate a profile from observed behavior
sudo aa-genprof /usr/local/bin/myapp
# In another terminal: run the app, exercise its features
# Then come back and answer aa-genprof's questions
Diagnose denials
sudo journalctl -k | grep -i apparmor
sudo dmesg | grep -i apparmor
Should you use them?
- Servers facing the internet: yes. The cost (occasional troubleshooting) is worth the security gain.
- Containers / cloud workloads: usually already enabled by your image (RHEL UBI ships SELinux on, Ubuntu ships AppArmor on).
- Personal desktop: AppArmor’s defaults rarely cause issues; SELinux can be more aggressive. Most users leave whatever the distro shipped.
Don’t disable on the first denial
The instinct when something doesn’t work is to disable SELinux/AppArmor. Resist. Instead:
- Switch the specific profile to permissive/complain.
- Reproduce the issue, collect the denial.
- Add an exception in policy.
- Re-enforce.
Disabling completely turns off a real security layer. Almost every “I disabled SELinux” forum thread ends with a real issue.
Other hardening to consider
- Updates —
unattended-upgradeson Debian/Ubuntu,dnf-automaticon Fedora. - Auditd — kernel-level audit logging.
- OSSEC / Wazuh — host intrusion detection.
- CIS Benchmarks — comprehensive hardening checklists.
Common mistakes
- Disabling instead of debugging the first denial.
- Using
chconwhen you should usesemanage fcontext+restorecon— chcon doesn’t survive a relabel. - Trying to copy an SELinux policy from one machine to another with different versions.
What to learn next
The last section of this roadmap: containers — the kernel features (namespaces, cgroups) that make Docker possible, and Docker itself.