AppArmor Profielen Schrijven op Ubuntu en Debian: aa-genprof, aa-logprof en Troubleshooting in 2026

Stap-voor-stap AppArmor profielen schrijven op Ubuntu 24.04 en Debian 12 met aa-genprof en aa-logprof. Inclusief enforce mode, DENIED troubleshooting en systemd integratie.

AppArmor Profielen Ubuntu Debian Gids 2026

Bijgewerkt: 22 juni 2026

AppArmor is een Mandatory Access Control (MAC) systeem in de Linux-kernel dat per applicatie beperkt welke bestanden, capabilities en netwerksockets een proces mag aanraken via een tekstprofiel onder /etc/apparmor.d/. Op Ubuntu 24.04 LTS en Debian 12 staat AppArmor standaard ingeschakeld, en het schrijven van een eigen profiel kost meestal minder dan een uur per service als je de gereedschappen kent. In deze gids loop ik stap voor stap door een aanval op een ongeprotegeerde service, en daarna koppel ik elke aanvalstap aan een AppArmor-regel die hem stopt.

  • AppArmor werkt op basis van padgebaseerde regels, in tegenstelling tot SELinux dat labels gebruikt. Dat maakt profielen leesbaarder, maar wel gevoelig voor bind-mounts en symlinks.
  • Gebruik aa-genprof om een nieuw profiel te genereren door de applicatie in een tweede shell te draaien terwijl de tool meekijkt naar audit logs.
  • Complain mode logt overtredingen zonder ze te blokkeren; enforce mode handhaaft de regels. Schakel altijd via complain naar enforce na minimaal 24 uur productieverkeer.
  • DENIED-meldingen verschijnen in /var/log/audit/audit.log of via journalctl -k; aa-logprof leest ze in en stelt regelaanpassingen voor.
  • Integreer AppArmor met systemd via de directieve AppArmorProfile= in de unit file om een service direct in een profiel te starten.
  • Combineer AppArmor met seccomp-filters en capability dropping voor defence-in-depth. Geen enkele laag is op zichzelf genoeg.

Waarom AppArmor in 2026 nog steeds relevant is

Eerlijk gezegd zie ik als pentester nog steeds servers waarbij een gecompromitteerde Node.js worker direct /etc/shadow kan lezen, alleen omdat niemand het service-account een MAC-profiel heeft gegeven. AppArmor lost dit op met een kleine leercurve: één tekstbestand per applicatie, gemiddeld 50 tot 150 regels, en je hebt het verschil tussen "remote code execution" en "remote code execution die nergens komt".

De grote AppArmor-update in kernel 6.6 LTS bracht ondersteuning voor unprivileged user namespaces in profielen en uitgebreide netwerkregels per port en adres. Ubuntu 24.04 LTS verzendt AppArmor 4.0.1, Debian 12 verzendt 3.0.8, en beide zijn productieklaar. Volgens de officiële AppArmor wiki draait minstens 60% van de actieve Ubuntu-servers met minstens één enforced profiel, meestal snapd of libvirtd.

Concurrenten als SELinux bieden vergelijkbare bescherming maar vragen een veel steilere leercurve. AppArmor is goed genoeg voor 95% van de bedreigingen die ik in penetratietesten tegenkom, vooral wanneer je het combineert met een goede nftables-firewall en sysctl-kernelhardening.

AppArmor installeren en status controleren

Op Ubuntu 24.04 en Debian 12 is AppArmor standaard geïnstalleerd en geladen, maar de utilities heb je apart nodig. Installeer ze even:

sudo apt update
sudo apt install -y apparmor apparmor-utils apparmor-profiles apparmor-profiles-extra auditd

Controleer dan of AppArmor draait en welke profielen geladen zijn:

# Kernel-module actief?
sudo aa-status

# Voorbeeld output:
# apparmor module is loaded.
# 47 profiles are loaded.
# 32 profiles are in enforce mode.
# 15 profiles are in complain mode.
# 12 processes have profiles defined.
# 9 processes are in enforce mode.

Krijg je geen output, of zie je "apparmor filesystem is not mounted"? Dan moet je AppArmor in de kernel command line activeren. Bewerk /etc/default/grub:

GRUB_CMDLINE_LINUX_DEFAULT="quiet splash apparmor=1 security=apparmor"

Werk vervolgens grub bij met sudo update-grub en herstart. Dit is zelden nodig op moderne installaties, maar wel cruciaal bij minimal-images, custom kernels en sommige VPS-providers die de boot-parameters strippen.

Aanvalsscenario: een Python-webhook zonder profiel

Voordat we een profiel schrijven, laat ik even zien waarom je het nodig hebt. Stel: een interne webhook draait als systemd-service onder de user webhook, met de volgende Python-code:

from flask import Flask, request
import subprocess
app = Flask(__name__)

