AppArmor
Quick Reference
# Check status
aa-status
sudo apparmor_status
# Profile modes
aa-enforce /etc/apparmor.d/usr.bin.firefox # Enforce mode
aa-complain /etc/apparmor.d/usr.bin.firefox # Complain mode
aa-disable /etc/apparmor.d/usr.bin.firefox # Disable profile
# Reload profiles
apparmor_parser -r /etc/apparmor.d/usr.bin.firefox
systemctl reload apparmor
# Generate profiles
aa-genprof /path/to/application
aa-logprof # Update profiles from logs
# View logs
journalctl -k | grep apparmor
dmesg | grep apparmor
Understanding AppArmor
What is AppArmor?
AppArmor (Application Armor) is a Linux Security Module (LSM) that provides Mandatory Access Control (MAC). It confines programs to a limited set of resources using per-program profiles.
Key characteristics:
-
Path-based - Rules based on file paths (unlike SELinux labels)
-
Profile-driven - Each confined application has a profile
-
Easier learning curve - Simpler syntax than SELinux
-
Default on Debian/Ubuntu - Pre-installed and enabled
AppArmor vs SELinux
| Aspect | AppArmor | SELinux |
|---|---|---|
Access control |
Path-based |
Label-based |
Default distros |
Debian, Ubuntu, SUSE |
RHEL, Fedora, CentOS |
Complexity |
Lower |
Higher |
Profile creation |
Interactive tools |
Policy compilation |
Flexibility |
Good for applications |
Better for system-wide |
Performance |
Minimal overhead |
Minimal overhead |
Installation and Status
Install AppArmor
# Debian/Ubuntu (usually pre-installed)
sudo apt install apparmor apparmor-utils apparmor-profiles apparmor-profiles-extra
# Arch Linux
sudo pacman -S apparmor
# Enable at boot (Arch/manual setup)
# Add to kernel parameters: apparmor=1 security=apparmor
# Verify kernel support
cat /sys/module/apparmor/parameters/enabled
# Y = enabled
Check Status
# Comprehensive status
sudo aa-status
# Output shows:
# - Number of loaded profiles
# - Profiles in enforce mode
# - Profiles in complain mode
# - Processes with profiles
# Alternative
sudo apparmor_status
# Check if AppArmor is enabled
systemctl status apparmor
# Kernel parameters
cat /proc/cmdline | grep apparmor
Working with Profiles
Profile Locations
# Main profile directory
/etc/apparmor.d/
# Profile abstractions (included by profiles)
/etc/apparmor.d/abstractions/
# Tunables (variables for profiles)
/etc/apparmor.d/tunables/
# Local overrides
/etc/apparmor.d/local/
# Cache (compiled profiles)
/var/cache/apparmor/
List Profiles
# List all profiles
ls /etc/apparmor.d/
# Profiles typically named by path
# usr.bin.firefox = /usr/bin/firefox
# usr.sbin.mysqld = /usr/sbin/mysqld
# List loaded profiles with status
sudo aa-status --pretty
# List processes confined by AppArmor
ps auxZ | grep -v unconfined
Change Profile Mode
# Set to enforce mode
sudo aa-enforce /etc/apparmor.d/usr.bin.firefox
sudo aa-enforce /usr/bin/firefox # By binary path
# Set to complain mode (for testing)
sudo aa-complain /etc/apparmor.d/usr.bin.firefox
# Disable profile
sudo aa-disable /etc/apparmor.d/usr.bin.firefox
# Enable disabled profile
sudo aa-enforce /etc/apparmor.d/usr.bin.firefox
Load and Reload Profiles
# Load new profile
sudo apparmor_parser /etc/apparmor.d/usr.bin.myapp
# Reload modified profile
sudo apparmor_parser -r /etc/apparmor.d/usr.bin.myapp
# Remove profile from kernel
sudo apparmor_parser -R /etc/apparmor.d/usr.bin.myapp
# Reload all profiles
sudo systemctl reload apparmor
# Clear cache and reload
sudo apparmor_parser -r --write-cache /etc/apparmor.d/
Profile Syntax
Basic Profile Structure
# /etc/apparmor.d/usr.bin.myapp
#include <tunables/global>
/usr/bin/myapp {
# Include common abstractions
#include <abstractions/base>
#include <abstractions/nameservice>
# Capabilities
capability net_bind_service,
capability setuid,
# Network access
network inet stream,
network inet dgram,
# File access rules
/usr/bin/myapp mr,
/etc/myapp.conf r,
/var/log/myapp.log w,
/var/lib/myapp/ rw,
/var/lib/myapp/** rw,
# Deny rules (explicit)
deny /etc/shadow r,
deny /home/** rw,
# Execute other programs
/usr/bin/helper Px,
/bin/sh ix,
# Local overrides
#include <local/usr.bin.myapp>
}
File Permission Flags
| Flag | Description |
|---|---|
r |
Read |
w |
Write |
a |
Append |
k |
Lock |
l |
Link |
m |
Memory map executable |
x |
Execute (general) |
ix |
Execute, inherit profile |
px |
Execute, use target’s profile (must exist) |
Px |
Execute, use target’s profile or fail |
ux |
Execute, unconfined (dangerous) |
cx |
Execute, use child profile (defined in current profile) |
Path Globbing
# Exact path
/etc/myapp.conf r,
# Single directory wildcard
/var/log/*.log rw,
# Recursive wildcard
/var/lib/myapp/** rw,
# Character class
/dev/tty[0-9] rw,
# Any single character
/tmp/myapp.? rw,
# Alternatives
/usr/{bin,sbin}/myapp mr,
Capabilities
# Common capabilities
capability chown,
capability dac_override,
capability fowner,
capability fsetid,
capability kill,
capability net_bind_service,
capability setgid,
capability setuid,
capability sys_chroot,
capability sys_ptrace,
# All capabilities (dangerous)
capability,
Network Rules
# TCP connections
network inet stream,
network inet6 stream,
# UDP
network inet dgram,
network inet6 dgram,
# Unix sockets
network unix stream,
network unix dgram,
# Raw sockets (requires capability)
network inet raw,
# All network (less restrictive)
network,
Signal and Ptrace Rules
# Allow sending signals to self
signal (send) set=(term, kill) peer=@{profile_name},
# Allow signals to specific process
signal (send) peer=/usr/bin/helper,
# Receive signals
signal (receive) set=(term, kill) peer=unconfined,
# Ptrace (debugging)
ptrace (trace) peer=/usr/bin/gdb,
Creating Profiles
Interactive Profile Generation
# Generate profile interactively
sudo aa-genprof /usr/bin/myapp
# In another terminal, run the application and exercise its features
# Then return to aa-genprof and press 'S' to scan logs
# Options during generation:
# (A)llow - Allow the access
# (D)eny - Deny the access
# (I)nherit - Execute with current profile
# (C)hild - Execute with child profile
# (N)amed - Execute with named profile
# (U)nconfined - Execute unconfined (dangerous)
# (S)can - Scan logs for new events
# (F)inish - Save and finish
Update Profile from Logs
# After running app in complain mode, update profile
sudo aa-logprof
# Process events from log
# Same interactive options as aa-genprof
# Update specific profile
sudo aa-logprof -f /etc/apparmor.d/usr.bin.myapp
Manual Profile Creation
# Create profile file
sudo vim /etc/apparmor.d/usr.bin.myapp
# Minimal profile template
#include <tunables/global>
/usr/bin/myapp {
#include <abstractions/base>
/usr/bin/myapp mr,
# Add rules as needed
}
# Load the profile
sudo apparmor_parser /etc/apparmor.d/usr.bin.myapp
# Test in complain mode first
sudo aa-complain /etc/apparmor.d/usr.bin.myapp
# After testing, enforce
sudo aa-enforce /etc/apparmor.d/usr.bin.myapp
Using Abstractions
# Common abstractions to include
# Base system access
#include <abstractions/base>
# Name resolution
#include <abstractions/nameservice>
# X11 applications
#include <abstractions/X>
# Audio
#include <abstractions/audio>
# Fonts
#include <abstractions/fonts>
# GNOME applications
#include <abstractions/gnome>
# PHP
#include <abstractions/php>
# Python
#include <abstractions/python>
# SSL/TLS
#include <abstractions/ssl_certs>
# User home directory
#include <abstractions/user-tmp>
Profile Examples
Web Server (nginx)
#include <tunables/global>
/usr/sbin/nginx {
#include <abstractions/base>
#include <abstractions/nameservice>
#include <abstractions/ssl_certs>
capability net_bind_service,
capability setuid,
capability setgid,
capability dac_override,
network inet stream,
network inet6 stream,
/usr/sbin/nginx mr,
/etc/nginx/** r,
/var/log/nginx/*.log w,
/var/lib/nginx/** rw,
/run/nginx.pid rw,
/var/www/** r,
# Worker processes
/usr/sbin/nginx ix,
#include <local/usr.sbin.nginx>
}
Database (MySQL/MariaDB)
#include <tunables/global>
/usr/sbin/mysqld {
#include <abstractions/base>
#include <abstractions/nameservice>
#include <abstractions/mysql>
capability dac_override,
capability setgid,
capability setuid,
capability sys_resource,
network inet stream,
network inet6 stream,
/usr/sbin/mysqld mr,
/etc/mysql/** r,
/var/lib/mysql/ r,
/var/lib/mysql/** rwk,
/var/log/mysql/** rw,
/run/mysqld/ rw,
/run/mysqld/** rw,
/tmp/** rw,
/var/tmp/** rw,
#include <local/usr.sbin.mysqld>
}
Container Runtime (Podman)
#include <tunables/global>
/usr/bin/podman {
#include <abstractions/base>
#include <abstractions/nameservice>
capability sys_admin,
capability net_admin,
capability sys_chroot,
capability setuid,
capability setgid,
capability mknod,
capability sys_ptrace,
network,
/usr/bin/podman mr,
/usr/bin/conmon Px,
/usr/bin/crun Px,
/etc/containers/** r,
/var/lib/containers/** rwk,
/run/containers/** rw,
/run/user/*/containers/** rw,
# CNI plugins
/usr/lib/cni/* ix,
/opt/cni/bin/* ix,
#include <local/usr.bin.podman>
}
Local Overrides
Troubleshooting
View AppArmor Denials
# Kernel messages
dmesg | grep apparmor
# Journal
journalctl -k | grep apparmor
journalctl -k -f | grep apparmor # Follow
# Audit log (if auditd running)
grep apparmor /var/log/audit/audit.log
# Syslog
grep apparmor /var/log/syslog
# Typical denial message:
# apparmor="DENIED" operation="open" profile="/usr/bin/myapp"
# name="/etc/secret" pid=1234 comm="myapp" requested_mask="r"
Debug Profile Issues
# Put profile in complain mode
sudo aa-complain /etc/apparmor.d/usr.bin.myapp
# Run application
/usr/bin/myapp
# Check what would be denied
sudo aa-logprof
# Add necessary rules
# Switch to enforce mode
sudo aa-enforce /etc/apparmor.d/usr.bin.myapp
Common Issues
# Issue: Profile not loading
# Check syntax
sudo apparmor_parser -p /etc/apparmor.d/usr.bin.myapp
# Issue: Application can't access files
# Check if path matches (exact vs glob)
# Verify permissions in profile match operation
# Issue: Network denied
# Add specific network rules
network inet stream,
network inet dgram,
# Issue: Execute denied
# Check execute transition (ix, px, cx, ux)
/path/to/helper Px,
# Issue: Capability denied
# Add required capability
capability net_bind_service,
Profile Debugging Tools
# Parse and validate profile
sudo apparmor_parser -p /etc/apparmor.d/usr.bin.myapp
# Show what profile would allow
sudo apparmor_parser -Q /etc/apparmor.d/usr.bin.myapp
# Detailed parse output
sudo apparmor_parser -v /etc/apparmor.d/usr.bin.myapp
# Trace profile loading
sudo apparmor_parser --debug /etc/apparmor.d/usr.bin.myapp
Containers and AppArmor
Docker with AppArmor
# Docker uses AppArmor by default
# Check container profile
docker inspect --format='{{.AppArmorProfile}}' container_name
# Run with specific profile
docker run --security-opt apparmor=my-profile nginx
# Run unconfined (less secure)
docker run --security-opt apparmor=unconfined nginx
# Generate Docker profile
aa-genprof docker
# Default Docker profile location
/etc/apparmor.d/docker
Quick Command Reference
# Status
sudo aa-status # Full status
sudo aa-status --pretty # Formatted
# Profile modes
sudo aa-enforce /etc/apparmor.d/PROFILE # Enforce
sudo aa-complain /etc/apparmor.d/PROFILE # Complain
sudo aa-disable /etc/apparmor.d/PROFILE # Disable
# Load/reload
sudo apparmor_parser /etc/apparmor.d/PROFILE # Load
sudo apparmor_parser -r /etc/apparmor.d/PROFILE # Reload
sudo apparmor_parser -R /etc/apparmor.d/PROFILE # Remove
sudo systemctl reload apparmor # Reload all
# Profile generation
sudo aa-genprof /path/to/binary # Generate
sudo aa-logprof # Update from logs
sudo aa-autodep /path/to/binary # Basic profile
# Troubleshooting
dmesg | grep apparmor # Kernel denials
journalctl -k | grep apparmor # Journal denials
sudo apparmor_parser -p PROFILE # Validate syntax