SELinux i praktiken 2026: Felsöka denials och skriva policymoduler på RHEL 10

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.

SELinux RHEL 10: Felsökning Guide (2026)

Uppdaterad: 31 maj 2026

SELinux är en obligatorisk åtkomstkontroll (MAC) i Linuxkärnan som via LSM-ramverket etiketterar varje process, fil och socket med en säkerhetskontext och tillåter endast åtkomst som en laddad policy explicit medger. På RHEL 10 och AlmaLinux 10 (släppt maj 2025, med kärna 6.12 LTS) körs SELinux i läget enforcing som standard med targeted-policyn, och ärligt talat: där hör den hemma. I den här guiden går jag igenom hur du felsöker AVC-denials, skriver egna policymoduler med audit2allow, justerar booleans och hanterar fil-kontexter på ett sätt som överlever omstarter och uppdateringar.

  • RHEL 10 och AlmaLinux 10 levereras med selinux-policy-targeted 40.x och kärnan 6.12. Kontrollera läget med sestatus innan du börjar felsöka.
  • AVC-denials syns i /var/log/audit/audit.log; använd ausearch -m AVC -ts recent och sealert för läsbara förklaringar.
  • Inaktivera aldrig SELinux som "lösning". Använd istället booleans, fil-kontexter eller en signerad policymodul.
  • Egna policymoduler skrivs i .te-syntax, kompileras med checkmodule och installeras med semodule -i.
  • Använd semanage fcontext för permanenta etiketter och restorecon -Rv för att applicera dem (chcon är bara temporärt).
  • För containerworkloads kombinerar du container_t med kärnans user namespaces; rootless Podman 5.x ärver kontext från anroparen.

Vad är SELinux och hur skiljer det sig från AppArmor?

SELinux implementerar typtvång (type enforcement): varje subjekt och objekt får en kontext på formatet användare:roll:typ:nivå, och policyn definierar tillåtna interaktioner mellan typer. En process som körs som httpd_t får exempelvis bara skriva till filer märkta httpd_log_t eller httpd_var_lib_t, även om DAC-rättigheterna (root, 0644) tekniskt skulle tillåta det. Det är fundamentalt annorlunda än AppArmor, som istället använder sökvägsbaserade profiler.

Skillnaden får tre praktiska konsekvenser. SELinux skyddar även när en fil flyttas eller hårdlänkas (etiketten följer inoden). AppArmor är enklare att läsa och felsöka, eftersom profiler är textfiler kopplade till absoluta sökvägar. Och SELinux har djupare integration med multinivåsäkerhet (MLS/MCS), som krävs för Common Criteria-certifieringar. Red Hat, Fedora, CentOS Stream, AlmaLinux och Rocky Linux levereras med SELinux; Ubuntu, Debian och SUSE använder AppArmor som standard.

För kärnversioner 6.12 och senare har SELinux fått stöd för nftables-baserad kontextöverföring och förbättrad landlock-interoperabilitet. Se kärnans SELinux-dokumentation för aktuella detaljer.

Hur kontrollerar jag om SELinux är aktiverat?

Det första kommandot jag kör på en obekant maskin är sestatus. Det visar nuvarande läge, läget vid uppstart, vilken policy som är laddad och vilken MLS-status som råder.

# Visa aktuellt SELinux-läge och laddad policy
sestatus

# Förväntad output på en härdad RHEL 10-maskin:
# SELinux status:                 enabled
# SELinuxfs mount:                /sys/fs/selinux
# SELinux root directory:         /etc/selinux
# Loaded policy name:             targeted
# Current mode:                   enforcing
# Mode from config file:          enforcing
# Policy MLS status:              enabled
# Policy deny_unknown status:     allowed
# Memory protection checking:     actual (secure)
# Max kernel policy version:      33

Om Current mode visar permissive loggar kärnan denials, men blockerar inget. Det är användbart vid felsökning, men aldrig som permanent tillstånd i produktion. Växla tillfälligt med setenforce 0 respektive setenforce 1. För permanent ändring redigerar du /etc/selinux/config och sätter SELINUX=enforcing; en omstart krävs om du går från disabled till enforcing, eftersom hela filsystemet måste etiketteras om via en autorelabel-körning vid boot.