@app.post("/run")
def run():
    cmd = request.json.get("cmd", "echo hello")
    # Kwetsbaar: command injection
    return subprocess.check_output(cmd, shell=True).decode()

app.run(host="127.0.0.1", port=8080)

Een aanvaller die deze endpoint bereikt kan elk shellcommando uitvoeren. Zonder MAC-profiel betekent dat:

  1. Recon: curl -d '{"cmd":"cat /etc/passwd"}' ... lekt alle gebruikersnamen.
  2. Privilege escalation hunt: find / -perm -4000 2>/dev/null vindt SUID-binaries.
  3. Persistence: schrijf een SSH-key naar ~webhook/.ssh/authorized_keys.
  4. Lateral movement: de webhook-process bind een reverse shell op poort 4444.

Stappen 1, 2, 3 en 4 zijn allemaal te stoppen met een correct AppArmor-profiel. Stap 1 stop je door /etc/passwd niet leesbaar te maken in het profiel. Stap 2 stop je door de capability dac_read_search uit te sluiten. Stap 3 stop je door schrijfrechten op de homedir te weigeren. Stap 4 stop je door network inet stream alleen voor localhost toe te staan.

Hoe maak ik een eigen AppArmor profiel met aa-genprof?

Om een AppArmor profiel te maken, start je aa-genprof in één terminal en draai je de applicatie in een tweede; de tool leest live audit-logs en stelt regels voor op basis van wat de applicatie écht doet. Dit is veel betrouwbaarder dan handmatig vanaf nul beginnen (geloof me, ik heb het in mijn vorige project geprobeerd en bij elke deploy een nieuw gat ontdekt).

Eerste stap: zorg dat auditd draait, anders heeft aa-genprof geen logs om te lezen:

sudo systemctl enable --now auditd

Start nu het profielgeneratieproces voor onze webhook (pad naar het uitvoerbare bestand):

sudo aa-genprof /usr/local/bin/webhook-server

De tool maakt een leeg profiel aan onder /etc/apparmor.d/usr.local.bin.webhook-server en zet het in complain mode. In een tweede terminal start je nu de applicatie en doorloop je een representatieve workload: maak HTTP-requests, lees configuratie, verbind met de database, schrijf logs. Hoe completer deze doorloop, hoe vollediger het profiel.

Terug in de eerste terminal druk je op S (Scan). De tool toont elke kernel-audit-entry en vraagt per geval wat je wilt doen: Allow, Deny, Glob, Abstraction. Voor typische webapps kies je vaak voor abstractions zoals <abstractions/base>, <abstractions/python>, en <abstractions/openssl>. Dat scheelt tientallen handmatige regels.

AppArmor profielsyntaxis: abstractions, capabilities en bestandsregels

Een minimaal werkend profiel voor onze Python-webhook ziet er na opschonen zo uit. Bewaar dit als /etc/apparmor.d/usr.local.bin.webhook-server:

#include <tunables/global>

