AppArmor: Profile Configuration

Introduction

AppArmor (Application Armor) is a Linux kernel security module that provides mandatory access control (MAC) through per-program profiles, offering an effective and user-friendly alternative to SELinux. Unlike traditional discretionary access control (DAC), AppArmor confines programs to a limited set of resources, significantly reducing the potential damage from security vulnerabilities or compromised applications.

Originally developed by Immunix and later acquired by Novell, AppArmor has become the default security framework for Ubuntu, SUSE Linux Enterprise, and several other distributions. Its profile-based approach makes it easier to configure and maintain compared to SELinux, while still providing robust application confinement and system protection.

This comprehensive guide covers everything you need to know about AppArmor profile configuration, from understanding basic concepts to creating custom profiles for your applications. Whether you're securing a web server, database, or custom application, this guide provides the knowledge and practical techniques needed to effectively implement AppArmor in production environments.

Understanding AppArmor and Security Context

What Is AppArmor?

AppArmor is a Mandatory Access Control (MAC) system implemented as a Linux Security Module (LSM) in the kernel. It restricts programs based on defined profiles that specify which files, capabilities, network access, and other resources each program can access.

How AppArmor Works

AppArmor operates using several key mechanisms:

1. Path-Based Security

Unlike SELinux which uses labels, AppArmor uses file paths to define access controls:

/usr/bin/application {
  /etc/application/config r,
  /var/log/application/** w,
  /home/*/.application/ rw,
}

2. Profiles

Each confined application has a profile that defines:

  • File access permissions (read, write, execute, append)
  • Network access (TCP, UDP, specific protocols)
  • Capability permissions (Linux capabilities like CAP_NET_ADMIN)
  • Resource limits
  • Child process execution

3. Operational Modes

AppArmor profiles can operate in two modes:

  • Enforce mode: Policy is enforced, violations are blocked and logged
  • Complain mode: Policy violations are logged but allowed (useful for profile development)

4. Include Directives

Profiles can include common abstractions:

#include <abstractions/base>
#include <abstractions/nameservice>

Why AppArmor Matters

AppArmor provides significant security benefits:

  • Application containment: Limits damage from compromised applications
  • Zero-day protection: Reduces exploit effectiveness even without patches
  • Easy configuration: Path-based policies are more intuitive than label-based systems
  • Low overhead: Minimal performance impact
  • Complain mode: Allows safe profile development in production
  • Compatibility: Works with standard Linux applications without modification

AppArmor vs. SELinux

FeatureAppArmorSELinux
Access ControlPath-basedLabel-based
Configuration ComplexityEasierMore complex
Default onUbuntu, SUSERHEL, CentOS, Fedora
Learning CurveModerateSteep
FlexibilityGoodExcellent
Policy DevelopmentSimplerMore powerful but complex

Common Use Cases

  • Confining web servers (Apache, Nginx)
  • Securing database servers (MySQL, PostgreSQL)
  • Protecting network services (OpenSSH, DNS, mail servers)
  • Isolating containerized applications
  • Restricting custom applications
  • Compliance with security frameworks

Prerequisites

Before configuring AppArmor profiles, ensure you have:

System Requirements

  • Operating System: Ubuntu, Debian, SUSE Linux Enterprise, openSUSE, or other AppArmor-enabled distribution
  • Kernel: Linux kernel 2.6.36 or later with AppArmor support
  • Root Access: Administrative privileges required
  • Disk Space: Adequate space for logs and profile files

Required Knowledge

  • Basic Linux system administration
  • Understanding of file permissions and paths
  • Command-line proficiency
  • Familiarity with system services
  • Basic understanding of Linux security concepts

Software Requirements

Ubuntu/Debian:

sudo apt-get update
sudo apt-get install apparmor apparmor-utils apparmor-profiles apparmor-profiles-extra

SUSE/openSUSE:

sudo zypper install apparmor-parser apparmor-profiles apparmor-utils

Verification:

sudo aa-status

Verify AppArmor is Active

Check AppArmor is loaded:

sudo systemctl status apparmor

Check kernel module:

sudo cat /sys/module/apparmor/parameters/enabled

Should return Y

View AppArmor filesystem:

sudo ls /sys/kernel/security/apparmor/

Step-by-Step AppArmor Profile Configuration

Step 1: Check AppArmor Status

View detailed AppArmor status:

