SELinux & AppArmor — Linux Mandatory Access Control Guide
In this tutorial, you'll learn about SELinux & AppArmor. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
SELinux and AppArmor are Linux kernel security modules that enforce mandatory access control (MAC), confining processes beyond traditional Unix permissions to limit damage from compromised services.
What You'll Learn
How SELinux labels every file and Process with security contexts, how AppArmor uses path-based profiles, how to write policies, troubleshoot denials, and switch between enforcing and permissive modes on RHEL and Ubuntu systems.
Why MAC Matters
Standard Linux permissions (rwx) are discretionary — if a Process runs as root, it can access anything. MAC layers add system-wide policies that even root cannot bypass. When a web server gets compromised, SELinux or AppArmor prevents it from writing to /etc/passwd or reading SSH keys. Durga Antivirus Pro uses SELinux in enforcing mode to isolate its scan engine from the rest of the OS.
Learning Path
flowchart LR A[User Management] --> B[File Permissions] B --> C[Security Hardening] C --> D[SELinux & AppArmor
You are here] D --> E[Server Hardening CIS] D --> F[Audit & Monitoring] style D fill:#f90,color:#fff
SELinux Overview
SELinux (Security-Enhanced Linux) was developed by the NSA and is enabled by default on RHEL, CentOS, Fedora, and Rocky Linux. Every file, Process, port, and device gets a security context. The kernel enforces rules defined in loaded policy modules.
SELinux Modes
# Check current mode
getenforce
# Set mode temporarily (until reboot)
sudo setenforce 0 # Permissive — log denials, do not block
sudo setenforce 1 # Enforcing — block and log
# Change mode permanently
sudo sed -i 's/SELINUX=enforcing/SELINUX=permissive/' /etc/selinux/config
Expected output:
$ getenforce
Enforcing
$ sudo setenforce 0
$ getenforce
Permissive
SELinux Contexts
# View context of files
ls -Z /etc/shadow
# system_u:object_r:shadow_t:s0 /etc/shadow
# View context of processes
ps -eZ | grep sshd
# system_u:system_r:sshd_t:s0-s0:c0.c1023 1234 ? 00:00:00 sshd
# View context of user
id -Z
# unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
A context has four parts: user:role:type:sensitivity. The type (sshd_t, shadow_t) is what SELinux uses for access decisions — this is called Type Enforcement (TE).
Managing Booleans
Booleans toggle SELinux rules without writing policy:
# List all booleans
getsebool -a
# Check a specific boolean
getsebool httpd_can_network_connect
# Set a boolean (temporary)
sudo setsebool httpd_can_network_connect on
# Set a boolean (permanent)
sudo setsebool -P httpd_can_network_connect on
Expected output:
$ getsebool httpd_can_network_connect
httpd_can_network_connect --> off
$ sudo setsebool httpd_can_network_connect on
$ getsebool httpd_can_network_connect
httpd_can_network_connect --> on
Troubleshooting Denials
# Search audit log for SELinux denials
sudo ausearch -m avc -ts recent
# Alternative: use sealert for human-readable messages
sudo sealert -a /var/log/audit/audit.log
# Generate custom policy from audit log
sudo ausearch -m avc -ts recent | audit2allow -M mycustom
sudo semodule -i mycustom.pp
# Fix common file context issues
sudo restorecon -Rv /var/www/html/
Expected audit output:
type=AVC msg=audit(1719203045.123:456): avc: denied { write } for pid=7890
comm="httpd" name="index.html" dev="sda1" ino=12345
scontext=system_u:system_r:httpd_t:s0
tcontext=unconfined_u:object_r:admin_home_t:s0
tclass=file
The fix: sudo restorecon /var/www/html/index.html or sudo chcon -t httpd_sys_content_t /var/www/html/index.html.
Writing SELinux Policy Modules
# Create a policy module for a custom app
cd ~
cat > myapp.te << 'POLICY'
module myapp 1.0;
require {
type unconfined_t;
type var_log_t;
class file { read write };
}
allow unconfined_t var_log_t:file { read write };
POLICY
# Compile and load
checkmodule -M -m -o myapp.mod myapp.te
semodule_package -o myapp.pp -m myapp.mod
sudo semodule -i myapp.pp
AppArmor Overview
AppArmor is the default MAC on Ubuntu and Debian. Instead of labeling files with contexts, AppArmor attaches profiles to executable paths. Profiles define what files, capabilities, and network access a program has.
AppArmor Status
# List loaded profiles
sudo aa-status
# Check if a process is confined
cat /proc/1/attr/current
# unconfined (for init)
# For a confined process
cat /proc/$(pgrep -u www-data | head -1)/attr/current
# /usr/sbin/nginx (enforce)
AppArmor Modes
# Enforce mode — block violations
sudo aa-enforce /usr/sbin/nginx
# Complain mode — log but do not block
sudo aa-complain /usr/sbin/nginx
# Disable profile
sudo aa-disable /usr/sbin/nginx
Profile Structure
# Profile for a custom application
sudo tee /etc/apparmor.d/usr.local.myapp << 'PROFILE'
#include <tunables/global>
/usr/local/bin/myapp {
#include <abstractions/base>
#include <abstractions/openssl>
/usr/local/bin/myapp r,
/etc/myapp/config r,
/var/log/myapp/** rw,
/var/run/myapp.pid w,
/tmp/myapp/** rw,
network tcp,
network inet dgram,
deny /etc/shadow r,
deny /root/** rwx,
}
PROFILE
sudo systemctl reload apparmor
Generating Profiles with aa-genprof
# Generate a profile interactively
sudo aa-genprof /usr/local/bin/myapp
# Follow the prompts:
# 1. Run the application in another terminal
# 2. aa-genprof asks for each denial — choose Allow/Deny/Glob
# 3. Save when done
Troubleshooting AppArmor
# View AppArmor denials
sudo journalctl -u apparmor | grep DENIED
sudo cat /var/log/syslog | grep apparmor
# Check for denials in audit log
sudo aa-notify -s 1 -v
# Temporarily disable a profile
sudo ln -s /etc/apparmor.d/usr.sbin.rsyslogd /etc/apparmor.d/disable/
sudo apparmor_parser -R /etc/apparmor.d/usr.sbin.rsymyslogd
SELinux vs AppArmor
| Feature | SELinux | AppArmor |
|---|---|---|
| Label system | Security contexts on all objects | Path-based profiles |
| Policy language | TE, RBAC, MLS rules | Simple text profiles |
| Default on | RHEL, Fedora, CentOS, Rocky | Ubuntu, Debian, OpenSUSE |
| Granularity | Object class + permission level | File paths + capabilities |
| Learning curve | Steep | Moderate |
| Policy management | semodule, audit2allow | aa-genprof, aa-easyprof |
Common Errors
1. SELinux Blocking Nginx from Connecting to Backend
Nginx reverse proxy fails with 502. Check getsebool httpd_can_network_connect. If off, enable it: sudo setsebool -P httpd_can_network_connect on.
2. AppArmor Blocking Custom Script
A custom script in /usr/local/bin fails to write to /var/log/myapp/. Create an AppArmor profile with the correct write path or put the profile in complain mode temporarily.
3. Context Mismatch After File Move
Moving files with cp preserves context; mv creates a new file with the default context. If a moved file is inaccessible, restore context with sudo restorecon /path/to/file.
4. SELinux in Permissive Mode in Production
Permissive logs denials but does not enforce them. A service might seem to work fine during testing but fail when you switch to enforcing. Always test in enforcing mode.
5. Conflicting Profiles
Multiple AppArmor profiles matching the same binary can cause unexpected behavior. Remove conflicting profiles with aa-disable.
6. Mislabeled Ports
SELinux controls which ports services can bind to. If Apache refuses to listen on port 8080, add the port: sudo semanage port -a -t http_port_t -p tcp 8080.
7. AppArmor Denies in Syslog Without Clear Cause
Use aa-logprof to scan denials and update profiles interactively: sudo aa-logprof.
Practice Questions
1. What command checks if SELinux is in enforcing mode?
getenforce. Returns Enforcing, Permissive, or Disabled.
2. How do you temporarily switch AppArmor to complain mode for a program?
sudo aa-complain /usr/bin/program. Violations are logged but not blocked.
3. What is the difference between SELinux types and AppArmor profiles? SELinux assigns types to all files and processes using security contexts. AppArmor binds profiles to executable paths. SELinux is label-based; AppArmor is path-based.
4. How do you generate a custom SELinux policy from audit denials?
Pipe ausearch output to audit2allow: sudo ausearch -m avc -ts recent | audit2allow -M myapp && sudo semodule -i myapp.pp.
5. What command restores default SELinux context on a directory?
sudo restorecon -Rv /path/to/dir. Recursively resets contexts to policy defaults.
Challenge: Deploy Nginx on a fresh RHEL 9 server with SELinux enforcing. Configure it to serve content from /data/www (instead of /usr/share/nginx/html). Use semanage fcontext and restorecon to label the new directory correctly. Verify with curl that content is served and ausearch shows no denials.
What's Next
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro. Updated 2026-06-24.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro