Logo

AppArmor: Linux process confinement by profile

AppArmor: Linux process confinement by profile

Limit what your services can do on Linux with AppArmor. Mandatory Access Control (MAC) system based on per-binary profiles. Easier to tame than SELinux, pre-installed on Ubuntu and Debian.

Introduction

By default on Linux, a process runs with the rights of the user who launched it: Nginx as www-data can read all of /etc, write to /tmp, open any port. If Nginx is compromised, the attacker has access to all that.

AppArmor restricts each process to what it actually needs via profiles that list allowed files and capabilities. If Nginx is compromised, the attacker can only access /var/www, read its certs and listen on 80/443. Nothing else.

Advantages vs SELinux: readable syntax, path-based, ships with Ubuntu/Debian, ready-made profiles for common services.

Prerequisites

  • Ubuntu / Debian (AppArmor preinstalled)
  • Root access
  • Basic Linux CLI

Step 1: Check AppArmor is active

sudo systemctl status apparmor
sudo aa-status

Typical output:

apparmor module is loaded.
35 profiles are loaded.
30 profiles are in enforce mode.
5 profiles are in complain mode.
12 processes have profiles defined.

If not installed:

sudo apt install -y apparmor apparmor-utils apparmor-profiles
sudo systemctl enable --now apparmor

Step 2: AppArmor modes

3 modes per profile:

  • enforce: blocks + logs (prod mode)
  • complain: logs only, doesn't block (test mode)
  • disabled: profile loaded but inactive
sudo aa-complain /etc/apparmor.d/usr.sbin.nginx
sudo aa-enforce /etc/apparmor.d/usr.sbin.nginx
sudo aa-disable /etc/apparmor.d/usr.sbin.nginx

Step 3: List existing profiles

ls /etc/apparmor.d/
sudo apt install -y apparmor-profiles apparmor-profiles-extra

Step 4: Anatomy of a profile

#include <tunables/global>

/usr/sbin/nginx {
    #include <abstractions/base>
    
    capability net_bind_service,
    capability setgid,
    capability setuid,

    /etc/nginx/** r,
    /etc/ssl/certs/** r,
    /var/log/nginx/*.log w,
    /var/www/** r,
    /run/nginx.pid w,
    
    network inet stream,
    network inet6 stream,
}

Legend: r read, w write, m mmap, k lock, x execute, ix inherit, Px exec with own profile.

Step 5: Create a profile for a new service

sudo aa-genprof /opt/downloader/app.py

In another terminal, run the script. AppArmor asks for each access.

Or manual:

sudo nano /etc/apparmor.d/opt.downloader.app
#include <tunables/global>

/opt/downloader/app.py {
    #include <abstractions/base>
    #include <abstractions/python>
    #include <abstractions/openssl>
    
    /usr/bin/python3 ix,
    /opt/downloader/** r,
    /srv/downloads/** rw,
    
    network inet stream,
    network inet6 stream,
}
sudo apparmor_parser -r /etc/apparmor.d/opt.downloader.app
sudo aa-enforce /etc/apparmor.d/opt.downloader.app
  1. sudo aa-complain /opt/downloader/app.py
  2. Run the app normally for a few days
  3. sudo aa-logprof to add missing permissions
  4. sudo aa-enforce /opt/downloader/app.py

Step 7: View logged violations

sudo dmesg | grep DENIED
sudo journalctl -k | grep apparmor

Example:

apparmor="DENIED" operation="open" profile="/opt/downloader/app.py" name="/etc/shadow"

Step 8: Profiles for Docker

sudo nano /etc/apparmor.d/docker-nginx
#include <tunables/global>

profile docker-nginx flags=(attach_disconnected,mediate_deleted) {
    #include <abstractions/base>
    network inet tcp,
    /etc/nginx/** r,
    /var/log/nginx/** w,
    /var/cache/nginx/** rw,
    /var/www/** r,
    deny /proc/sysrq-trigger w,
}
sudo apparmor_parser -r /etc/apparmor.d/docker-nginx
docker run --security-opt apparmor=docker-nginx nginx

Step 9: Real-world case — protect vsftpd

#include <tunables/global>

/usr/sbin/vsftpd {
    #include <abstractions/base>
    #include <abstractions/nameservice>
    
    capability net_bind_service,
    capability setuid,
    capability setgid,
    
    /etc/vsftpd.conf r,
    /srv/ftp/** rw,
    /var/log/vsftpd.log w,
    
    network inet stream,
    
    deny /etc/shadow r,
    deny /root/** rwx,
    deny /home/** rwx,
}

Even if vsftpd is compromised, attacker can't read /home, /root, or /etc/shadow.

Step 10: Common abstractions

In /etc/apparmor.d/abstractions/:

  • base: essential libc, DNS
  • nameservice: DNS, /etc/hosts
  • python: standard Python libs
  • openssl: crypto
  • nginx: Nginx needs
  • apache2-common: Apache
  • ssl_certs: read certs

Step 11: Reload after changes

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

Step 12: Disable AppArmor (avoid)

sudo systemctl stop apparmor
sudo systemctl disable apparmor

⚠️ Never leave disabled in prod on exposed services.

Troubleshooting

Service won't start after enforce

sudo dmesg | grep DENIED | tail -20
sudo aa-complain /etc/apparmor.d/usr.sbin.servicename

"Failed to apply profile"

sudo apparmor_parser -Q /etc/apparmor.d/usr.sbin.servicename

Profile doesn't apply

which nginx
# Profile must list the correct binary path

Useful commands

sudo aa-status                                    # overall status
sudo aa-logprof                                   # suggest rules from logs
ps auxZ | grep nginx                              # see if confined
sudo aa-unconfined                                # unconfined processes
sudo apparmor_parser -r /etc/apparmor.d/profile   # reload one
sudo aa-mergeprof                                 # merge DENIED into rules

Conclusion

AppArmor adds a major defense layer to your Linux:

  • Per-binary confinement
  • Complain mode to test without breaking
  • Ready profiles for common services
  • Combinable with Docker, systemd

Going further: combine with seccomp, use bpftrace for live observation, migrate to SELinux on RedHat / CentOS.

Resources

Join our Discord community server

For any questions, suggestions, or just to chat with the community, join us on Discord!

900+Members