sudo aa-status

Expected output:

apparmor module is loaded.
50 profiles are loaded.
40 profiles are in enforce mode.
   /sbin/dhclient
   /usr/bin/man
   /usr/lib/NetworkManager/nm-dhcp-client.action
   ...
10 profiles are in complain mode.
   /usr/bin/firefox
   /usr/sbin/mysqld
   ...
5 processes have profiles defined.
5 processes are in enforce mode.
0 processes are in complain mode.
0 processes are unconfined but have a profile defined.

Check if a specific profile is loaded:

sudo aa-status | grep nginx

View profile mode:

sudo aa-status --pretty-print

Step 2: Understand Profile Locations

System profile directories:

  • /etc/apparmor.d/: Main directory for profiles
  • /etc/apparmor.d/abstractions/: Reusable policy components
  • /etc/apparmor.d/tunables/: Variable definitions
  • /etc/apparmor.d/disable/: Disabled profiles
  • /etc/apparmor.d/local/: Local customizations

View available profiles:

ls -l /etc/apparmor.d/

View profile abstractions:

ls -l /etc/apparmor.d/abstractions/

Step 3: Read and Understand an Existing Profile

View an example profile (Nginx):

sudo cat /etc/apparmor.d/usr.sbin.nginx

Example profile structure:

#include <tunables/global>