Hur felsöker jag SELinux-denials steg för steg?

När en tjänst plötsligt inte fungerar och du misstänker SELinux är arbetsflödet alltid detsamma: hitta AVC-meddelandet, översätt det till mänsklig text, identifiera rotorsaken, välj rätt åtgärd. Audit-subsystemet loggar alla denials till /var/log/audit/audit.log med type=AVC.

# Hitta alla AVC-denials från de senaste 10 minuterna
ausearch -m AVC,USER_AVC -ts recent

# Färska denials i läsbart format med åtgärdsförslag
sealert -a /var/log/audit/audit.log | less

# Strömma denials i realtid medan du provocerar felet
journalctl -f _TRANSPORT=audit | grep -i avc

Ett typiskt AVC-meddelande ser ut så här:

type=AVC msg=audit(1717150823.412:1287): avc:  denied  { read } for
  pid=4521 comm="nginx" name="app.log" dev="dm-0" ino=131073
  scontext=system_u:system_r:httpd_t:s0
  tcontext=unconfined_u:object_r:var_log_t:s0
  tclass=file permissive=0

Det här säger: processen nginx (kontext httpd_t) nekades läsa filen app.log, som har kontexten var_log_t. Rotorsaken är nästan alltid en av tre: fel fil-kontext (vanligast), en boolean som är avstängd, eller saknad regel i policyn. I exemplet ovan är problemet fel etikett. app.log borde ha typen httpd_log_t. Lös det med semanage fcontext och restorecon, inte med chcon, eftersom chcon försvinner vid nästa restorecon-körning.

Använda booleans innan du skriver policy

SELinux-policyn har över 300 inbyggda booleans som låter dig slå på eller av vanliga undantag utan att kompilera om något. Innan du skriver en egen modul ska du alltid kontrollera om en boolean redan löser problemet. (Det sparar dig från att underhålla policykod som ändå redan finns färdig.)

# Lista alla booleans relaterade till en tjänst
getsebool -a | grep httpd

# Aktivera nätverksanslutningar från Apache permanent
setsebool -P httpd_can_network_connect on

# Tillåt Apache att läsa användares hemkataloger
setsebool -P httpd_read_user_content on

# Visa beskrivning av varje boolean
semanage boolean -l | grep httpd_can_network

Flaggan -P är kritisk: utan den nollställs ändringen vid nästa omstart, eftersom värdet bara skrivs till körningstidens policy. Tre booleans jag ofta sätter på i moderna webbstackar är httpd_can_network_connect (för reverse proxy mot uppströms applikationer), nis_enabled (för LDAP/Kerberos-integration) och container_manage_cgroup (för Podman/Docker med cgroups v2). En komplett referens finns i Red Hats officiella SELinux-guide för RHEL 10.

Skriva och installera en egen policymodul

När varken booleans eller fil-kontexter räcker behöver du en egen policymodul. Arbetsflödet är: provocera felet i permissive-läge så alla denials loggas, kör audit2allow på loggen, granska den genererade regeln, kompilera och installera. Hoppa aldrig över granskningen. audit2allow genererar precis det som efterfrågas, vilket kan vara mer än vad som är säkert (jag har själv haft en .te-fil som råkade öppna upp etc_t-skrivning för en webbtjänst, något som aldrig skulle ha släppts förbi en review).

# 1. Sätt målnoden i permissive bara för en specifik domän
semanage permissive -a httpd_t

# 2. Reproducera felet och samla AVC-loggar
ausearch -m AVC -c nginx --start recent > /tmp/avc.log

# 3. Generera modulkällkod (.te) med kommentarer
audit2allow -i /tmp/avc.log -m nginx_custom > nginx_custom.te
cat nginx_custom.te

# 4. Granska. Ta bort regler som ger mer åtkomst än nödvändigt
$EDITOR nginx_custom.te

