Approach Comparison: DAC vs. SELinux vs. AppArmor
Six months ago, our team rolled out Mandatory Access Control (MAC) policies across 45 production servers to shield Python and Node.js microservices from zero-day exploits. Traditional Linux security stops at Discretionary Access Control (DAC)—the standard file permissions (chmod, chown) and user groups.
That model has a fatal blind spot. If an attacker lands Remote Code Execution (RCE) inside a service account like www-data, DAC cannot contain them. The attacker inherits every file read permission, writes payload scripts to /tmp, and probes for local kernel exploits.
When hardening modern Linux distributions at the MAC layer, two primary frameworks stand out:
- SELinux (Security-Enhanced Linux): Uses inode-based labeling to attach security contexts to files, processes, and sockets. It dominates RHEL, Rocky Linux, and Fedora environments. However, crafting SELinux policies from scratch requires mastering a steep macro language and debugging complex type transitions.
- AppArmor (Application Armor): Relies on path-based access control by binding security profiles directly to executable filesystem paths like
/usr/bin/nginxor/opt/api/server. Profiles are plain-text, easily auditable files stored under/etc/apparmor.d/.
For Debian and Ubuntu environments, AppArmor offers the fastest route to daemon isolation without demanding a dedicated security team just to compile policies.
Pros and Cons: 6 Months in Production
Running AppArmor across high-throughput production nodes delivered solid containment gains, alongside a few operational quirks worth tracking.
The Advantages
- Human-Readable Rules: Rules rely on standard globbing and explicit permission flags like
r(read),w(write), andix(inherit execute). Any engineer comfortable with shell wildcards can audit a profile in minutes. - Interactive Learning Mode: Utilities like
aa-genprofandaa-logprofparse audit logs and provide interactive prompts to approve, deny, or glob access paths discovered during live runs. - Zero-Overhead Sandboxing: AppArmor hooks directly into Linux Security Modules (LSM) inside the kernel. In our synthetic benchmarks at 50,000 requests/sec with
wrk, latency overhead stayed below 0.8% with no measurable memory footprint.
The Drawbacks
- Path Sensitivity: Because AppArmor targets absolute paths, symbolic links, binary renames, hard links, or unmapped chroot environments can bypass or break profile matching if not declared up front.
- No L3/L4 Network Filtering: AppArmor restricts protocol families—such as permitting
tcpwhile blocking raw packet sockets—but it cannot filter by IP address or destination port. You still neednftablesoriptablesfor packet filtering.
Recommended Setup
To establish a reliable sandboxing pipeline on Ubuntu 22.04 or Debian 12, install the core tooling and activate the kernel audit daemon:
# Install AppArmor utilities and audit daemon
sudo apt update
sudo apt install -y apparmor apparmor-utils auditd
# Verify AppArmor kernel module status
sudo aa-status
Never push an untested profile straight into enforce mode. Overly aggressive policies often break background log rotation at 3 AM, kill health check probes, or block tempfile creation. Run new profiles in complain mode for at least 48 to 72 hours under normal traffic before enforcing them.
Implementation Guide: Creating a Custom AppArmor Profile
Consider a practical deployment: sandboxing a custom FastAPI worker located at /opt/api/server.py. The application needs to read its JSON configuration, bind to a TCP socket, and write to a log folder. It must never touch /etc/shadow, inspect user home folders, or spawn interactive shells.
Step 1: Define the Application Stub
Ensure the service runs through a fixed entrypoint script, such as /usr/local/bin/myapi:
sudo chmod +x /usr/local/bin/myapi
Step 2: Initialize the Profile with aa-genprof
Launch the profile generator in complain mode to monitor kernel audit events:
sudo aa-genprof /usr/local/bin/myapi
Keep that terminal active. In another session, trigger standard application workflows: send real HTTP payloads, run test suites, rotate logs, and execute background tasks. AppArmor captures every system call and records requested paths to /var/log/audit/audit.log.
Step 3: Review and Inspect Profile Syntax
Return to the aa-genprof session and press S to scan audit logs. The utility presents interactive prompts to allow, deny, or glob observed file paths. The resulting configuration file is saved to /etc/apparmor.d/usr.local.bin.myapi.
Here is a clean, hardened profile ready for production:
# /etc/apparmor.d/usr.local.bin.myapi
#include <tunables/global>
/usr/local/bin/myapi {
# Base abstractions
#include <abstractions/base>
#include <abstractions/python>
#include <abstractions/nameservice>
# Network access
network inet tcp,
network inet udp,
# Interpreter and entrypoint
/usr/bin/python3* ix,
/usr/local/bin/myapi r,
# Application source and configuration
/opt/api/ r,
/opt/api/** r,
/opt/api/config.json r,
# Logging and temporary runtime storage
/var/log/myapi/ w,
/var/log/myapi/** rw,
/tmp/myapi_*.tmp rw,
# Hard block sensitive paths and shell spawns
deny /etc/shadow* rwklx,
deny /etc/gshadow* rwklx,
deny /root/** rwklx,
deny /home/** rwklx,
deny /bin/dash rx,
deny /bin/bash rx,
deny /usr/bin/sh rx,
}
Step 4: Reload and Enforce the Profile
Compile the updated rules directly into the kernel and activate strict enforcement mode:
# Parse and reload profile rules
sudo apparmor_parser -r /etc/apparmor.d/usr.local.bin.myapi
# Switch profile to enforce mode
sudo aa-enforce /usr/local/bin/myapi
# Verify active enforcement
sudo aa-status | grep myapi
Step 5: Testing Sandboxing and Containment
Validate that the sandbox blocks lateral movement by attempting unauthorized operations from within the application context:
# Check kernel logs for access denials
sudo dmesg | grep -i "apparmor.*denied"
# Or query recent AVC denials via auditd
sudo ausearch -m AVC -ts recent
If an attacker exploits an RCE flaw, any attempt to launch /bin/bash or inspect sensitive directories triggers an immediate kernel-level EACCES (Permission Denied) error. The attack stalls instantly, leaving an audit trail without exposing root credentials.