/usr/sbin/nginx {
  #include <abstractions/base>
  #include <abstractions/nameservice>
  #include <abstractions/openssl>

  capability dac_override,
  capability net_bind_service,
  capability setgid,
  capability setuid,

  /usr/sbin/nginx mr,
  /etc/nginx/** r,
  /etc/ssl/openssl.cnf r,
  /var/log/nginx/* w,
  /var/www/html/** r,
  /run/nginx.pid rw,

  # Local customizations
  #include <local/usr.sbin.nginx>
}

Profile components explained:

  • #include <tunables/global>: Global variables
  • #include <abstractions/...>: Common policy components
  • capability: Linux capabilities the program needs
  • File paths with permissions:
    • r: read
    • w: write
    • m: memory map
    • x: execute
    • ix: inherit execute (inherit parent profile)
    • px: profile execute (use child's profile)
    • ux: unconfined execute (no profile)

Step 4: Generate a Profile Automatically

Use aa-genprof to create a new profile:

sudo aa-genprof /usr/bin/myapp

This launches an interactive profile generation tool:

  1. Start the application in another terminal
  2. Exercise all functionality of the application
  3. Return to aa-genprof terminal and press 'S' to scan logs
  4. Review suggested rules and accept/reject each
  5. Save the profile when complete

The tool will prompt for decisions:

Profile:  /usr/bin/myapp
Execute:  /etc/myapp/script.sh

(I)nherit / (P)rofile / (U)nconfined / (X) ix On / (D)eny / Abo(r)t / (F)inish
  • (I)nherit: Use parent profile
  • (P)rofile: Use separate profile
  • (U)nconfined: No restriction
  • (D)eny: Block access

Step 5: Create a Profile Manually

Create a basic profile for a custom application:

sudo nano /etc/apparmor.d/usr.local.bin.myapp

Basic profile template:

#include <tunables/global>

/usr/local/bin/myapp {
  #include <abstractions/base>
  #include <abstractions/nameservice>

  # Executable
  /usr/local/bin/myapp mr,

  # Configuration files
  /etc/myapp/** r,
  /etc/myapp/config rw,

  # Data directories
  /var/lib/myapp/** rw,

  # Log files
  /var/log/myapp/* w,
  /var/log/myapp/** rw,

  # Temporary files
  /tmp/myapp-* rw,
  /run/myapp.pid rw,

  # Capabilities
  capability dac_override,
  capability net_bind_service,

  # Network access
  network inet stream,
  network inet dgram,

  # Child processes
  /usr/bin/bash ix,
  /usr/bin/cat rix,

  # Local customizations
  #include <local/usr.local.bin.myapp>
}

Save and load the profile:

sudo apparmor_parser -r /etc/apparmor.d/usr.local.bin.myapp

Step 6: Switch Profiles Between Enforce and Complain Modes

Put a profile in complain mode:

sudo aa-complain /usr/sbin/nginx

Put a profile in enforce mode:

sudo aa-enforce /usr/sbin/nginx

Put all profiles in complain mode:

sudo aa-complain /etc/apparmor.d/*

Verify mode change:

sudo aa-status

Step 7: Test and Refine Profiles

Put profile in complain mode for testing:

sudo aa-complain /usr/local/bin/myapp

Monitor denials in real-time:

sudo tail -f /var/log/syslog | grep DENIED

Or on systems using journald:

sudo journalctl -f | grep DENIED

Exercise the application:

Test all functionality to generate comprehensive logs.

Use aa-logprof to update profile:

sudo aa-logprof

This tool:

  1. Analyzes logs for AppArmor denials
  2. Suggests additions to profiles
  3. Allows you to approve/reject each suggestion
  4. Updates profiles automatically

After refinement, switch to enforce mode:

sudo aa-enforce /usr/local/bin/myapp

Step 8: Disable or Remove Profiles

Disable a profile (move to disabled directory):

sudo aa-disable /usr/sbin/nginx

This creates a symlink in /etc/apparmor.d/disable/

Re-enable a disabled profile:

sudo aa-enforce /usr/sbin/nginx

Remove a profile completely:

sudo rm /etc/apparmor.d/usr.sbin.nginx
sudo apparmor_parser -R /etc/apparmor.d/usr.sbin.nginx

Unload a profile from kernel:

sudo aa-remove-unknown

Step 9: Create Local Customizations

Instead of modifying main profiles, use local includes:

sudo nano /etc/apparmor.d/local/usr.sbin.nginx

Add custom rules:

# Allow access to custom web directory
/srv/www/** r,

# Allow PHP-FPM socket
/run/php/php8.1-fpm.sock rw,

# Custom log location
/custom/logs/nginx/* w,

Reload the profile:

sudo apparmor_parser -r /etc/apparmor.d/usr.sbin.nginx

Local customizations survive package updates.

Advanced AppArmor Hardening Tips

1. Use Abstractions Effectively

Common useful abstractions:

#include <abstractions/base>              # Essential system files
#include <abstractions/nameservice>       # DNS, NSS lookups
#include <abstractions/ssl_certs>         # SSL certificate access
#include <abstractions/openssl>           # OpenSSL libraries
#include <abstractions/mysql>             # MySQL client
#include <abstractions/php>               # PHP execution
#include <abstractions/apache2-common>    # Apache common files

View abstraction contents:

cat /etc/apparmor.d/abstractions/base

2. Implement Network Restrictions

Restrict network access precisely:

# Allow only IPv4 TCP
network inet stream,

# Allow only IPv6 UDP
network inet6 dgram,

# Allow Unix domain sockets
network unix stream,

# Deny all other network
deny network,

Protocol-specific restrictions:

network inet stream tcp,
network inet dgram udp,
network netlink raw,

3. Use Profile Variables (Tunables)

Define variables in /etc/apparmor.d/tunables/myapp:

@{MYAPP_HOME}=/opt/myapp
@{MYAPP_CONFIG}=/etc/myapp
@{MYAPP_DATA}=/var/lib/myapp

Use in profiles:

#include <tunables/myapp>

/usr/local/bin/myapp {
  @{MYAPP_CONFIG}/** r,
  @{MYAPP_DATA}/** rw,
  @{MYAPP_HOME}/bin/* rix,
}

4. Implement Child Profile Transitions

Define child profile for scripts:

/usr/local/bin/myapp {
  # Main application rules
  /usr/local/bin/myapp mr,

  # Execute helper script with separate profile
  /usr/local/bin/helper.sh px -> myapp_helper,
}

profile myapp_helper {
  #include <abstractions/base>
  #include <abstractions/bash>

  /usr/local/bin/helper.sh r,
  /tmp/helper-* rw,
}

5. Use Conditional Rules

Platform-specific rules:

#include <tunables/global>

/usr/bin/myapp {
  #include <abstractions/base>

  # Common rules
  /usr/bin/myapp mr,

  # Conditional include based on distribution
  #include if exists <local/usr.bin.myapp>
}

6. Implement Deny Rules

Explicitly deny sensitive paths:

/usr/local/bin/myapp {
  #include <abstractions/base>

  # Normal access
  /etc/myapp/** r,

  # Explicitly deny sensitive files
  deny /etc/shadow r,
  deny /etc/gshadow r,
  deny /etc/ssh/ssh_host_* r,

  # Deny write to config
  deny /etc/myapp/secure.conf w,
}

7. Implement File Locking

Allow specific file operations:

/var/lib/myapp/database.db rwk,

Permissions:

  • r: read
  • w: write
  • k: lock
  • l: link

8. Profile Stacking for Containers

Create container-aware profiles:

profile docker-default flags=(attach_disconnected,mediate_deleted) {
  #include <abstractions/base>

  network,
  capability,
  file,
  umount,

  deny @{PROC}/* w,
  deny @{PROC}/{[^1]*,1/[^s]*,1/s[^y]*} w,
}

Verification and Testing

Verify Profile Syntax

Test profile syntax before loading:

sudo apparmor_parser -p /etc/apparmor.d/usr.local.bin.myapp

Dry-run profile loading:

sudo apparmor_parser -n -r /etc/apparmor.d/usr.local.bin.myapp

Test Profile Enforcement

Start application and check its confinement:

ps auxZ | grep myapp

Should show profile name instead of unconfined.

Check process context:

cat /proc/$(pidof myapp)/attr/current

Monitor Profile Violations

Real-time monitoring:

sudo tail -f /var/log/syslog | grep apparmor

Or with journald:

sudo journalctl -f | grep apparmor

Search for specific denials:

sudo grep DENIED /var/log/syslog | grep myapp

Analyze denials with aa-notify:

sudo aa-notify -p -f /var/log/syslog

Verify Profile Updates

After making changes, verify reload:

sudo apparmor_parser -r /etc/apparmor.d/usr.local.bin.myapp
sudo aa-status | grep myapp

Check profile cache:

ls -l /var/cache/apparmor/

Test Complete Application Functionality

Testing checklist:

  1. Start application and verify it runs
  2. Test all major features
  3. Check for denied operations in logs
  4. Verify child processes work correctly
  5. Test network connectivity if required
  6. Verify file access permissions
  7. Check capability requirements

Troubleshooting Common Issues

Issue 1: Profile Syntax Error

Symptoms: Parser errors when loading profile

Solutions:

  1. Check syntax:

    sudo apparmor_parser -p /etc/apparmor.d/usr.bin.myapp
    
  2. Common syntax issues:

    • Missing comma after permissions
    • Unclosed braces
    • Invalid permission characters
    • Incorrect path format
  3. Example correction:

    # Wrong
    /etc/myapp/config rw
    
    # Correct
    /etc/myapp/config rw,
    

Issue 2: Application Won't Start

Symptoms: Application fails to launch with AppArmor loaded

Solutions:

  1. Check denials immediately:

    sudo tail -20 /var/log/syslog | grep DENIED
    
  2. Put profile in complain mode:

    sudo aa-complain /usr/bin/myapp
    
  3. Start application and monitor:

    sudo journalctl -f | grep apparmor
    
  4. Update profile with aa-logprof:

    sudo aa-logprof
    
  5. Return to enforce mode after fixes:

    sudo aa-enforce /usr/bin/myapp
    

Issue 3: Permission Denied Errors

Symptoms: Application runs but fails specific operations

Solutions:

  1. Identify missing permission:

    sudo grep "myapp" /var/log/syslog | grep DENIED | tail -5
    
  2. Add to local profile:

    sudo nano /etc/apparmor.d/local/usr.bin.myapp
    
  3. Add the denied path/capability:

    /path/to/denied/file rw,
    
  4. Reload profile:

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

Issue 4: Profile Not Loading

Symptoms: Profile exists but not showing in aa-status

Solutions:

  1. Manually load profile:

    sudo apparmor_parser -a /etc/apparmor.d/usr.bin.myapp
    
  2. Check for errors:

    sudo systemctl status apparmor
    
  3. Verify profile isn't disabled:

    ls /etc/apparmor.d/disable/
    
  4. Restart AppArmor service:

    sudo systemctl restart apparmor
    

Issue 5: Child Process Failures

Symptoms: Main application works, but child processes fail

Solutions:

  1. Check for child process denials:

    sudo grep DENIED /var/log/syslog | grep exec
    
  2. Add appropriate execution rule:

    # Inherit parent profile
    /usr/bin/child ix,
    
    # Use child's profile
    /usr/bin/child px,
    
    # Unconfined execution (less secure)
    /usr/bin/child ux,
    
  3. Create separate child profile if needed:

    profile myapp_child {
      #include <abstractions/base>
      /usr/bin/child mr,
    }
    

Issue 6: Network Access Denied

Symptoms: Application cannot connect to network

Solutions:

  1. Check for network denials:

    sudo grep DENIED /var/log/syslog | grep network
    
  2. Add network permissions:

    network inet stream,
    network inet6 stream,
    
  3. For specific protocols:

    network inet stream tcp,
    network inet dgram udp,
    

Issue 7: Capability Denials

Symptoms: Operations requiring Linux capabilities fail

Solutions:

  1. Identify required capability:

    sudo grep "capability" /var/log/syslog | grep DENIED
    
  2. Add to profile:

    capability net_bind_service,
    capability sys_admin,
    
  3. Common capabilities:

    • dac_override: Bypass file permissions
    • net_bind_service: Bind to ports < 1024
    • setuid/setgid: Change user/group
    • sys_admin: Various admin operations

Best Practices for AppArmor Management

1. Profile Development Workflow

  1. Start in complain mode during development
  2. Exercise all functionality to capture complete access patterns
  3. Review and refine using aa-logprof
  4. Test thoroughly in complain mode
  5. Enable enforce mode after validation
  6. Monitor production for unexpected denials
  7. Iterate as needed when adding features

2. Profile Organization

  • Use abstractions for common patterns
  • Create local includes for customizations
  • Use variables for paths that may change
  • Document unusual rules with comments
  • Version control profile files
  • Organize by application or service type

3. Security Hardening

  • Principle of least privilege: Grant minimum necessary access
  • Explicit denials: Deny access to sensitive system files
  • Avoid unconfined execution: Use ix or px instead of ux
  • Restrict capabilities: Only grant required capabilities
  • Network restrictions: Limit network access by protocol/type
  • Regular audits: Review profiles quarterly

4. Operational Practices

  • Monitor regularly: Set up log monitoring for denials
  • Test updates: Test profile changes in non-production first
  • Backup profiles: Maintain backups before modifications
  • Documentation: Document why specific rules exist
  • Gradual deployment: Roll out new profiles incrementally

5. Performance Considerations

  • Profile caching: AppArmor caches compiled profiles for performance
  • Avoid excessive wildcards: More specific rules perform better
  • Use abstractions: Shared abstractions are cached
  • Monitor overhead: Generally minimal but monitor in high-performance environments

6. Compliance and Auditing

  • Profile inventory: Maintain list of all custom profiles
  • Change management: Track all profile modifications
  • Regular reviews: Audit profiles for unnecessary permissions
  • Compliance mapping: Document how profiles meet security requirements
  • Incident response: Include AppArmor logs in security investigations

7. Container Security

  • Default container profile: Use docker-default or custom profiles
  • Kubernetes integration: Use AppArmor annotations
  • Profile per container: Create specific profiles for container applications
  • Test in development: Verify profiles in development environments

Conclusion

AppArmor provides powerful, path-based mandatory access control that enhances Linux security through application confinement. Its intuitive profile syntax and complain mode make it more accessible than alternatives like SELinux, while still providing robust protection against compromised applications and security vulnerabilities.

Key takeaways:

  • Path-based approach: Easier to understand and configure than label-based systems
  • Complain mode: Invaluable for safe profile development and testing
  • Profile flexibility: Supports varying levels of restriction per application
  • Local customizations: Preserve customizations across updates
  • Performance: Minimal overhead with significant security benefits
  • Comprehensive tooling: aa-genprof, aa-logprof, and aa-status simplify management

By following the practices outlined in this guide, you implement effective application confinement that significantly reduces your attack surface. AppArmor is particularly valuable for:

  • Confining network-facing services
  • Protecting against zero-day exploits
  • Implementing defense-in-depth security
  • Meeting compliance requirements
  • Securing containerized applications

Remember that AppArmor is most effective when profiles are:

  • Developed with complete understanding of application behavior
  • Tested thoroughly in complain mode before enforcement
  • Regularly reviewed and updated as applications evolve
  • Monitored for denials that may indicate attacks or misconfigurations

Start with system-provided profiles for common applications, customize them to your needs using local includes, and gradually extend AppArmor protection to custom applications. With proper implementation and maintenance, AppArmor becomes an invaluable component of your security strategy, providing strong application isolation with manageable administrative overhead.