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

Profile Modes

  • Enforce - Violations blocked and logged

  • Complain - Violations logged but allowed (for profile development)

  • Disabled - Profile not loaded

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

Service Management

# Start AppArmor
sudo systemctl start apparmor

# Enable at boot
sudo systemctl enable apparmor

# Reload all profiles
sudo systemctl reload apparmor

# Stop (unloads profiles, less secure)
sudo systemctl stop 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,

DBus Rules

# DBus session bus
dbus (send, receive) bus=session,

# Specific service
dbus (send)
    bus=system
    path=/org/freedesktop/NetworkManager
    interface=org.freedesktop.NetworkManager
    peer=(name=org.freedesktop.NetworkManager),

# DBus ownership
dbus (bind)
    bus=session
    name=org.myapp.Service,

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

Adding Local Rules

# Create local override file
sudo vim /etc/apparmor.d/local/usr.bin.myapp

# Add additional rules
# These are included at the end of the main profile

/extra/path/needed rw,
/another/directory/** r,

# Reload profile
sudo apparmor_parser -r /etc/apparmor.d/usr.bin.myapp

Tunables

# /etc/apparmor.d/tunables/global includes variables

# Home directory
@{HOME} = /home/*/ /root/

# User-specific
@{HOMEDIRS} = /home/

# Proc filesystem
@{PROC} = /proc/

# System binaries
@{sys} = /sys/

# Usage in profiles
owner @{HOME}/.config/myapp/** rw,
@{PROC}/sys/kernel/** r,

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

Podman with AppArmor

# Check if container has AppArmor profile
podman inspect --format='{{.AppArmorProfile}}' container_name

# Run with specific profile
podman run --security-opt apparmor=my-profile nginx

# Rootless containers
# AppArmor profiles may need adjustment for user namespaces

LXC/LXD with AppArmor

# LXD uses AppArmor by default
# View container profile
lxc config get container_name raw.apparmor

# Customize profile
lxc config set container_name raw.apparmor "..."

# Profiles stored in
/etc/apparmor.d/lxc/

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