/usr/local/bin/webhook-server {
  #include <abstractions/base>
  #include <abstractions/python>
  #include <abstractions/openssl>
  #include <abstractions/nameservice>

  # Capabilities (alleen wat we echt nodig hebben)
  capability net_bind_service,
  # Geen dac_read_search, geen sys_admin, geen setuid

  # Het programma zelf
  /usr/local/bin/webhook-server r,
  /usr/bin/python3.12 ix,

  # Python runtime
  /usr/lib/python3.12/** r,
  /usr/lib/python3/dist-packages/** r,
  /opt/webhook/venv/** r,
  /opt/webhook/venv/bin/python3 ix,

  # Configuratie en logs
  /etc/webhook/config.yaml r,
  /var/log/webhook/*.log w,

  # Tijdelijke bestanden in eigen tmp
  owner /tmp/webhook-*.sock rw,

  # Netwerk: alleen TCP en alleen op localhost-bereik
  network inet stream,
  network inet6 stream,

  # Expliciet weigeren wat we nooit nodig hebben
  deny /etc/shadow r,
  deny /etc/sudoers r,
  deny /root/** rwx,
  deny owner /home/*/.ssh/** rwx,
  deny capability sys_ptrace,
  deny capability sys_module,
}

Belangrijke syntaxisregels:

  • Padgebaseerd: regels matchen tegen het bestandspad, inclusief globs (*, **, ?).
  • Permissies: r read, w write, x execute, m memory map executable, k file locking, l hardlink.
  • Execute modes: ix inherit (kindproces blijft in zelfde profiel), Px nieuw profiel, Cx child profile, ux unconfined (gevaarlijk).
  • owner-prefix: regel geldt alleen als het proces eigenaar is van het bestand. Handig voor multi-user systemen.
  • deny: expliciete weigering, overstijgt allows. Gebruik dit voor defense-in-depth.

Laad het profiel en zet het in enforce mode:

sudo apparmor_parser -r /etc/apparmor.d/usr.local.bin.webhook-server
sudo aa-enforce /etc/apparmor.d/usr.local.bin.webhook-server
sudo systemctl restart webhook
sudo aa-status | grep webhook

Wat is het verschil tussen complain en enforce mode?

In complain mode staat AppArmor alle acties toe en logt overtredingen alleen als audit-events. Dit is ideaal voor het bouwen en testen van profielen, want je breekt niets, maar je ziet wel exact wat het profiel zou blokkeren. In enforce mode blokkeert AppArmor actief elke actie die niet expliciet in het profiel staat en logt de poging.

Schakelen gaat met deze commando's:

# Zet één profiel in complain mode
sudo aa-complain /etc/apparmor.d/usr.local.bin.webhook-server

# Zet één profiel in enforce mode
sudo aa-enforce /etc/apparmor.d/usr.local.bin.webhook-server

# Schakel alle profielen tegelijk
sudo aa-complain /etc/apparmor.d/*
sudo aa-enforce /etc/apparmor.d/*

Mijn deployment-workflow is altijd hetzelfde: bouw het profiel met aa-genprof, draai 24 tot 72 uur in complain mode terwijl je aa-logprof regelmatig draait om nieuwe events te incorporeren, en pas dan schakel je naar enforce. Sla deze stap echt niet over. Een te restrictief profiel in enforce dat in een onverwacht codepad terechtkomt veroorzaakt productie-incidenten die moeilijk te debuggen zijn.

Hoe troubleshoot ik AppArmor DENIED meldingen?

Om AppArmor DENIED meldingen te troubleshooten lees je de audit-logs met journalctl -k | grep apparmor of ausearch -m AVC; gebruik vervolgens aa-logprof om de regels interactief aan te vullen. Een DENIED-entry ziet er typisch zo uit:

kernel: audit: type=1400 audit(1718990400.123:456):
  apparmor="DENIED" operation="open"
  profile="/usr/local/bin/webhook-server"
  name="/etc/ssl/private/server.key" pid=12345
  comm="webhook-serve" requested_mask="r" denied_mask="r"
  fsuid=1001 ouid=0

Lees deze entry zo:

  • profile: welk profiel actief was bij de overtreding.
  • operation + name: wat het proces probeerde te doen en op welk pad.
  • requested_mask: welke permissie het nodig had (hier r).
  • denied_mask: welke permissie geweigerd werd.

Drie redenen waarom je zo'n DENIED kunt zien, met elk een andere actie:

  1. Legitiem ontbrekende regel: de applicatie heeft die toegang echt nodig. Voeg een regel toe via aa-logprof.
  2. Aanval of misconfiguratie: de applicatie hoort dit pad niet aan te raken. Onderzoek waarom het toch gebeurt. Vaak vind je hier een gecompromitteerde dependency of een misgeplaatste env-variabele.
  3. Pad-mismatch door symlinks of bind mounts: AppArmor evalueert resolved paths. Als je /var/log als bind mount naar /srv/logs hebt, moet je beide paden in het profiel zetten.

Draai vervolgens aa-logprof om de logs interactief in te lezen:

sudo aa-logprof

# De tool toont elk DENIED-event en vraagt:
# (A)llow / (D)eny / (I)gnore / (G)lob / abstraction / (Q)uit
# Maak goede keuzes. Allow op /etc/shadow is een rode vlag.

AppArmor vs SELinux: welke moet ik kiezen?

Beide zijn Linux MAC-systemen, maar de filosofie verschilt fundamenteel. AppArmor is padgebaseerd en gericht op leesbaarheid; SELinux is labelgebaseerd en gericht op fijnmazigheid. Voor de meeste organisaties is de praktische keuze al gemaakt door de distributiekeuze: Ubuntu en Debian leveren AppArmor, RHEL en Fedora leveren SELinux.

EigenschapAppArmorSELinux
ModelPad-gebaseerdLabel-gebaseerd (Type Enforcement)
Standaard opUbuntu, Debian, SUSERHEL, Fedora, CentOS Stream, AlmaLinux
LeercurveLaag (profielen zijn leesbaar)Hoog (vereist begrip van TE, MLS, MCS)
Profielen genererenaa-genprof interactiefaudit2allow + handmatige policy edits
Symlink-resistentieZwak (pad kan veranderen)Sterk (label volgt het bestand)
GranulariteitBestand/pad-niveauPer-inode, per-context, per-rol
Container-ondersteuningPodman, LXC, DockerPodman, containerd (default in CRI-O)
Geschikt voorWeb-apps, microservices, desktopsMulti-tenant, hoge compliance (FedRAMP, DoD)

In de praktijk: kies AppArmor als je team beperkte tijd heeft voor security-tooling en je primaire bedreigingsmodel verkeer-van-internet is. Kies SELinux als je multi-tenant draait, compliance-eisen je policy-granulariteit dwingen, of als je workloads echte Type Enforcement nodig hebben tussen processen die elkaars bestanden kunnen aanraken.

AppArmor en systemd integreren

Veel services laden hun profiel pas wanneer ze het uitvoerbare bestand aanroepen. Voor systemd-units kun je het profiel expliciet koppelen met AppArmorProfile=, wat zorgt dat systemd weigert de service te starten als het profiel niet geladen is. Dit voorkomt een veelvoorkomende foutklasse: profiel uitgeschakeld door een operator, service draait in unconfined mode zonder dat iemand het merkt.

[Unit]
Description=Internal Webhook Service
After=network.target

[Service]
Type=simple
User=webhook
Group=webhook
ExecStart=/usr/local/bin/webhook-server
Restart=on-failure

# AppArmor binding: service start niet zonder profiel
AppArmorProfile=/usr/local/bin/webhook-server

# Defense-in-depth: combineer met systemd sandbox-directieven
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
PrivateDevices=yes
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
SystemCallFilter=@system-service
SystemCallFilter=~@privileged @resources

[Install]
WantedBy=multi-user.target

Voor diepere systemd-hardening, zie mijn eerdere gids over systemd service hardening met sandbox-directieven. De combinatie van een streng AppArmor-profiel en systemd's sandbox-opties is wat ik in productie aanraad: AppArmor handhaaft de bestandsspecifieke regels, systemd handhaaft de namespace- en syscall-isolatie, en beide werken onafhankelijk zodat één bypass de andere niet doorbreekt.

Hoe schakel ik AppArmor uit voor één applicatie?

Om AppArmor voor één applicatie uit te schakelen zonder het hele systeem te raken, gebruik je aa-disable. Dit unloads het profiel en plaatst een symlink in /etc/apparmor.d/disable/ zodat het ook na een reboot uitgeschakeld blijft.

sudo aa-disable /etc/apparmor.d/usr.local.bin.webhook-server

# Verifieer
sudo aa-status | grep webhook
# Geen output betekent: niet meer geladen.

# Later weer inschakelen
sudo rm /etc/apparmor.d/disable/usr.local.bin.webhook-server
sudo apparmor_parser -r /etc/apparmor.d/usr.local.bin.webhook-server

Doe dit alleen als laatste redmiddel. In bijna alle gevallen is een te restrictief profiel beter op te lossen door extra regels toe te voegen met aa-logprof dan door het profiel helemaal uit te schakelen. Een service zonder MAC-profiel die compromised raakt is een gratis foothold voor laterale beweging, en dat is exact het probleem dat we met dit hele artikel willen vermijden.

Voor de officiële profielsyntax-referentie zie de Ubuntu AppArmor documentatie en de Debian AppArmor wiki voor distributie-specifieke verschillen.

Veelgestelde vragen

Hoe weet ik welk AppArmor profiel een proces gebruikt?

Gebruik cat /proc/<pid>/attr/current om het actieve profiel en de mode voor een specifiek PID te zien. Voor een systeembreed overzicht draai sudo aa-status, dat alle processen toont met een geladen profiel plus hun mode (enforce, complain, unconfined).

Kan AppArmor mijn applicatie breken na een upgrade?

Ja, vooral bij grote versie-upgrades die nieuwe bestandspaden of libraries introduceren. Schakel het profiel naar complain mode voor de upgrade, voer de upgrade uit, draai een testronde, gebruik aa-logprof om nieuwe regels toe te voegen, en schakel pas dan terug naar enforce.

Werkt AppArmor met Docker en Podman containers?

Ja. Docker laadt standaard het docker-default profiel voor elke container, en Podman ondersteunt AppArmor via de --security-opt apparmor=<profile> vlag. Voor productie-containers raad ik aan een eigen profiel per image te schrijven in plaats van te vertrouwen op het generieke standaardprofiel.

Hoeveel performance-impact heeft AppArmor?

In benchmarks van het AppArmor-team op kernel 6.6 is de overhead minder dan 1% voor typische webserver-workloads en 2 tot 4% voor IO-intensieve workloads met veel kleine bestandsoperaties. Voor de meeste productiesystemen is dit verwaarloosbaar tegenover de security-winst.

Kan ik AppArmor en SELinux tegelijk draaien op dezelfde server?

Nee, niet praktisch. De Linux kernel ondersteunt sinds 5.1 LSM-stacking maar in distributies is slechts één hoofdsysteem standaard actief. Ubuntu draait AppArmor, RHEL draait SELinux. Het mengen vereist een custom kernel-build en wordt voor productie afgeraden.

Felix Lindqvist
Over de Auteur Felix Lindqvist

Penetration tester and OSCP holder. Reverse engineers misconfigured servers for a living and writes about what he finds.