# 5. Kompilera och paketera modulen
checkmodule -M -m -o nginx_custom.mod nginx_custom.te
semodule_package -o nginx_custom.pp -m nginx_custom.mod

# 6. Installera och återställ enforcing
semodule -i nginx_custom.pp
semanage permissive -d httpd_t
setenforce 1

# 7. Verifiera att modulen är aktiv
semodule -l | grep nginx_custom

En genererad .te-fil kan se ut så här:

module nginx_custom 1.0;

require {
    type httpd_t;
    type var_log_t;
    class file { read open getattr };
}

#============= httpd_t ==============
allow httpd_t var_log_t:file { read open getattr };

Den här regeln är för bred. Den ger Apache åtkomst till alla filer märkta var_log_t. En bättre lösning är att etikettera om bara den specifika filen till httpd_log_t (se nästa avsnitt). Använd bara egna policymoduler när det finns ett genuint behov av en kombination som inte existerar i basreferensen; annars introducerar du tekniska skulder som måste underhållas vid varje policyuppdatering.

Hantera fil-kontexter permanent

Fil-kontexter är en central del av SELinux och den punkt där de flesta administratörer snubblar. chcon ändrar etiketten i farten, men restorecon eller en filsystem-relabeling återställer den till det som file_contexts-databasen säger. Permanenta ändringar görs alltid via semanage fcontext.

# Lägg till en permanent regel för en alternativ webroot
semanage fcontext -a -t httpd_sys_content_t "/srv/www(/.*)?"

# Applicera regeln på existerande filer
restorecon -Rv /srv/www

# Visa alla regler som matchar en sökväg
matchpathcon /srv/www/index.html
# /srv/www/index.html  system_u:object_r:httpd_sys_content_t:s0

# Lista anpassade fcontext-regler (utan basen)
semanage fcontext -l -C

För skrivbara webbkataloger använder du httpd_sys_rw_content_t; för CGI-skript httpd_sys_script_exec_t. På RHEL 10 har semanage även flaggan --equal som länkar en hel underkatalogs etikettregler till en annan, vilket är användbart när du har parallella miljöer som ska bete sig identiskt. Mer om detta finns i min tidigare guide om SSH-härdning 2026 med postkvant-kryptografi, där samma semanage-mönster används för att låsa ned sshd-konfigurationen.

SELinux för containers och rootless Podman

Containerruntimes på RHEL 10 (Podman 5.x och CRI-O 1.32+) använder typen container_t för processer inuti containern och container_file_t för monterade volymer. När du binder en värdkatalog till en container måste den ha rätt etikett, annars nekas läsning oavsett vilka POSIX-rättigheter du satt. (Den här fällan har jag själv ramlat i fler gånger än jag vill erkänna.)

# Montera värdkatalog med automatisk relabeling
podman run --rm -v /srv/data:/data:Z alpine ls /data

# Z = privat etikett bara för denna container
# z = delad etikett mellan flera containers

# Manuellt sätta container_file_t på en permanent volym
semanage fcontext -a -t container_file_t "/opt/appdata(/.*)?"
restorecon -Rv /opt/appdata

För rootless Podman ärver containern användarens UID-namespace och kör fortfarande under container_t, men med userns_t-baserade transitions. Det innebär att även om en angripare bryter sig ut ur containern, hamnar de under en oprivilegierad användares kontext utan möjlighet att eskalera till unconfined_t. Kombinationen med eBPF-baserad runtime-detektion ger djupförsvar. Se min genomgång av eBPF-baserad runtime-säkerhet med Falco och Tetragon för hur du loggar policyöverträdelser i realtid. Officiella riktlinjer publiceras av container-selinux-projektet på GitHub.

Härdningsrekommendationer för RHEL 10 i produktion

