Linux — Level by Level›17 · SELinux & AppArmor

Lesson 17 of 19 · Level 3 — Advanced: kernel & security

SELinux & AppArmor

Mandatory access control adds a second lock behind file permissions. Read SELinux contexts and AppArmor profiles, find denials in the logs, and fix them properly instead of switching protection off.

Advanced
Key wordsMACSELinuxcontextsrestoreconsemanagebooleansAppArmorprofilesAVC denial

Why a second lock?

Normal file permissions (lesson 04) are discretionary: owners decide who gets access, and root bypasses them. If an attacker takes over the web server process, they get everything that process's user can touch.

Mandatory Access Control (MAC) adds rules that even owners and (mostly) root can't override: the web server may read web content and bind web ports, and nothing else, whatever the file permissions say.

File permissions are the lock on each classroom door, and teachers carry keys. MAC is a security guard with a list at every corridor: "the kitchen staff may only go to the kitchen and the canteen." Even if a kitchen worker finds a master key, the guard still won't let them into the headteacher's office.

SELinux AppArmor
Default on RHEL, Rocky, Alma, Fedora Ubuntu, Debian, SUSE
Identifies things by Labels (contexts) on files, processes, ports Paths of programs and files
Unit of policy Type enforcement rules Per-program profiles
Debug mode Permissive Complain

SELinux in practice

Every file and process has a context user:role:type:level. The type is what matters day to day:

$ getenforce
Enforcing
$ ls -Z /var/www/html/index.html
system_u:object_r:httpd_sys_content_t:s0 /var/www/html/index.html
$ ps -eZ | grep nginx
system_u:system_r:httpd_t:s0      1487 ?  00:00:00 nginx

The policy says processes of type httpd_t may read files of type httpd_sys_content_t.

The classic problem: new content path

You move a website to /srv/www. Permissions are fine, but nginx returns 403:

$ ls -Z /srv/www/index.html
unconfined_u:object_r:default_t:s0 /srv/www/index.html
$ sudo ausearch -m AVC -ts recent
type=AVC msg=audit(…): avc:  denied  { read } for  pid=1488 comm="nginx" name="index.html"
  scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:default_t:s0 tclass=file permissive=0

Read the denial: who (httpd_t) tried what (read) on which label (default_t). The fix is to label the path correctly, in policy so it survives relabels:

$ sudo semanage fcontext -a -t httpd_sys_content_t '/srv/www(/.*)?'
$ sudo restorecon -Rv /srv/www
Relabeled /srv/www/index.html from unconfined_u:object_r:default_t:s0 to unconfined_u:object_r:httpd_sys_content_t:s0

Booleans and ports

Common behaviours are switchable booleans:

$ getsebool -a | grep httpd_can_network_connect
httpd_can_network_connect --> off
$ sudo setsebool -P httpd_can_network_connect on     # e.g. nginx proxying to a backend

And services may only bind ports with the right type:

$ sudo semanage port -l | grep http_port_t
http_port_t   tcp   80, 81, 443, 488, 8008, 8009, 8443, 9000
$ sudo semanage port -a -t http_port_t -p tcp 8081

(The exact port list varies by policy version.)

Don't disable SELinux. Read the denial.

setenforce 0 "fixes" things by removing protection for everything. Almost every denial is solved by the right label (semanage fcontext + restorecon), a boolean, or a port type. Writing custom policy with audit2allow is a last resort, and deserves review.

AppArmor in practice

AppArmor attaches a profile to a program path:

$ sudo aa-status | head -8
apparmor module is loaded.
42 profiles are loaded.
38 profiles are in enforce mode.
   /usr/sbin/cupsd
   /usr/sbin/mysqld
   …
4 profiles are in complain mode.

Denials appear in the kernel log:

$ sudo journalctl -k | grep -i 'apparmor="DENIED"' | tail -1
… apparmor="DENIED" operation="open" profile="/usr/sbin/mysqld" name="/data/mysql/ibdata1" pid=2310 comm="mysqld" requested_mask="r" denied_mask="r"

MySQL was moved to /data/mysql, but its profile only allows the default path. Fix it by allowing the new path, via a local override (/etc/apparmor.d/local/usr.sbin.mysqld, where the distribution provides one):

/data/mysql/ r,
/data/mysql/** rwk,

then sudo apparmor_parser -r /etc/apparmor.d/usr.sbin.mysqld.

MAC and containers

Container runtimes apply MAC automatically: containers run as the SELinux type container_t (with per-container categories so containers can't read each other's files), or under a default AppArmor profile. In Kubernetes, pods can choose profiles and options through securityContext (seLinuxOptions, appArmorProfile). Volume mounts sometimes need the right labels, which is a common source of "permission denied" on SELinux nodes.

Try it: break and fix (RHEL-family VM for SELinux)

  1. Install nginx, create /srv/www/index.html, and point nginx's root at /srv/www. Confirm the 403 and read the denial with ausearch -m AVC -ts recent.
  2. Fix it with semanage fcontext + restorecon. Confirm with ls -Z and curl.
  3. Configure nginx to listen on port 8081; see the start failure and the denial; fix it with semanage port.
  4. On Ubuntu, list AppArmor profiles with aa-status, pick one in enforce mode, and find its file in /etc/apparmor.d/.

Going deeper: MAC at scale

  • Keep Enforcing everywhere, including Kubernetes nodes; container escapes are much harder with MAC in place.
  • Use sealert (setroubleshoot) on RHEL for human-readable explanations and suggested fixes.
  • Ship audit logs centrally. A sudden burst of AVC denials is an intrusion signal as well as a misconfiguration signal.
  • For custom services, write a small targeted policy module or AppArmor profile as part of the package, reviewed like code, rather than running them unconfined.

Recap

  • MAC adds rules no ordinary user can override: SELinux (labels) or AppArmor (paths).
  • Diagnose with denials: ausearch -m AVC (SELinux), journalctl -k | grep apparmor (AppArmor).
  • Fix SELinux with semanage fcontext + restorecon, booleans, and port types; fix AppArmor with profile rules.
  • Never "fix" by disabling. Permissive/complain mode is for diagnosis only.

This site is a public version of my personal engineering knowledge hub. It intentionally excludes confidential company information and internal operational details.