Mandatory Access Control im Vergleich
Standard-Unix-Rechte stoppen einen kompromittierten Prozess nicht daran, alles zu lesen, was der Benutzer lesen darf. Mandatory Access Control setzt eine zweite, vom Kernel erzwungene Schranke davor. Dieser Artikel vergleicht SELinux und AppArmor, erklärt labelbasierte und pfadbasierte Policy an echten Beispielen und zeigt, wie man einen MAC-bedingten Permission-Denied zuverlässig diagnostiziert.
Inhaltsverzeichnis
- 1. Was Mandatory Access Control über Unix-Rechte hinaus leistet
- 2. SELinux: Labels, Kontexte und Policy-Typen
- 3. AppArmor: pfadbasierte Profile und Modi
- 4. Architekturvergleich: Label vs. Pfad
- 5. Diagnose: SELinux-bedingtes Permission-Denied finden
- 6. Diagnose: AppArmor-bedingtes Permission-Denied finden
- 7. Policy anpassen statt Enforcement pauschal abschalten
- 8. Enforcing, Permissive oder Disabled: die Entscheidung pro Umgebung
- 9. SELinux und AppArmor im direkten Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Was Mandatory Access Control über Unix-Rechte hinaus leistet
Klassische Unix-Rechte (rwx, Owner, Gruppe, Sonstige) sind Discretionary Access Control: Der Besitzer einer Datei entscheidet eigenmächtig, wer darauf zugreifen darf, und jeder Prozess, der unter diesem Benutzer läuft, erbt automatisch alle Rechte des Benutzers. Wird ein Webserver-Prozess kompromittiert, kann der Angreifer im Rahmen der Rechte des www-data-Benutzers auf alle Dateien zugreifen, die dieser Benutzer lesen darf, selbst wenn diese Dateien fachlich nichts mit dem Webserver zu tun haben. Genau diese Lücke schließt Mandatory Access Control (MAC).
MAC fügt eine zweite, vom Kernel erzwungene Kontrollschicht hinzu, die nicht vom Dateibesitzer, sondern von einer zentralen Sicherheitsrichtlinie definiert wird. Selbst wenn ein Prozess laut Unix-Rechten Zugriff haben dürfte, kann die MAC-Policy diesen Zugriff zusätzlich verweigern. Ein kompromittierter Nginx-Prozess mit vollem root-Unix-Recht kann durch eine passende SELinux- oder AppArmor-Policy trotzdem daran gehindert werden, /etc/shadow zu lesen oder eine neue Shell zu starten. Diese Eindämmung, auch Confinement genannt, ist der zentrale Mehrwert von MAC-Systemen gegenüber reinen Unix-Berechtigungen.
Beide großen Linux-MAC-Implementierungen, SELinux und AppArmor, setzen auf demselben Kernel-Mechanismus auf: den Linux Security Modules (LSM), einer Hook-Schicht, die vor jedem sicherheitsrelevanten Systemaufruf zusätzliche Prüfungen erlaubt. Der grundlegende Unterschied liegt nicht in der Kernel-Anbindung, sondern in der Art, wie Objekte identifiziert und Regeln formuliert werden, wie die folgenden Abschnitte zeigen.
2. SELinux: Labels, Kontexte und Policy-Typen
SELinux (Security-Enhanced Linux), ursprünglich von der NSA entwickelt und Standard auf RHEL, CentOS, Fedora und Rocky Linux, arbeitet labelbasiert. Jede Datei, jeder Prozess, jeder Port und jedes Verzeichnis erhält einen Sicherheitskontext in der Form user:role:type:level, zum Beispiel system_u:object_r:httpd_sys_content_t:s0. Die Policy-Entscheidung basiert fast ausschließlich auf dem Type-Feld: Ein Prozess mit Typ httpd_t darf auf Dateien mit Typ httpd_sys_content_t zugreifen, weil eine entsprechende Regel dies erlaubt, unabhängig vom tatsächlichen Dateipfad.
Diese Type Enforcement genannte Grundregel bedeutet, dass ein Label an der Datei hängt, nicht am Pfad. Wird eine Datei mit mv in ein anderes Verzeichnis verschoben, behält sie ihr altes Label, was oft zu unerwarteten Permission-Denied-Fehlern führt, während cp das Label meist neu vom Zielverzeichnis erbt. Die Labels selbst werden im erweiterten Dateisystemattribut security.selinux gespeichert und sind daher an das Dateisystem gebunden, nicht an den Kernel-Prozess.
SELinux kennt zwei Policy-Typen: targeted, die auf RHEL-Systemen Standard ist und nur ausgewählte, netzwerkexponierte Dienste einschränkt, und strict, die jeden Prozess im System einschränkt und in der Praxis kaum noch eingesetzt wird, weil der Pflegeaufwand zu hoch ist. Der Betriebsmodus wird getrennt davon in /etc/selinux/config festgelegt: enforcing blockiert Verstöße aktiv, permissive protokolliert sie nur, und disabled schaltet SELinux vollständig ab.
# Check current SELinux mode and policy type
sestatus
# SELinux status: enabled
# SELinuxfs mount: /sys/fs/selinux
# Current mode: enforcing
# Loaded policy name: targeted
# Inspect the security context of a running process
ps -eZ | grep nginx
# system_u:system_r:httpd_t:s0 1204 ? 00:00:03 nginx
# Inspect the security context of files
ls -lZ /var/www/html/index.php
# -rw-r--r--. www-data www-data system_u:object_r:httpd_sys_content_t:s0 index.php
# A file moved with mv keeps its old, now wrong label
mv /home/deploy/upload.php /var/www/html/upload.php
ls -lZ /var/www/html/upload.php
# ...unconfined_u:object_r:user_home_t:s0 upload.php <- wrong type, will be denied
# Fix: relabel according to the policy's default file context
restorecon -v /var/www/html/upload.php
3. AppArmor: pfadbasierte Profile und Modi
AppArmor, Standard auf Ubuntu, Debian und SUSE, verfolgt einen fundamental anderen Ansatz: Es ist pfadbasiert statt labelbasiert. Statt Dateien mit unsichtbaren Metadaten zu versehen, definiert AppArmor pro Anwendung ein Profil, eine Textdatei unter /etc/apparmor.d/, die exakt auflistet, welche Dateipfade mit welchen Rechten (lesend, schreibend, ausführend) zugänglich sind. Der Profilname entspricht meist dem vollständigen Pfad zur Binary, etwa /etc/apparmor.d/usr.sbin.nginx.
Dieser Ansatz hat einen entscheidenden praktischen Vorteil: Profile sind menschenlesbarer Klartext, den man direkt editieren kann, ohne Labels neu setzen zu müssen. Der Nachteil zeigt sich bei Symlinks und Bind-Mounts: Weil die Regel an den Pfad gebunden ist, kann ein cleverer Symlink auf ein alternatives Ziel unter Umständen eine Pfad-Regel umgehen, ein Angriffsvektor, den labelbasierte Systeme wie SELinux durch die Bindung an das tatsächliche Objekt eher vermeiden.
AppArmor kennt pro Profil zwei zentrale Modi: enforce, das Verstöße aktiv blockiert, und complain (das Pendant zu SELinux permissive), das Verstöße nur protokolliert, ohne sie zu verhindern. Anders als SELinux, das systemweit meist einen einzigen Modus kennt, lässt sich bei AppArmor der Modus pro Profil individuell setzen: Ein neu eingeführter Dienst kann in complain laufen, während etablierte Dienste in enforce bleiben, was schrittweise Einführung erheblich erleichtert.
# List loaded profiles and their current mode
aa-status
# apparmor module is loaded.
# 34 profiles are loaded.
# 30 profiles are in enforce mode.
# /usr/sbin/nginx
# /usr/sbin/mysqld
# 4 profiles are in complain mode.
# /usr/sbin/dovecot
# Example profile excerpt: /etc/apparmor.d/usr.sbin.nginx
#include <tunables/global>
/usr/sbin/nginx {
#include <abstractions/base>
#include <abstractions/nameservice>
capability net_bind_service,
capability setuid,
capability setgid,
/etc/nginx/** r,
/var/www/html/** r,
/var/log/nginx/*.log w,
/run/nginx.pid rw,
deny /home/** rwx,
}
# Switch a profile into complain mode for a low-risk rollout
aa-complain /usr/sbin/nginx
# Switch it back into enforce mode once the profile is validated
aa-enforce /usr/sbin/nginx
4. Architekturvergleich: Label vs. Pfad
Der Kernunterschied zwischen beiden Systemen zeigt sich am deutlichsten bei Dateisystemoperationen. SELinux-Labels sind als erweitertes Attribut am Inode gespeichert und überleben deshalb mv innerhalb desselben Dateisystems (mit falschem, altem Label), gehen aber verloren, wenn das Dateisystem keine erweiterten Attribute unterstützt, etwa bei manchen NFS-Exports oder tmpfs-Mounts ohne passende Mount-Optionen. AppArmor-Pfadregeln hingegen kennen dieses Problem nicht, weil sie bei jedem Systemaufruf live gegen den aktuellen Pfad ausgewertet werden. Dafür erkauft man sich eine andere Schwäche: Bind-Mounts und geschickt platzierte Symlinks können eine Pfadregel unter bestimmten Bedingungen umgehen, während ein SELinux-Label am tatsächlichen Objekt hängen bleibt.
Auch operativ unterscheiden sich beide Welten deutlich. SELinux-Policy wird aus Typdefinitionen und CIL-Regeln übersetzt, gepflegt mit semanage, restorecon und Policy-Modulen, und erfordert anfangs eine steilere Lernkurve, bietet dafür aber mit Multi-Level-Security (s0 bis s0:c0.c1023) eine zusätzliche Klassifizierungsebene für hochsensible Multi-Tenant-Umgebungen. AppArmor-Profile sind für Entwickler, die an Textkonfiguration gewöhnt sind, oft schneller zugänglich, kennen aber keine vergleichbare Kategorisierungsebene. Im Container-Umfeld nutzen Docker und Podman auf RHEL-Hosts SELinux-Typen wie container_t für Isolation zwischen Containern, während LXD und Snap-Pakete auf Ubuntu-Hosts auf automatisch generierte AppArmor-Profile pro Container setzen.
5. Diagnose: SELinux-bedingtes Permission-Denied finden
Das typische Symptom: Ein Prozess bekommt Permission denied, obwohl ls -l eindeutig zeigt, dass Owner, Gruppe und Modus den Zugriff erlauben sollten. Ein Blick in /var/log/messages oder journalctl bringt in diesem Fall meist nichts, weil SELinux-Verstöße, sogenannte AVC-Denials (Access Vector Cache), in einer eigenen Logdatei landen: /var/log/audit/audit.log. Der gezielte Weg dorthin führt über ausearch -m avc -ts recent, das nur die relevanten AVC-Einträge der letzten Zeit herausfiltert, statt manuell durch ein riesiges Audit-Log zu scrollen.
Ein AVC-Eintrag enthält scontext (Kontext des zugreifenden Prozesses), tcontext (Kontext des Zielobjekts) und tclass (Objektklasse wie file oder tcp_socket). Aus diesen drei Feldern lässt sich meist sofort ablesen, welche Regel fehlt. Ist setroubleshoot installiert, liefert sealert -a /var/log/audit/audit.log zusätzlich eine Klartext-Erklärung samt konkretem Reparaturvorschlag, was die Fehlersuche für Teams ohne tägliche SELinux-Praxis erheblich beschleunigt.
# Search recent AVC denials instead of scrolling raw audit.log
ausearch -m avc -ts recent
# Example AVC denial line
# type=AVC msg=audit(1720771200.123:456): avc: denied { read } for
# pid=1204 comm="nginx" name="upload.php" dev="dm-0" ino=459812
# scontext=system_u:system_r:httpd_t:s0
# tcontext=unconfined_u:object_r:user_home_t:s0
# tclass=file permissive=0
# Human-readable explanation and fix suggestion
sealert -a /var/log/audit/audit.log
# Generate a minimal policy module from the last denial (review before loading!)
ausearch -m avc -ts recent | audit2allow -M nginx_upload_fix
semodule -i nginx_upload_fix.pp
6. Diagnose: AppArmor-bedingtes Permission-Denied finden
Bei AppArmor landen Verstöße nicht in einem eigenen Audit-Log, sondern direkt im Kernel-Ringpuffer, sichtbar über dmesg oder journalctl -k. Die charakteristische Zeile enthält apparmor="DENIED", gefolgt vom betroffenen Profil, dem Pfad und der angeforderten Operation. Wer nach einem mysteriösen Permission denied zuerst ls -l und die Unix-Rechte prüft und dort nichts Auffälliges findet, sollte als nächsten Schritt gezielt nach dieser Zeile filtern, statt das komplette System-Log zu durchsuchen.
Steht der Verursacher fest, zeigt aa-status, in welchem Modus das zuständige Profil aktuell läuft. Für die eigentliche Reparatur ist aa-logprof das zentrale Werkzeug: Es liest die aufgezeichneten Denials interaktiv aus und schlägt für jede fehlende Regel eine Ergänzung vor, die man einzeln bestätigen, anpassen oder ablehnen kann. Für komplett neue Anwendungen ohne bestehendes Profil erzeugt aa-genprof im Zusammenspiel mit realer Nutzung des Programms ein Startprofil im complain-Modus.
# Filter kernel log for AppArmor denials
journalctl -k --since "10 min ago" | grep apparmor
# Example denial line
# audit: type=1400 audit(1720771300.456:78): apparmor="DENIED"
# operation="open" profile="/usr/sbin/nginx"
# name="/home/deploy/uploads/report.pdf" pid=1204 comm="nginx"
# requested_mask="r" denied_mask="r" fsuid=33 ouid=1001
# Check which mode the responsible profile is currently running in
aa-status | grep -A2 nginx
# Interactively update the profile based on captured denials
aa-logprof
# Reload the updated profile without a reboot
apparmor_parser -r /etc/apparmor.d/usr.sbin.nginx
7. Policy anpassen statt Enforcement pauschal abschalten
Der häufigste Fehler unter Zeitdruck: setenforce 0 oder systemctl stop apparmor, um einen blockierten Deploy schnell wieder zum Laufen zu bringen. Das Problem dabei ist, dass diese Befehle nicht nur die eine fehlende Regel umgehen, sondern die komplette Schutzschicht für das gesamte System oder alle Profile gleichzeitig deaktivieren, oft auf unbestimmte Zeit, weil niemand daran denkt, sie wieder zu aktivieren. Der richtige Weg ist immer, gezielt die fehlende Ausnahme zu ergänzen, statt die ganze Kontrolle abzuschalten.
Für SELinux bedeutet das, die durch audit2allow generierte Policy vor dem Laden mit semodule -i zu lesen: Enthält die Quelle mehrere unabhängige Denials aus unterschiedlichen Vorfällen, kann das generierte Modul unbeabsichtigt zu breite Rechte vergeben. Bei dauerhaften Pfadänderungen ist semanage fcontext -a -t httpd_sys_content_t "/srv/app(/.*)?" gefolgt von restorecon -Rv /srv/app der richtige Weg, der Neustarts und erneutes Relabeling übersteht. Für AppArmor gilt Analoges: aa-logprof statt Profil komplett zu löschen, und jede vorgeschlagene Regel einzeln prüfen, bevor sie übernommen wird, damit das Profil nicht schleichend zu einer bedeutungslosen Freigabe für alles verkommt.
8. Enforcing, Permissive oder Disabled: die Entscheidung pro Umgebung
Auf öffentlich erreichbaren Produktionsservern sollte MAC praktisch immer im aktiven Modus laufen, SELinux enforcing oder AppArmor-Profile in enforce, weil genau diese Systeme das primäre Angriffsziel sind und der zusätzliche Aufwand für Policy-Pflege durch den Sicherheitsgewinn gerechtfertigt ist. Viele Compliance-Rahmenwerke wie PCI-DSS oder ISO 27001 verlangen ohnehin eine aktive Zugriffskontrolle jenseits reiner Unix-Rechte, was Enforcing in regulierten Umgebungen faktisch zur Pflicht macht. Lokale Entwicklungsumgebungen können dagegen vorübergehend in permissive beziehungsweise complain laufen, um die Iterationsgeschwindigkeit während der Einführung eines neuen Dienstes nicht zu bremsen, sollten aber vor dem Produktions-Deploy zwingend in denselben Modus wie die Zielumgebung wechseln.
SELinux vollständig zu deaktivieren (disabled in /etc/selinux/config) erfordert anders als der Wechsel zwischen enforcing und permissive zusätzlich einen Neustart, weil der Kernel die Sicherheitsstruktur beim Boot initialisiert. Diese betriebliche Hürde ist bewusst hoch angesetzt und sollte als Warnsignal verstanden werden: Wer regelmäßig disabled braucht, hat meist ein Policy-Problem, kein grundsätzliches SELinux-Problem. Realistisch bleibt disabled nur für isolierte Spezialfälle sinnvoll, etwa kurzlebige Wegwerf-Testumgebungen ohne jeden Produktionsbezug.
# Ansible task: enforce MAC baseline differently per environment tier
- name: Set SELinux mode based on environment tier
ansible.posix.selinux:
policy: targeted
state: "{{ 'enforcing' if env_tier == 'production' else 'permissive' }}"
when: ansible_os_family == "RedHat"
- name: Ensure AppArmor profile is enforced in production
command: aa-enforce /etc/apparmor.d/usr.sbin.nginx
when:
- ansible_os_family == "Debian"
- env_tier == "production"
- name: Keep AppArmor profile in complain mode on staging
command: aa-complain /etc/apparmor.d/usr.sbin.nginx
when:
- ansible_os_family == "Debian"
- env_tier == "staging"
9. SELinux und AppArmor im direkten Vergleich
Beide Systeme lösen dasselbe Grundproblem mit unterschiedlichen Mitteln, und in der Praxis wird selten zwischen ihnen gewählt, weil die Linux-Distribution die Entscheidung meist vorgibt. Entscheidend ist, im Alltag die typischen Fehlreaktionen auf einen Denial zu vermeiden und stattdessen gezielt nachzubessern.
| Situation | Riskante Reaktion | Empfohlenes Vorgehen | Vorteil |
|---|---|---|---|
| SELinux blockiert unerwarteten Zugriff | setenforce 0 dauerhaft setzen | audit2allow-Policy gezielt generieren und prüfen | Schutz bleibt aktiv, nur die nötige Ausnahme wird erlaubt |
| AppArmor blockiert neuen Dienst | Profil löschen oder aa-disable | Profil in complain setzen und aa-logprof nutzen | Confinement bleibt vorbereitet, Feintuning ohne Ausfall |
| Datei nach mv nicht erreichbar | chmod 777 als Workaround | restorecon -v für korrektes Label | Ursache behoben statt nur versteckt |
| Neue Umgebung wird aufgesetzt | MAC pauschal per Vorlage deaktivieren | targeted/enforce als Standard, Ausnahmen dokumentieren | Keine stille Sicherheitsregression bei neuen Hosts |
| Denial-Ursache suchen | Wahllos im Syslog nach "denied" suchen | ausearch -m avc bzw. journalctl -k gezielt filtern | Präzise Kontext-Zuordnung statt Zufallstreffer |
Mironsoft
Server-Härtung, Sicherheitsaudits und Deployment-Infrastruktur
SELinux- oder AppArmor-Denials, die den Deploy blockieren?
Wir analysieren AVC-Denials und AppArmor-Profile, generieren gezielte Policy-Ausnahmen statt Enforcement pauschal abzuschalten und sorgen dafür, dass eure Server produktionsreif gehärtet bleiben, ohne Deployments zu blockieren.
Denial-Analyse
audit.log und dmesg gezielt auswerten und die tatsächliche Ursache identifizieren
Policy-Härtung
Minimal-invasive SELinux-Module und AppArmor-Profile statt pauschaler Deaktivierung
Rollout-Strategie
Schrittweiser Wechsel von permissive/complain zu enforcing ohne Produktionsausfall
10. Zusammenfassung
SELinux und AppArmor lösen dasselbe Grundproblem, dass Unix-Rechte einem kompromittierten Prozess zu viel Spielraum lassen, mit unterschiedlicher Architektur. SELinux vergibt Labels an Dateien, Prozesse und Ports und entscheidet über Type Enforcement, unabhängig vom Dateipfad, was besonders robust gegen Symlink-Tricks ist, aber eine steilere Lernkurve mitbringt. AppArmor arbeitet pfadbasiert mit menschenlesbaren Profilen unter /etc/apparmor.d/, ist schneller zugänglich, aber theoretisch anfälliger für clevere Pfadmanipulationen.
Für die Diagnose gilt in beiden Welten dieselbe Grundregel: Ein Permission denied trotz korrekter Unix-Rechte deutet fast immer auf einen MAC-Denial hin, sichtbar in /var/log/audit/audit.log bei SELinux oder im Kernel-Log via dmesg/journalctl -k bei AppArmor. Statt setenforce 0 oder ein Profil komplett zu deaktivieren, sollte immer die konkrete fehlende Regel per audit2allow beziehungsweise aa-logprof ergänzt werden. Auf öffentlich erreichbaren Produktionssystemen gehört der aktive Modus, enforcing oder enforce, zum Sicherheitsstandard, während lokale Entwicklungsumgebungen vorübergehend im protokollierenden Modus laufen können.
SELinux vs. AppArmor: Das Wichtigste auf einen Blick
Label vs. Pfad
SELinux entscheidet über Datei-Labels und Type Enforcement, AppArmor über Dateipfade in lesbaren Profilen.
Diagnose-Quelle
SELinux-Denials stehen in audit.log (ausearch -m avc), AppArmor-Denials im Kernel-Log (dmesg, journalctl -k).
Reparatur statt Abschalten
audit2allow und aa-logprof ergänzen gezielt fehlende Regeln, statt Enforcement pauschal zu deaktivieren.
Modus pro Umgebung
Produktion: enforcing/enforce als Standard. Entwicklung: permissive/complain vorübergehend vertretbar.