AppArmor på Ubuntu 24.04: Skriva profiler, tolka DENIED och använda complain-läge
Praktisk guide till AppArmor på Ubuntu 24.04: skriv profiler med aa-genprof, tolka DENIED-loggar i dmesg, kör complain-läge säkert och undvik de vanligaste fallgroparna med abstractions.
AppArmor på Ubuntu 24.04 är den pathbaserade Linux Security Module (LSM) som körs som standard och begränsar processer utifrån filsystemsvägar snarare än inode-etiketter. För att skriva en fungerande profil startar du programmet under aa-genprof, kör dess normala arbetsflöde i complain-läge, godkänner de systemanrop verktyget föreslår och sätter sedan profilen i enforce-läge med aa-enforce. I den här guiden går jag igenom hela cykeln, från första aa-genprof-session till hur du tolkar DENIED-loggar i dmesg och undviker de vanligaste fallgroparna med abstractions (jag har snubblat på dem alla åtminstone en gång).
AppArmor är aktivt som standard på Ubuntu 24.04 LTS och laddar cirka 40 profiler vid boot – kontrollera med aa-status.
Profiler skrivs pathbaserat i /etc/apparmor.d/ och kompileras till en binär cache i /var/cache/apparmor/.
Complain-läge loggar överträdelser utan att blockera – använd det alltid innan du sätter en profil i enforce.
aa-genprof och aa-logprof läser /var/log/audit/audit.log eller dmesg och föreslår regler interaktivt.
Vanliga fallgropar är saknade abstractions (#include <abstractions/base>), obehandlad DBUS-mediation och Snap-paket som har sina egna profiler i /var/lib/snapd/apparmor/profiles/.
AppArmor och SELinux kan inte köras samtidigt – valet görs i kärnans lsm=-parameter vid boot.
Vad är AppArmor och varför valde Ubuntu det?
AppArmor är en obligatorisk åtkomstkontroll-modul (Mandatory Access Control) som implementerades ovanpå Linux Security Modules-ramverket 2007 och blev en del av mainline-kärnan i version 2.6.36. Till skillnad från traditionell diskretionär åtkomstkontroll – där ägaren av en fil bestämmer vem som får läsa den – begränsar AppArmor vad en process får göra oavsett vilken användare den körs som. En rot-process bunden till en restriktiv profil kan alltså inte läsa /etc/shadow om profilen inte tillåter det.
Ubuntu valde AppArmor framför SELinux redan 2007 av två skäl som fortfarande är relevanta 2026: modellen är pathbaserad, vilket gör den läsbar för administratörer utan att man behöver förstå ett komplett policyspråk, och profiler kan laddas per applikation snarare än som ett helt policytäcke över systemet. I praktiken innebär det att du kan hårdgöra nginx eller bind9 utan att först lära dig ett XACML-liknande regelsystem. Kärnans egen dokumentation beskriver LSM-hooks som AppArmor kopplar in sig på i kernel.org:s admin-guide för AppArmor.
Nackdelen är också pathbaserad: om ett program kan skapa hårda länkar till skyddade filer under en väg som profilen tillåter kan mediationen kringgås. Det är därför moderna profiler i Ubuntu 24.04 kombinerar path-regler med mount- och link-mediation, något jag återkommer till längre ned.
AppArmor vs SELinux: när passar vad?
Den vanligaste frågan jag får från Nordiska infrastrukturkunder som kör en blandad Ubuntu/RHEL-flotta är vilken LSM som är "bäst". Svaret är att de löser samma problem med olika filosofi, och valet styrs mest av vilken distribution du redan har och vilket team som ska underhålla policyn. Om du är van vid audit2allow-arbetsflödet kan du läsa min praktiska guide till SELinux på RHEL 10 för motsvarande cykel på Red Hat-sidan.
Egenskap
AppArmor (Ubuntu 24.04)
SELinux (RHEL 10)
Åtkomstmodell
Pathbaserad
Labelbaserad (inode-etiketter)
Standardläge
Enforce per profil
Enforce systemweit (targeted)
Läskurva
Låg – profiler liknar en allow-list
Hög – kräver förståelse av typer, roller, domäner
Verktyg för inlärning
aa-genprof, aa-logprof
audit2allow, sepolicy generate
Granularitet
Fil, capability, nätverk, mount, DBUS
Fil, port, boolean, MLS/MCS-etiketter
Container-integration
Docker/Podman via --security-opt apparmor=
Podman auto-etikettering via SELinux-boolean
Kringgåendes risk
Hårda länkar, symbolisk följning
Felkonfigurerade booleans, label-shifting
I praktiken blandar många organisationer inte de två. LSM-stackning i modern kärna tillåter att flera moduler laddas samtidigt, men major-moduler som AppArmor och SELinux är fortfarande ömsesidigt uteslutande – valet görs via kärnparametern lsm= i /etc/default/grub. Om du försöker aktivera SELinux på en Ubuntu-installation måste du därmed avlasta AppArmor först, och de flesta appspecifika profiler går förlorade.
Är AppArmor aktivt som standard i Ubuntu 24.04?
Ja, AppArmor är aktivt från första boot i Ubuntu 24.04 LTS "Noble Numbat" och laddar mellan 30 och 50 profiler beroende på installerade paket. Du bekräftar statusen med:
sudo aa-status
# 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.
# 11 processes are in enforce mode.
# 1 processes are in complain mode.
De profiler som laddas automatiskt kommer från paketet apparmor-profiles samt från paket som har egna profiler i /etc/apparmor.d/ – till exempel bind9, cups-daemon, tcpdump, ntpd och Firefox. Sedan Ubuntu 23.10 är dessutom kernel.apparmor_restrict_unprivileged_userns=1 satt som standard, vilket blockerar oprivilegierade user namespaces om inte processen har en explicit profil som tillåter det. Det är en direkt reaktion på de user-namespace-baserade priv-esc-kedjor som dominerade CVE-listorna under 2022–2024.
Vill du snabbt se vilka processer som körs unconfined använder du:
sudo aa-unconfined --paranoid | head -20
Flaggan --paranoid listar även processer som lyssnar på nätverket men saknar profil – det är ofta där du hittar dina största confinement-luckor. I en produktions-Ubuntu 24.04 med standardinstallation brukar den lista upp systemd-resolved, chronyd och lokala utvecklarverktyg. Alla dessa har profiler i paketet apparmor-profiles-extra som inte installeras som standard.
Skriva din första AppArmor-profil med aa-genprof
Så, det här är arbetsflödet jag använder när jag skriver en profil från grunden, och det är i princip alltid samma: kör programmet under aa-genprof, öva alla dess normala användningsfall i en separat terminal, svara på verktygets frågor, och iterera med aa-logprof tills DENIED-loggarna tystnar. Låt oss anta att vi ska hårdgöra ett litet Python-script i /usr/local/bin/backup-runner.
# Installera hjälpverktygen om de saknas
sudo apt install apparmor-utils
# Starta interaktiv profilgenerering
sudo aa-genprof /usr/local/bin/backup-runner
aa-genprof skapar då en tom profil i /etc/apparmor.d/usr.local.bin.backup-runner, sätter den i complain-läge och väntar. Kör nu skriptet i en annan terminal med samtliga argument det brukar användas med. När du är klar trycker du S (Scan) i aa-genprof-terminalen, och verktyget läser /var/log/audit/audit.log för att hitta överträdelser. För varje överträdelse presenteras alternativ:
A – Allow (skapa en regel som tillåter operationen)
D – Deny (explicit blockera – användbart för /etc/shadow etc.)
I – Inherit (kör child-process under samma profil)
C – Child (skapa en separat sub-profil för child-processen)
U – Unconfined (låt child köra utan profil – undvik i produktion)
För filåtkomster föreslår verktyget ofta en abstraction (#include <abstractions/base>, <abstractions/python>) framför explicita path-regler. Acceptera alltid abstractions när du kan – de underhålls av upstream och överlever distributions-uppgraderingar. Ett vanligt utfall efter en aa-genprof-session ser ut så här:
Notera regelmodifierare som owner (endast om processens euid matchar filens ägare), ix (inherit execute – child-processen ärver profilen) och k (får ta låskonflikt via flock()). Fullständig grammatik för profilspråket dokumenteras i AppArmor upstream-wikin.
Så tolkar du DENIED-loggar från kärnan
När en profil i enforce-läge blockerar en operation loggar kärnan händelsen via audit-subsystemet. På Ubuntu 24.04 hamnar de i /var/log/audit/audit.log om auditd är installerat, annars i dmesg och journalctl -k. En typisk rad ser ut så här:
operation – vad kärnan försökte göra: open, exec, mkdir, connect, mount, ptrace, capable
profile – vilken profil som blockerade (nyttigt när flera profiler är laddade)
name eller target – vilken resurs som efterfrågades
requested_mask – vilka permissions processen bad om (r, w, x, m för mmap)
denied_mask – vilka som blockerades (kan skilja sig från requested vid partiell blockering)
comm – programmets kommandonamn (avslöjar när fel binär snappats upp)
fsuid/ouid – effektivt filsystems-uid respektive filens ägar-uid (viktigt vid owner-regler)
Kör sudo aa-logprof för att låta verktyget föreslå regler baserat på loggen, precis som i aa-genprof-fasen. För komplexa fall där du behöver spåra en enda operation i realtid är aa-notify -s 1 -v användbart – det poppar upp desktop-notifikationer för varje DENIED och är det snabbaste sättet att fånga tillfälliga fel under integrationstester.
Om du använder centraliserad loggning kan du kombinera detta med runtime-detektion. Falco har inbyggd stöd för att korrelera AppArmor DENIED med processtart via eBPF – se min guide till eBPF-baserad runtime-säkerhet med Falco och Tetragon för hur du bygger den pipelinen.
Använda complain-läge för att lära in profiler säkert
Complain-läge (även kallat "learning mode") gör att profilen loggar överträdelser utan att faktiskt blockera dem. Det är det enda säkra sättet att rulla ut en ny eller ändrad profil i produktion, eftersom du annars riskerar att stoppa applikationen vid första oförutsedda syscall.
# Sätt en enskild profil i complain-läge
sudo aa-complain /etc/apparmor.d/usr.local.bin.backup-runner
# Efter en observationsperiod – granska loggen
sudo aa-logprof
# När loggen är tyst i minst 24h, sätt i enforce
sudo aa-enforce /etc/apparmor.d/usr.local.bin.backup-runner
# Verifiera
sudo aa-status | grep backup-runner
Min tumregel efter sex år med Livepatch-tooling på Canonical: låt en ny profil ligga i complain minst en full arbetsvecka innan du enforcar den. Cron-jobb som körs på månadsbasis, backupfönster på söndagsnätter och års-slut-processer är precis den typ av edge case som annars slår tillbaka första gången profilen möter verkligheten. För kritiska tjänster är två veckor rimligare.
Vanliga fallgropar med abstractions och tunables
Efter att ha granskat hundratals AppArmor-profiler hos konsultkunder ser jag ärligt talat samma tre misstag om och om igen. Det första körde jag själv på så sent som förra året. Det första är att glömma #include <abstractions/base>. Utan den tillåts inte grundläggande operationer som att läsa /etc/ld.so.cache, mappa in shared libraries eller anropa getrandom(2). Symptomet är en profil som fungerar när den skrevs men börjar krascha vid nästa libc-uppdatering.
Det andra misstaget är hårdkodade sökvägar där en tunable borde stå. Tunables i /etc/apparmor.d/tunables/ är variabler som @{HOME}, @{PROC} och @{sys} som expanderas vid laddning. En regel som /home/*/.ssh/id_rsa r, går sönder på system där hemkataloger ligger i /opt/users/. Skriv istället:
@{HOME}/.ssh/id_rsa r,
Det tredje och farligaste misstaget är att lita på path-regler för känsliga filer utan att också blockera hårda länkar. Om en profil tillåter /tmp/** rw, men blockerar /etc/shadow r,, kan en angripare potentiellt skapa en hård länk från /tmp/x till /etc/shadow och läsa den via den tillåtna vägen. Åtgärden är att aktivera link-mediation genom att lägga till l-permission på källfilerna, eller använda modern owner-modifikation för att kräva ägarskap.
DBUS-, mount- och nätverksmediation
Modern AppArmor på kärna 6.8+ (som Ubuntu 24.04 kör) mediererar långt mer än filsystem. De tre mediationstyper som oftast förbises är DBUS, mount och nätverk – och det är också där de intressanta escape-vägarna finns.
DBUS-mediation begränsar vilka bus-namn en process får äga och vilka metoder den får anropa. Ett vanligt mönster för desktop-applikationer:
Mount-mediation är avgörande för containerprofiler. AppArmor-profiler för Docker och Podman i Ubuntu 24.04 blockerar default mount-syscallen förutom några whitelistade fstypes:
mount fstype=(tmpfs, proc, sysfs, cgroup2) -> /**,
deny mount fstype=(nfs, cifs, fuse) -> /**,
umount /**,
Om du kör Podman rootless kompletterar AppArmor-profilen seccomp och user namespaces – jag har skrivit mer om den kombinationen i container escape-guiden för 2026.
Nätverksmediation begränsar vilka socket-familjer processen får skapa. Sedan 2023 stöder AppArmor även fin-granulär mediation per adress och port via network inet tcp bind=127.0.0.1:8080,-syntax, vilket eliminerar behovet av separat firewall-regler för lokala tjänster. Se Ubuntus officiella AppArmor-dokumentation för aktuell syntax och de tunables som Ubuntu-kärnan lägger till ovanpå upstream.
Integration med systemd och Snap-confinement
systemd och AppArmor är komplementära, inte konkurrenter (även om de överlappar en del). Enhetsfilens ProtectSystem=strict och PrivateTmp=yes ger snabb confinement på boundary-nivå, medan AppArmor styr detaljerna. Kombinera båda genom att sätta AppArmorProfile= i unit-filen:
Fördelen med AppArmorProfile= i unit-filen framför att förlita sig på path-baserad automatisk laddning är att profilen appliceras även om binären flyttas eller körs via en wrapper. Detta är särskilt viktigt för tjänster som spawnas via systemctl start [email protected] eller under snap-runtime.
Snap-paket har en separat AppArmor-integrering. Snapd genererar profiler dynamiskt utifrån snap:ens plugs och slots och lägger dem i /var/lib/snapd/apparmor/profiles/. Redigera dem aldrig manuellt – nästa snap refresh skriver över dem. Vill du lossa på en restriktion för ett snap använder du interfaces-kommandot istället:
Om en systemd-tjänst konsekvent fastnar i complain-läge trots att alla regler är på plats, kontrollera att profilen faktiskt laddades vid boot: systemctl status apparmor.service. Vid uppgradering av kärnan mellan LTS-versioner händer det att nya LSM-hooks introduceras som gamla profiler inte täcker – då genererar kärnan complain-loggar för operationer som tidigare var osynliga. Det är inte en regression, det är förbättrad mediation. Lös det genom att köra aa-logprof efter varje major kernel bump.
Vanliga frågor
Hur inaktiverar jag AppArmor för en enskild applikation?
Kör sudo aa-disable /etc/apparmor.d/din.profil. Kommandot skapar en symlänk från profilen till /etc/apparmor.d/disable/ och laddar om utan den. För att återaktivera: sudo aa-enforce /etc/apparmor.d/din.profil. Inaktivera aldrig hela AppArmor bara för att en profil klagar – sätt istället den specifika profilen i complain-läge medan du felsöker.
Vad är skillnaden mellan aa-genprof och aa-logprof?
aa-genprof startar från noll och skapar en ny profil interaktivt genom att övervaka en process du kör i en annan terminal. aa-logprof förutsätter att profilen redan finns och läser bara igenom nya DENIED-loggar sedan senaste körning för att föreslå tillägg. I praktiken använder du aa-genprof första gången och aa-logprof för alla efterföljande justeringar.
Kan jag köra AppArmor och SELinux samtidigt på Ubuntu?
Nej, inte som major-LSM. Kärnan tillåter bara en av AppArmor och SELinux åt gången eftersom båda registrerar sig som "exclusive" LSM. Du kan dock köra AppArmor tillsammans med "minor" LSM som Yama, Landlock och SafeSetID, vilket är standard i Ubuntu 24.04. Kombinationen ger djupförsvar utan konflikter.
Varför loggar min profil DENIED efter en kernel-uppgradering?
Varje ny stabil kärna kan lägga till LSM-hooks för syscalls som tidigare var omedierade. När kärnan börjar fråga AppArmor om en operation den inte kände till förut träffar den profilens implicita default-deny. Åtgärden är att köra sudo aa-logprof efter uppgraderingen och godkänna de nya reglerna – ofta räcker det att lägga till en modernare abstraction som #include <abstractions/nameservice-strict>.
Hur profilerar jag en tjänst som körs som en container?
Skapa profilen på värden och applicera den vid container-start med docker run --security-opt apparmor=namn eller podman run --security-opt apparmor=namn. Profilen måste vara laddad i värdens kärna – sudo apparmor_parser -r /etc/apparmor.d/din.profil. För Kubernetes 1.30+ används annotationen container.apparmor.security.beta.kubernetes.io/<container>: localhost/namn, som är stabil sedan v1.31 och heter numera bara appArmorProfile i podspecens securityContext.
Tobias spent six years on Canonical's kernel team in the LTS hardening group, mostly working on Livepatch tooling and the long tail of CVE backports nobody wants to do. He left in 2025 to consult independently with Nordic infrastructure customers running large Ubuntu fleets, where most of the work is AppArmor profiles, unattended-upgrades that don't break things, and explaining what kernel.unprivileged_userns_clone actually does.
He co-maintains a small open-source tool for diffing kernel config across distributions and contributes occasionally to the linux-hardening mailing list. His side project is a Yocto-based minimal image for industrial gateways that boots in under four seconds with full secure boot.
Tobias writes mostly about kernel-level security primitives - namespaces, seccomp, LSMs - with the assumption that you've already read the man page.
Praktisk guide till SELinux på RHEL 10 och AlmaLinux 10: läs AVC-denials, justera booleans, skriv policymoduler med audit2allow och sätt permanenta fil-kontexter. Med produktionsrekommendationer för containers, rootless Podman och CIS-härdning.