Utöver att hålla SELinux i enforcing med targeted-policy finns det en handfull mätbara åtgärder som tar dig från "default" till "härdat". CIS Red Hat Enterprise Linux 10 Benchmark v1.0 (publicerad januari 2026) listar 21 SELinux-relaterade kontroller, och så här prioriterar jag dem:

  • Begränsa unconfined_t-domäner. Kör semanage login -l och flytta administratörsanvändare från unconfined_u till staff_u eller user_u. Det blockerar utbredning till hela systemet om kontot komprometteras.
  • Aktivera secure_mode_policyload som boolean. Den hindrar oprivilegierade laddningar av nya policymoduler även om angriparen är root.
  • Audit alla setuid-binärer med find / -perm -4000 -type f -exec ls -lZ {} \; och säkerställ att inga av dem har kontexten unconfined_exec_t.
  • Aktivera fapolicyd tillsammans med SELinux för applikationskontroll i kärnrymd. Den blockerar exekvering av binärer som inte finns på en signerad lista, ortogonalt till MAC-typtvånget.
  • Skicka audit-loggar centralt med auditd + aushape till en SIEM. Lokala loggar kan raderas av en angripare; centrala kan inte.

För kontinuerlig efterlevnadskontroll använd oscap med scap-security-guide 0.1.78 eller senare, som inkluderar uppdaterade DISA STIG-profiler för RHEL 10. En enkel daglig körning ser ut så här:

oscap xccdf eval \
  --profile xccdf_org.ssgproject.content_profile_stig \
  --results /var/log/oscap-$(date +%F).xml \
  --report /var/log/oscap-$(date +%F).html \
  /usr/share/xml/scap/ssg/content/ssg-rhel10-ds.xml

Resultatet är en HTML-rapport med pass/fail per kontroll. Kombinerat med ett konfigurationshanteringssystem som Ansible kan du sluta cirkeln och få automatisk remediering. För nätverkssegmentering på samma maskiner, läs vidare i Zero Trust-arkitektur på Linux.

Vanliga frågor

Ska jag inaktivera SELinux om det blockerar min applikation?

Nej. Inaktivering tar bort hela MAC-lagret från kärnan och kan inte återaktiveras utan omstart och fullständig relabeling. Sätt istället den specifika domänen i permissive med semanage permissive -a min_domain_t, samla AVC-loggarna och fixa rotorsaken med en boolean, fcontext eller policymodul.

Vad är skillnaden mellan targeted- och MLS-policyn?

Targeted begränsar bara namngivna systemtjänster (httpd, sshd, named etc.) och låter inloggade användare köra under unconfined_u som standard. MLS (Multi-Level Security) tillämpar Bell-LaPadula-modellen med klassificerade nivåer (Secret, Top Secret) och används främst i statliga eller militära miljöer. RHEL 10 stödjer båda, men 99 % av installationerna använder targeted.

Hur återställer jag SELinux-etiketter på hela filsystemet?

Skapa filen /.autorelabel och starta om: touch /.autorelabel && reboot. Kärnan kör då fixfiles relabel vid uppstart och raderar filen automatiskt efteråt. På stora system kan det ta 10 till 30 minuter; använd fixfiles -F onboot för att tvinga relabeling även av filer med korrekt etikett.

Varför fungerar inte min Apache trots att SELinux är permissive?

Permissive-läge stänger av blockering, men ändrar inte DAC-rättigheter, brandväggsregler eller systemd-konfiguration. Kontrollera journalctl -u httpd, firewall-cmd --list-all och POSIX-rättigheter med ls -lZ. Om felet försvinner i permissive men kommer tillbaka i enforcing är det däremot definitivt en SELinux-fråga som ska lösas med fcontext eller policy.

Kan jag använda SELinux och AppArmor samtidigt?

Nej, kärnan tillåter bara en "exclusive" major LSM åt gången. Sedan kärnversion 5.1 kan dock landlock, bpf-lsm och yama staplas ovanpå antingen SELinux eller AppArmor. På RHEL 10 (kärna 6.12) är SELinux den enda major LSM som är aktiverad i standardbygget.

Yuki Tanaka
Om Författaren Yuki Tanaka

Linux kernel security engineer with a background in eBPF and LSM. Likes hardening more than she likes sleeping.