Mandatory Access Control: AppArmor & SELinux Enterprise Practice
Created: 2026-11-10 Updated: 2026-11-10
Management Summary: Traditional Linux permissions governed by Discretionary Access Control (DAC) fail the moment a service operating with superuser authority is compromised: by architectural design,
rootcan inspect, modify, or delete every file across the operating system. Mandatory Access Control (MAC) introduces an inviolable second enforcement boundary directly within the Linux kernel via the Linux Security Module (LSM) interface. Even if an adversary achieves full root authority (UID 0) inside a daemon or web application, the security module confines execution strictly within an isolated sandbox. This enterprise engineering guide deconstructs both leading implementations: AppArmor (path-based, default on Debian and Ubuntu) and SELinux (label-based, default on RHEL and Fedora): From LSM kernel architecture and profile lifecycle management (aa-genprof,aa-logprof) to SELinux Type Enforcement, booleans, and port labels, culminating in systematic AVC denial forensics usingausearchandaudit2why.
-
DAC vs. MAC Architecture
Understand the fundamental distinction between user-controlled discretionary permissions (DAC) and kernel-enforced LSM security policies (MAC).
-
AppArmor Profile Lifecycle
From
complainmonitoring mode to strictenforcelockdown: define, test, and atomically activate daemon security profiles. -
AppArmor CLI Toolchain
Efficiently generate and maintain profiles using
aa-genprof,aa-logprof,aa-complain,aa-enforce, and theaa-statusdashboard. -
SELinux Type Enforcement
Labels, extended attributes (
security.selinux), security contexts (user:role:type:level), and domain transitions in the Linux kernel. -
SELinux Booleans & Ports
Dynamically toggle daemon capabilities using
setsebool -Pand assign custom network ports viasemanage port. -
AVC Denials & Audit Forensics
Systematically isolate access denials with
ausearch -m avc, translate errors viaaudit2why, and compile hardened policies withaudit2allow.
1. DAC vs. MAC: Why Discretionary Access Control Is Insufficient
Under traditional Discretionary Access Control (DAC), the file owner governs read, write, and execute permissions (chmod, chown). When a daemon (such as Nginx, Apache, or Bind) executes as root or an adversary weaponizes a local privilege escalation flaw, the attacker instantly gains unconstrained authority over the entire filesystem.
Mandatory Access Control (MAC) hooks into the Linux Security Module (LSM) subsystem, acting as a mandatory second checkpoint built directly into the kernel execution path. Every system call must first pass DAC evaluation and subsequently satisfy the active MAC policy:
%%{init: {'flowchart': {'wrappingWidth': 350}}}%%
flowchart TB
Process["Process<br/>(e.g., Web Server UID 0)"] ==> SysCall["System Call<br/>(openat, execve, socket)"]
SysCall --> DAC_Check{"1. DAC Check<br/>(UID / GID / Mode)"}
DAC_Check -->|"Denied"| Access_Denied_DAC["Access Blocked<br/>(EACCES)"]
DAC_Check -->|"Allowed"| LSM_Check{"2. LSM / MAC Check<br/>(AppArmor / SELinux)"}
LSM_Check -->|"Profile Authorizes"| Access_Granted["Kernel Grants Resource"]
LSM_Check -->|"No Rule in Profile"| Access_Denied_MAC["Access Blocked & Audited<br/>(EPERM / AVC Denial)"]
The Decisive Security Benefit
Even if an attacker achieves full root code execution within the web server process, the active MAC profile forbids reading /etc/shadow, spawning interactive shells (/bin/bash), or binding unauthorized sockets. The compromise is contained within the sandbox.
2. AppArmor Deep Architecture & Profile Syntax (Debian/Ubuntu)
AppArmor binds security policies directly to the absolute binary path of an executable. This design makes profiles readable, declarative, and easy to maintain.
Profiles reside as plaintext files in /etc/apparmor.d/ (such as /etc/apparmor.d/usr.sbin.nginx).
Profile Modes: complain vs. enforce
- Complain Mode (
complain): Policy violations are logged to the audit stream, but requests are permitted to execute (ideal for development, staging, and behavioral learning). - Enforce Mode (
enforce): Any access not explicitly granted is immediately rejected by the kernel withEPERMand recorded in the audit log.
AppArmor Permission Flags
Inside a profile, explicit permission flags govern path access:
| Flag | Meaning | Security Enforcement |
|---|---|---|
r |
Read | Permits file read access. |
w |
Write | Permits creating, appending, and truncating files. |
m |
Memory Map Executable | Permits loading binary code into memory (shared libraries). |
k |
Lock | Permits acquiring advisory file locks (flock). |
ix |
Inherit Execute | Executes target binary within the current profile context. |
px |
Profile Execute | Transitions upon execution into a discrete, dedicated profile. |
cx |
Child Execute | Transitions into an embedded child sub-profile. |
Complete Production Profile for an API Daemon
Here is a hardened AppArmor profile for a custom daemon (/usr/local/bin/api-daemon):
# /etc/apparmor.d/usr.local.bin.api-daemon
#include <tunables/global>
/usr/local/bin/api-daemon {
#include <abstractions/base>
#include <abstractions/nameservice>
# Restrict networking strictly to IPv4/IPv6 TCP sockets
network inet stream,
network inet6 stream,
# Allow binding privileged ports (80/443)
capability net_bind_service,
capability setuid,
capability setgid,
# Self execution and shared library mappings
/usr/local/bin/api-daemon mr,
/lib{,32,64}/** mr,
/usr/lib{,32,64}/** mr,
# Configuration read-only access
/etc/api-daemon/** r,
/etc/ssl/certs/** r,
# Application state: read, write, and lock
/var/lib/api-daemon/** rwk,
/var/log/api-daemon/** w,
# Transient scratch files
/tmp/api-daemon* rwk,
# Explicit denials (Deny rules override Allow rules)
deny /etc/shadow r,
deny /etc/passwd w,
deny /root/** rwx,
}
3. AppArmor Profile Lifecycle in Production
Managing profiles follows a structured pipeline using the AppArmor CLI suite:
1. Inspecting Operational Status with aa-status
2. Interactive Profiling with aa-genprof
aa-genprof creates a baseline skeleton, sets complain mode, and listens for operational events:
In a second terminal, exercise all typical workflows of the service (workload tests, API calls, log rotation, service reloads).
3. Parsing Audit Logs with aa-logprof
Return to aa-genprof (or invoke aa-logprof independently). The tool parses access records from /var/log/audit/audit.log (or /var/log/syslog) and prompts interactively:
(A)llow: Grants persistent permission.(D)eny: Explicitly forbids access.(G)lob: Grants permission via wildcard across the path (/*).
4. Enforcing the Profile (enforce)
# Switch profile to strict enforcing mode
sudo aa-enforce /etc/apparmor.d/usr.local.bin.api-daemon
# Reload profile live in running kernel
sudo apparmor_parser -r /etc/apparmor.d/usr.local.bin.api-daemon
4. SELinux Architecture & Type Enforcement (RHEL/Fedora)
Unlike AppArmor, SELinux attaches security labels directly to filesystem inodes via extended attributes (xattr). Renaming or moving a file preserves its SELinux label!
The 4-Part Security Context
Every file, directory, and running process carries a structured label:
- User (
system_u): SELinux user identity (independent of Linux UIDs). - Role (
object_r): Role used in Role-Based Access Control (RBAC). For files, this is universallyobject_r. - Type (
httpd_sys_content_t): The core mechanism of SELinux—Type Enforcement. Domains and file types must have an explicit allow rule in policy to interact. - Level (
s0): Multi-Level / Multi-Category Security (MLS/MCS) isolation.
Inspecting Security Contexts
# View SELinux contexts on files (-Z flag)
ls -laZ /var/www/html/
# View SELinux contexts of executing processes
ps -eZ | grep httpd
5. SELinux Booleans, Port Labels & File Contexts
SELinux provides rich baseline policies for hundreds of system daemons. Rather than writing custom modules, administrators configure behavior using booleans and port labels.
1. Controlling Booleans with getsebool and setsebool
A boolean is a kernel toggle enabling or disabling specific privileges:
# Check if web server is permitted to initiate outbound network sockets
getsebool httpd_can_network_connect
# Persistently enable outbound connections across reboots (-P flag mandatory!)
sudo setsebool -P httpd_can_network_connect 1
# Permit web daemon to dispatch emails
sudo setsebool -P httpd_can_sendmail 1
2. Assigning Custom Network Ports
If a daemon listens on a non-standard port (such as TCP 8080 instead of 80), SELinux terminates the bind() call. Assign the port to the appropriate label:
# List all ports authorized for HTTP traffic
sudo semanage port -l | grep http_port_t
# Add port 8080/TCP to the authorized HTTP port set
sudo semanage port -a -t http_port_t -p tcp 8080
3. Repairing File Contexts: restorecon vs. chcon
Production Ban on chcon
Never use chcon in production! chcon applies transient labels directly to inodes. During system relabeling (touch /.autorelabel) or maintenance, all chcon labels are permanently overwritten.
Always define persistent rules via semanage fcontext and enforce them using restorecon:
# Define persistent labeling rule for a custom webroot
sudo semanage fcontext -a -t httpd_sys_content_t "/srv/webdata(/.*)?"
# Apply the persistent label recursively across the filesystem
sudo restorecon -Rv /srv/webdata
6. AVC Denial Forensics & Troubleshooting Runbook
When SELinux blocks an operation, the kernel writes an AVC (Access Vector Cache) Denial to /var/log/audit/audit.log.
1. Tracking Denials with ausearch
# Query all AVC denials from the past 10 minutes
sudo ausearch -m avc -ts recent
# Explain denial reasons with actionable guidance via audit2why
sudo ausearch -m avc -ts recent | audit2why
audit2why translates cryptic kernel hexadecimal logs into human-readable recommendations (e.g., “Missing boolean httpd_can_network_connect”).
2. Generating Custom Policy Modules with audit2allow
When a proprietary daemon requires legitimate permissions not covered by standard policies, generate a tailored Type Enforcement module:
# Generate readable TE policy source and compiled PP module from recent denials
sudo ausearch -m avc -ts recent | audit2allow -M custom_app_policy
# Inspect the generated source carefully before activating!
cat custom_app_policy.te
# Install compiled policy module into the running kernel
sudo semodule -i custom_app_policy.pp
Caution Against Indiscriminate audit2allow
Never blindly execute audit2allow -a -M ...! If an attacker just attempted to read /etc/shadow, running audit2allow generates a rule that explicitly authorizes reading shadow hashes in the future!
Enterprise Troubleshooting Table
| Issue / Symptom | Root Cause | Remediation Workflow |
|---|---|---|
| AppArmor blocks file access | Path missing from profile | Run sudo aa-logprof or add explicit r/w path. |
| AppArmor daemon fails to start | Profile syntax error | Execute sudo apparmor_parser -r /etc/apparmor.d/<profile> to see line error. |
| SELinux blocks web directory | Incorrect file context | Execute sudo restorecon -Rv /path. |
| SELinux blocks custom port | Port not in authorized type set | Run sudo semanage port -a -t <type> -p tcp <port>. |
| Emergency Triage | Unidentified production outage | Execute sudo setenforce 0 (temporarily switches to Permissive mode for testing). |
Mandatory Access Control provides the most resilient in-kernel isolation barrier available in Linux: processes adhere strictly to least-privilege boundaries, ensuring that a compromised daemon cannot achieve host takeover.