SELinux auf Debian: Unterschied zwischen den Versionen
| (3 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt) | |||
| Zeile 1: | Zeile 1: | ||
= SELinux statt AppArmor auf Debian = | = SELinux statt AppArmor auf Debian = | ||
| − | Diese Seite dokumentiert die Migration von AppArmor zu SELinux auf it2XX und die anschließende Evaluierung für sshd, | + | Diese Seite dokumentiert die Migration von AppArmor zu SELinux auf it2XX und die anschließende Evaluierung für sshd und Apache2, inklusive Apache als Reverse Proxy. |
== Hintergrund == | == Hintergrund == | ||
| Zeile 12: | Zeile 12: | ||
* Die generierten Vorschläge waren teils unpassend (z. B. Vorschlag von <code>abstractions/apache2-common</code> für einen SSH-Prozess) | * Die generierten Vorschläge waren teils unpassend (z. B. Vorschlag von <code>abstractions/apache2-common</code> für einen SSH-Prozess) | ||
| − | Daraus folgt die Entscheidung, AppArmor zu entfernen und SELinux als MAC-System auf Debian einzusetzen und für sshd | + | Daraus folgt die Entscheidung, AppArmor zu entfernen und SELinux als MAC-System auf Debian einzusetzen und für sshd und Apache2 zu evaluieren. |
== AppArmor entfernen == | == AppArmor entfernen == | ||
| Zeile 49: | Zeile 49: | ||
Beim ersten Boot nach der Aktivierung wird das komplette Dateisystem relabelt. Dieser Vorgang kann je nach Systemgröße mehrere Minuten dauern. | Beim ersten Boot nach der Aktivierung wird das komplette Dateisystem relabelt. Dieser Vorgang kann je nach Systemgröße mehrere Minuten dauern. | ||
| − | ; | + | ;Enforcing-Modus prüfen |
| − | + | ||
| + | Wie bei Rocky wird direkt im enforcing Modus gearbeitet, kein Umweg über permissive: | ||
| + | |||
*getenforce | *getenforce | ||
| − | + | Erwartet: <code>Enforcing</code> (Standard nach der Aktivierung). Falls nicht, dauerhaft in <code>/etc/selinux/config</code> setzen: | |
<pre> | <pre> | ||
SELINUX=enforcing | SELINUX=enforcing | ||
</pre> | </pre> | ||
| + | |||
| + | und neu starten. | ||
== Evaluierung: sshd == | == Evaluierung: sshd == | ||
| Zeile 74: | Zeile 78: | ||
;Denials nach Testlogin auswerten | ;Denials nach Testlogin auswerten | ||
*ausearch -m avc -c sshd | *ausearch -m avc -c sshd | ||
| − | ;Port | + | |
| + | === Port ändern (Beispiel: 2222) === | ||
| + | |||
| + | ;Modus prüfen | ||
| + | *getenforce | ||
| + | |||
| + | ;Port in der Konfiguration setzen | ||
*cat /etc/ssh/sshd_config | *cat /etc/ssh/sshd_config | ||
Port 2222 | Port 2222 | ||
... | ... | ||
| − | *systemctl restart ssh | + | |
| − | ; | + | ;Neustart (schlägt ohne Port-Label fehl) |
| + | *systemctl restart ssh | ||
| + | |||
| + | ;Denial prüfen | ||
*ausearch -m avc -ts recent | *ausearch -m avc -ts recent | ||
| − | ; | + | |
| + | ;Aktuell zugelassene Ports prüfen | ||
*semanage port -l | grep ssh_port_t | *semanage port -l | grep ssh_port_t | ||
| − | ;Port | + | |
| + | ;Port-Label ergänzen | ||
*semanage port -a -t ssh_port_t -p tcp 2222 | *semanage port -a -t ssh_port_t -p tcp 2222 | ||
| − | ; | + | |
| + | ;Neustart erneut | ||
*systemctl restart ssh | *systemctl restart ssh | ||
| + | |||
| + | ;Login-Test auf dem neuen Port | ||
| + | *ssh -p 2222 user@it2XX | ||
| + | |||
| + | '''Hinweis:''' Bei semanage-Operationen erscheint häufig die Meldung <code>libsemanage.add_user: user sddm not in password file</code>. Das ist ein bekanntes, kosmetisches Problem der Debian-Paketierung der Default-Policy (sddm-User existiert auf Headless-Servern nicht) und kann ignoriert werden — der eigentliche Befehl läuft trotzdem durch. | ||
== Evaluierung: Apache2 == | == Evaluierung: Apache2 == | ||
| Zeile 98: | Zeile 119: | ||
;Kontext prüfen | ;Kontext prüfen | ||
*ps -eZ | grep apache2 | *ps -eZ | grep apache2 | ||
| + | |||
| + | Erwarteter Typ: <code>httpd_t</code>. | ||
;Vorhandene Port-Labels prüfen | ;Vorhandene Port-Labels prüfen | ||
*semanage port -l | grep http_port_t | *semanage port -l | grep http_port_t | ||
| − | + | === Custom Webroot einrichten === | |
| + | |||
| + | ;Verzeichnis anlegen | ||
| + | *mkdir -p /srv/kurswebsite | ||
| + | *mkdir -p /srv/kurswebsite/uploads | ||
| + | |||
| + | ;Testinhalt anlegen | ||
| + | *echo "Kurswebsite it2XX" > /srv/kurswebsite/index.html | ||
| + | |||
| + | ;vHost anlegen | ||
| + | *vim /etc/apache2/sites-available/kurswebsite.conf | ||
| + | |||
| + | <pre> | ||
| + | <VirtualHost *:80> | ||
| + | ServerName it2XX.int | ||
| + | DocumentRoot /srv/kurswebsite | ||
| + | <Directory /srv/kurswebsite> | ||
| + | Options Indexes FollowSymLinks | ||
| + | AllowOverride None | ||
| + | Require all granted | ||
| + | </Directory> | ||
| + | </VirtualHost> | ||
| + | </pre> | ||
| + | |||
| + | ;vHost aktivieren | ||
| + | *a2ensite kurswebsite.conf | ||
| + | *a2dissite 000-default.conf | ||
| + | *systemctl reload apache2 | ||
| + | |||
| + | === Zugriff vor dem Labeling testen (Denial provozieren) === | ||
| + | |||
| + | ;Test von der Kommandozeile | ||
| + | *curl -I http://it2XX.int/ | ||
| + | |||
| + | Erwartet an dieser Stelle: <code>403 Forbidden</code>, obwohl die klassischen Unix-Rechte (Owner, Gruppe, Permissions) korrekt gesetzt sind — das Verzeichnis trägt noch keinen SELinux-Kontext für Apache. | ||
| + | |||
| + | ;Denial im Audit-Log bestätigen | ||
| + | *ausearch -m avc -ts recent -c httpd | ||
| + | |||
| + | ;Aktuellen (falschen) Kontext ansehen | ||
| + | *ls -Zd /srv/kurswebsite | ||
| + | |||
| + | Erwarteter (falscher) Typ an dieser Stelle: <code>default_t</code> oder <code>var_t</code>, je nach übergeordnetem Verzeichnis — jedenfalls nicht <code>httpd_sys_content_t</code>. | ||
| + | |||
| + | === Kontext dauerhaft setzen === | ||
Niemals <code>chcon</code> verwenden, sondern dauerhaft über <code>semanage fcontext</code>: | Niemals <code>chcon</code> verwenden, sondern dauerhaft über <code>semanage fcontext</code>: | ||
| Zeile 109: | Zeile 176: | ||
*restorecon -Rv /srv/kurswebsite | *restorecon -Rv /srv/kurswebsite | ||
| − | ; | + | ;Kontext erneut prüfen |
| − | + | *ls -Zd /srv/kurswebsite | |
| + | |||
| + | Erwarteter Typ jetzt: <code>httpd_sys_content_t</code>. | ||
| + | |||
| + | ;Zugriff erneut testen | ||
| + | *curl -I http://it2XX.int/ | ||
| + | |||
| + | Erwartet: <code>200 OK</code>. | ||
| + | |||
| + | === Upload-Verzeichnis mit Schreibzugriff === | ||
| + | |||
| + | Für das Unterverzeichnis <code>uploads</code> reicht Lesezugriff nicht — Apache soll dort schreiben dürfen (z. B. für eine Formularanwendung). | ||
| + | |||
| + | ;Eigenen Kontext für uploads setzen | ||
*semanage fcontext -a -t httpd_sys_rw_content_t "/srv/kurswebsite/uploads(/.*)?" | *semanage fcontext -a -t httpd_sys_rw_content_t "/srv/kurswebsite/uploads(/.*)?" | ||
*restorecon -Rv /srv/kurswebsite/uploads | *restorecon -Rv /srv/kurswebsite/uploads | ||
| − | == | + | ;Kontext prüfen |
| + | *ls -Zd /srv/kurswebsite/uploads | ||
| + | |||
| + | Erwarteter Typ: <code>httpd_sys_rw_content_t</code>. | ||
| + | |||
| + | ;Boolean prüfen und ggf. setzen | ||
| + | |||
| + | Der Boolean <code>httpd_unified</code> hebt die Trennung zwischen Lese- und Schreibkontexten teilweise auf und sollte für dieses Szenario deaktiviert bleiben, damit die Trennung sichtbar bleibt: | ||
| + | |||
| + | *getsebool httpd_unified | ||
| + | *setsebool -P httpd_unified off | ||
| + | |||
| + | ;Schreibtest (z. B. über die Anwendung oder testweise als www-data) | ||
| + | *sudo -u www-data touch /srv/kurswebsite/uploads/test.txt | ||
| + | *ls -l /srv/kurswebsite/uploads/ | ||
| + | |||
| + | ;Bei Fehlschlag: Denials prüfen | ||
| + | *ausearch -m avc -ts recent -c httpd | ||
| + | |||
| + | === HTTPS mit eigenem Zertifikat === | ||
| + | |||
| + | ;Zertifikat und Key ablegen | ||
| + | *ls -l /etc/ssl/own.crt /etc/ssl/own.key | ||
| + | |||
| + | ;SELinux-Kontext der Zertifikatsdateien prüfen | ||
| + | *ls -Z /etc/ssl/own.crt /etc/ssl/own.key | ||
| + | |||
| + | Erwarteter Typ: <code>cert_t</code>, da <code>/etc/ssl</code> bereits standardmäßig darauf gelabelt ist. Falls die Dateien von woanders hierher kopiert wurden (statt z. B. per <code>mv</code> innerhalb von <code>/etc/ssl</code> verschoben), kann der Kontext vom Ursprungsort geerbt worden sein und muss korrigiert werden: | ||
| + | |||
| + | *restorecon -v /etc/ssl/own.crt /etc/ssl/own.key | ||
| + | |||
| + | ;mod_ssl aktivieren | ||
| + | *a2enmod ssl | ||
| + | |||
| + | ;vHost für Port 443 ergänzen | ||
| + | *vim /etc/apache2/sites-available/kurswebsite.conf | ||
| + | |||
| + | <pre> | ||
| + | <VirtualHost *:443> | ||
| + | ServerName it2XX.int | ||
| + | DocumentRoot /srv/kurswebsite | ||
| + | SSLEngine on | ||
| + | SSLCertificateFile /etc/ssl/own.crt | ||
| + | SSLCertificateKeyFile /etc/ssl/own.key | ||
| + | <Directory /srv/kurswebsite> | ||
| + | Options Indexes FollowSymLinks | ||
| + | AllowOverride None | ||
| + | Require all granted | ||
| + | </Directory> | ||
| + | </VirtualHost> | ||
| + | </pre> | ||
| + | |||
| + | ;Apache neu laden | ||
| + | *systemctl reload apache2 | ||
| + | |||
| + | ;Port-Label prüfen | ||
| + | *semanage port -l | grep http_port_t | ||
| + | |||
| + | Port 443 ist im Standardzustand bereits als <code>http_port_t</code> gelabelt, üblicherweise ist hier kein manueller Eingriff nötig. | ||
| + | |||
| + | ;Zugriff testen | ||
| + | *curl -Ik https://it2XX.int/ | ||
| + | |||
| + | ;Bei Fehlschlag: Denials prüfen | ||
| + | *ausearch -m avc -ts recent -c httpd | ||
| + | |||
| + | Häufigste Ursache bei SSL-Denials: der Boolean <code>httpd_read_user_content</code> oder ein falsch gelabelter Key/Cert-Pfad außerhalb von <code>/etc/ssl</code> bzw. <code>/etc/pki</code>. In dem Fall: | ||
| + | |||
| + | *semanage fcontext -a -t cert_t "/etc/ssl/own\.(crt|key)" | ||
| + | *restorecon -v /etc/ssl/own.crt /etc/ssl/own.key | ||
| + | |||
| + | === Redirect von Port 80 auf 443 === | ||
| + | |||
| + | ;mod_rewrite aktivieren | ||
| + | *a2enmod rewrite | ||
| + | |||
| + | ;Port-80-vHost auf Redirect umstellen | ||
| + | *vim /etc/apache2/sites-available/kurswebsite.conf | ||
| + | |||
| + | <pre> | ||
| + | <VirtualHost *:80> | ||
| + | ServerName it2XX.int | ||
| + | RewriteEngine On | ||
| + | RewriteCond %{HTTPS} off | ||
| + | RewriteRule ^(.*)$ https://%{HTTP_HOST}$1 [R=301,L] | ||
| + | </VirtualHost> | ||
| + | |||
| + | <VirtualHost *:443> | ||
| + | ServerName it2XX.int | ||
| + | DocumentRoot /srv/kurswebsite | ||
| + | SSLEngine on | ||
| + | SSLCertificateFile /etc/ssl/own.crt | ||
| + | SSLCertificateKeyFile /etc/ssl/own.key | ||
| + | <Directory /srv/kurswebsite> | ||
| + | Options Indexes FollowSymLinks | ||
| + | AllowOverride None | ||
| + | Require all granted | ||
| + | </Directory> | ||
| + | </VirtualHost> | ||
| + | </pre> | ||
| − | + | ;Apache neu laden | |
| + | *systemctl reload apache2 | ||
| − | + | ;Redirect testen | |
| − | + | *curl -I http://it2XX.int/ | |
| − | + | Erwartet: <code>301 Moved Permanently</code> mit <code>Location: https://it2XX.int/</code>. | |
| − | |||
| − | |||
| − | + | ;Vollständigen Redirect-Durchlauf testen | |
| + | *curl -IL http://it2XX.int/ | ||
| − | ; | + | Erwartet: erst <code>301</code>, dann <code>200 OK</code> nach dem Folgen des Redirects. |
| − | *ausearch -m avc -c | + | |
| − | * | + | SELinux-seitig ist hier normalerweise kein weiterer Eingriff nötig, da beide Ports (80 und 443) bereits als <code>http_port_t</code> gelabelt sind und der Redirect selbst keine zusätzlichen Datei- oder Netzwerkzugriffe außerhalb des bestehenden <code>httpd_t</code>-Kontexts benötigt. Falls trotzdem ein Denial auftritt: |
| + | |||
| + | *ausearch -m avc -ts recent -c httpd | ||
| + | |||
| + | === Reverse Proxy (ausgehender Verkehr) === | ||
| + | |||
| + | Standardmäßig darf ein Apache-Prozess unter SELinux keine ausgehenden Netzwerkverbindungen zu einem Backend aufbauen — das betrifft klassisches <code>mod_proxy</code>, aber auch z. B. Verbindungen zu einem Datenbank- oder Applikationsserver. | ||
| + | |||
| + | ;Proxy-Module aktivieren | ||
| + | *a2enmod proxy | ||
| + | *a2enmod proxy_http | ||
| + | |||
| + | ;vHost mit Reverse Proxy anlegen | ||
| + | *vim /etc/apache2/sites-available/kurswebsite.conf | ||
| + | |||
| + | <pre> | ||
| + | <VirtualHost *:80> | ||
| + | ServerName proxy.it2XX.int | ||
| + | ProxyPreserveHost On | ||
| + | ProxyPass / http://192.168.2XX.10:8080/ | ||
| + | ProxyPassReverse / http://192.168.2XX.10:8080/ | ||
| + | </VirtualHost> | ||
| + | </pre> | ||
| + | |||
| + | ;vHost aktivieren | ||
| + | *a2ensite kurswebsite.conf | ||
| + | *systemctl reload apache2 | ||
| + | |||
| + | === Zugriff vor dem Boolean testen (Denial provozieren) === | ||
| + | |||
| + | ;Test von der Kommandozeile | ||
| + | *curl -I http://proxy.it2XX.int/ | ||
| + | |||
| + | Erwartet an dieser Stelle: <code>503 Service Unavailable</code>, obwohl das Backend selbst erreichbar ist — Apache darf noch keine ausgehende Verbindung aufbauen. | ||
| + | |||
| + | ;Denial im Audit-Log bestätigen | ||
| + | *ausearch -m avc -ts recent -c httpd | ||
| + | |||
| + | Erwartete Meldung u. a.: Zugriff verweigert für <code>httpd_t</code> auf <code>name_connect</code>. | ||
| + | |||
| + | === Boolean für ausgehenden Verkehr setzen === | ||
| + | |||
| + | ;Aktuellen Zustand prüfen | ||
| + | *getsebool httpd_can_network_connect | ||
| + | |||
| + | ;Boolean dauerhaft aktivieren | ||
| + | *setsebool -P httpd_can_network_connect on | ||
| + | |||
| + | ;Zugriff erneut testen | ||
| + | *curl -I http://proxy.it2XX.int/ | ||
| + | |||
| + | Erwartet: <code>200 OK</code>, sofern das Backend erreichbar ist und selbst korrekt antwortet. | ||
| + | |||
| + | ;Bei Fehlschlag: Denials erneut prüfen | ||
| + | *ausearch -m avc -ts recent -c httpd | ||
| + | |||
| + | '''Hinweis:''' Falls das Backend auf einem ungewöhnlichen Port läuft (hier 8080) und dieser nicht bereits als <code>http_port_t</code>, <code>http_cache_port_t</code> o. ä. gelabelt ist, kann zusätzlich ein gezielterer Boolean nötig sein, z. B. <code>httpd_can_network_connect_db</code> bei Datenbank-Backends statt des pauschalen <code>httpd_can_network_connect</code>. Für den Kurs reicht der allgemeine Boolean als Einstieg, sollte aber als Beispiel für "möglichst granular statt pauschal erlauben" erwähnt werden. | ||
== Vergleichstabelle (auszufüllen nach Testlauf) == | == Vergleichstabelle (auszufüllen nach Testlauf) == | ||
{| class="wikitable" | {| class="wikitable" | ||
| − | ! Dienst !! Natives Policy-Modul !! Setup-Aufwand !! Ergebnis | + | ! Dienst / Szenario !! Natives Policy-Modul !! Setup-Aufwand !! Ergebnis |
|- | |- | ||
| sshd || ja, automatisch || niedrig || | | sshd || ja, automatisch || niedrig || | ||
|- | |- | ||
| − | | Apache2 || ja, automatisch || niedrig || | + | | Apache2 (Webroot, HTTPS) || ja, automatisch || niedrig || |
|- | |- | ||
| − | | | + | | Apache2 als Reverse Proxy || ja, über Boolean || niedrig || |
|} | |} | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
Aktuelle Version vom 28. Juli 2026, 17:17 Uhr
SELinux statt AppArmor auf Debian
Diese Seite dokumentiert die Migration von AppArmor zu SELinux auf it2XX und die anschließende Evaluierung für sshd und Apache2, inklusive Apache als Reverse Proxy.
Hintergrund
Im Rahmen der Kursvorbereitung wurde AppArmor auf Basis eines Praxistests mit dem sshd-Profil aus apparmor-profiles-extra evaluiert. Dabei zeigten sich mehrere strukturelle Schwächen:
- Das mitgelieferte sshd-Profil ist laut Debian-Paketbeschreibung explizit als experimentell gekennzeichnet und nicht für den enforce-Mode vorgesehen
- Nach dem OpenSSH-Split (Version 9.8, sshd-session als separater Prozess) war das Profil nicht mehr aktuell und musste manuell über mehrere
aa-logprof-Durchläufe nachgezogen werden aa-logprofkann journald nicht nativ lesen; ohne zusätzlich installiertes rsyslog schlägt die Log-Auswertung fehl- Die generierten Vorschläge waren teils unpassend (z. B. Vorschlag von
abstractions/apache2-commonfür einen SSH-Prozess)
Daraus folgt die Entscheidung, AppArmor zu entfernen und SELinux als MAC-System auf Debian einzusetzen und für sshd und Apache2 zu evaluieren.
AppArmor entfernen
- Dienst stoppen und deaktivieren
- systemctl stop apparmor
- systemctl disable apparmor
- Pakete entfernen
- apt purge apparmor apparmor-utils apparmor-profiles apparmor-profiles-extra python3-apparmor python3-libapparmor
- apt autoremove
- AppArmor im Kernel deaktivieren
Bootparameter prüfen:
- cat /proc/cmdline | grep -i apparmor
Falls kein expliziter Parameter gesetzt ist, in /etc/default/grub ergänzen:
GRUB_CMDLINE_LINUX_DEFAULT="... apparmor=0"
- GRUB-Konfiguration neu erstellen und neu starten
- update-grub
- reboot
SELinux installieren
- Pakete installieren
- apt install selinux-basics selinux-policy-default auditd
- SELinux aktivieren
- selinux-activate
- reboot
Beim ersten Boot nach der Aktivierung wird das komplette Dateisystem relabelt. Dieser Vorgang kann je nach Systemgröße mehrere Minuten dauern.
- Enforcing-Modus prüfen
Wie bei Rocky wird direkt im enforcing Modus gearbeitet, kein Umweg über permissive:
- getenforce
Erwartet: Enforcing (Standard nach der Aktivierung). Falls nicht, dauerhaft in /etc/selinux/config setzen:
SELINUX=enforcing
und neu starten.
Evaluierung: sshd
Das ssh-Policy-Modul wird von der Standard-Policy automatisch anhand des installierten Pakets geladen.
- Paket sicherstellen
- apt install openssh-server
- Kontext prüfen
- ps -eZ | grep sshd
- ls -Z /usr/sbin/sshd
Erwarteter Typ: sshd_t für den Prozess, sshd_exec_t für die Binary.
- Denials nach Testlogin auswerten
- ausearch -m avc -c sshd
Port ändern (Beispiel: 2222)
- Modus prüfen
- getenforce
- Port in der Konfiguration setzen
- cat /etc/ssh/sshd_config
Port 2222 ...
- Neustart (schlägt ohne Port-Label fehl)
- systemctl restart ssh
- Denial prüfen
- ausearch -m avc -ts recent
- Aktuell zugelassene Ports prüfen
- semanage port -l | grep ssh_port_t
- Port-Label ergänzen
- semanage port -a -t ssh_port_t -p tcp 2222
- Neustart erneut
- systemctl restart ssh
- Login-Test auf dem neuen Port
- ssh -p 2222 user@it2XX
Hinweis: Bei semanage-Operationen erscheint häufig die Meldung libsemanage.add_user: user sddm not in password file. Das ist ein bekanntes, kosmetisches Problem der Debian-Paketierung der Default-Policy (sddm-User existiert auf Headless-Servern nicht) und kann ignoriert werden — der eigentliche Befehl läuft trotzdem durch.
Evaluierung: Apache2
Das httpd-Policy-Modul deckt Apache2 unter Debian standardmäßig ab.
- Paket installieren
- apt install apache2
- systemctl restart apache2
- Kontext prüfen
- ps -eZ | grep apache2
Erwarteter Typ: httpd_t.
- Vorhandene Port-Labels prüfen
- semanage port -l | grep http_port_t
Custom Webroot einrichten
- Verzeichnis anlegen
- mkdir -p /srv/kurswebsite
- mkdir -p /srv/kurswebsite/uploads
- Testinhalt anlegen
- echo "Kurswebsite it2XX" > /srv/kurswebsite/index.html
- vHost anlegen
- vim /etc/apache2/sites-available/kurswebsite.conf
<VirtualHost *:80>
ServerName it2XX.int
DocumentRoot /srv/kurswebsite
<Directory /srv/kurswebsite>
Options Indexes FollowSymLinks
AllowOverride None
Require all granted
</Directory>
</VirtualHost>
- vHost aktivieren
- a2ensite kurswebsite.conf
- a2dissite 000-default.conf
- systemctl reload apache2
Zugriff vor dem Labeling testen (Denial provozieren)
- Test von der Kommandozeile
- curl -I http://it2XX.int/
Erwartet an dieser Stelle: 403 Forbidden, obwohl die klassischen Unix-Rechte (Owner, Gruppe, Permissions) korrekt gesetzt sind — das Verzeichnis trägt noch keinen SELinux-Kontext für Apache.
- Denial im Audit-Log bestätigen
- ausearch -m avc -ts recent -c httpd
- Aktuellen (falschen) Kontext ansehen
- ls -Zd /srv/kurswebsite
Erwarteter (falscher) Typ an dieser Stelle: default_t oder var_t, je nach übergeordnetem Verzeichnis — jedenfalls nicht httpd_sys_content_t.
Kontext dauerhaft setzen
Niemals chcon verwenden, sondern dauerhaft über semanage fcontext:
- semanage fcontext -a -t httpd_sys_content_t "/srv/kurswebsite(/.*)?"
- restorecon -Rv /srv/kurswebsite
- Kontext erneut prüfen
- ls -Zd /srv/kurswebsite
Erwarteter Typ jetzt: httpd_sys_content_t.
- Zugriff erneut testen
- curl -I http://it2XX.int/
Erwartet: 200 OK.
Upload-Verzeichnis mit Schreibzugriff
Für das Unterverzeichnis uploads reicht Lesezugriff nicht — Apache soll dort schreiben dürfen (z. B. für eine Formularanwendung).
- Eigenen Kontext für uploads setzen
- semanage fcontext -a -t httpd_sys_rw_content_t "/srv/kurswebsite/uploads(/.*)?"
- restorecon -Rv /srv/kurswebsite/uploads
- Kontext prüfen
- ls -Zd /srv/kurswebsite/uploads
Erwarteter Typ: httpd_sys_rw_content_t.
- Boolean prüfen und ggf. setzen
Der Boolean httpd_unified hebt die Trennung zwischen Lese- und Schreibkontexten teilweise auf und sollte für dieses Szenario deaktiviert bleiben, damit die Trennung sichtbar bleibt:
- getsebool httpd_unified
- setsebool -P httpd_unified off
- Schreibtest (z. B. über die Anwendung oder testweise als www-data)
- sudo -u www-data touch /srv/kurswebsite/uploads/test.txt
- ls -l /srv/kurswebsite/uploads/
- Bei Fehlschlag
- Denials prüfen
- ausearch -m avc -ts recent -c httpd
HTTPS mit eigenem Zertifikat
- Zertifikat und Key ablegen
- ls -l /etc/ssl/own.crt /etc/ssl/own.key
- SELinux-Kontext der Zertifikatsdateien prüfen
- ls -Z /etc/ssl/own.crt /etc/ssl/own.key
Erwarteter Typ: cert_t, da /etc/ssl bereits standardmäßig darauf gelabelt ist. Falls die Dateien von woanders hierher kopiert wurden (statt z. B. per mv innerhalb von /etc/ssl verschoben), kann der Kontext vom Ursprungsort geerbt worden sein und muss korrigiert werden:
- restorecon -v /etc/ssl/own.crt /etc/ssl/own.key
- mod_ssl aktivieren
- a2enmod ssl
- vHost für Port 443 ergänzen
- vim /etc/apache2/sites-available/kurswebsite.conf
<VirtualHost *:443>
ServerName it2XX.int
DocumentRoot /srv/kurswebsite
SSLEngine on
SSLCertificateFile /etc/ssl/own.crt
SSLCertificateKeyFile /etc/ssl/own.key
<Directory /srv/kurswebsite>
Options Indexes FollowSymLinks
AllowOverride None
Require all granted
</Directory>
</VirtualHost>
- Apache neu laden
- systemctl reload apache2
- Port-Label prüfen
- semanage port -l | grep http_port_t
Port 443 ist im Standardzustand bereits als http_port_t gelabelt, üblicherweise ist hier kein manueller Eingriff nötig.
- Zugriff testen
- curl -Ik https://it2XX.int/
- Bei Fehlschlag
- Denials prüfen
- ausearch -m avc -ts recent -c httpd
Häufigste Ursache bei SSL-Denials: der Boolean httpd_read_user_content oder ein falsch gelabelter Key/Cert-Pfad außerhalb von /etc/ssl bzw. /etc/pki. In dem Fall:
- semanage fcontext -a -t cert_t "/etc/ssl/own\.(crt|key)"
- restorecon -v /etc/ssl/own.crt /etc/ssl/own.key
Redirect von Port 80 auf 443
- mod_rewrite aktivieren
- a2enmod rewrite
- Port-80-vHost auf Redirect umstellen
- vim /etc/apache2/sites-available/kurswebsite.conf
<VirtualHost *:80>
ServerName it2XX.int
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}$1 [R=301,L]
</VirtualHost>
<VirtualHost *:443>
ServerName it2XX.int
DocumentRoot /srv/kurswebsite
SSLEngine on
SSLCertificateFile /etc/ssl/own.crt
SSLCertificateKeyFile /etc/ssl/own.key
<Directory /srv/kurswebsite>
Options Indexes FollowSymLinks
AllowOverride None
Require all granted
</Directory>
</VirtualHost>
- Apache neu laden
- systemctl reload apache2
- Redirect testen
- curl -I http://it2XX.int/
Erwartet: 301 Moved Permanently mit Location: https://it2XX.int/.
- Vollständigen Redirect-Durchlauf testen
- curl -IL http://it2XX.int/
Erwartet: erst 301, dann 200 OK nach dem Folgen des Redirects.
SELinux-seitig ist hier normalerweise kein weiterer Eingriff nötig, da beide Ports (80 und 443) bereits als http_port_t gelabelt sind und der Redirect selbst keine zusätzlichen Datei- oder Netzwerkzugriffe außerhalb des bestehenden httpd_t-Kontexts benötigt. Falls trotzdem ein Denial auftritt:
- ausearch -m avc -ts recent -c httpd
Reverse Proxy (ausgehender Verkehr)
Standardmäßig darf ein Apache-Prozess unter SELinux keine ausgehenden Netzwerkverbindungen zu einem Backend aufbauen — das betrifft klassisches mod_proxy, aber auch z. B. Verbindungen zu einem Datenbank- oder Applikationsserver.
- Proxy-Module aktivieren
- a2enmod proxy
- a2enmod proxy_http
- vHost mit Reverse Proxy anlegen
- vim /etc/apache2/sites-available/kurswebsite.conf
<VirtualHost *:80>
ServerName proxy.it2XX.int
ProxyPreserveHost On
ProxyPass / http://192.168.2XX.10:8080/
ProxyPassReverse / http://192.168.2XX.10:8080/
</VirtualHost>
- vHost aktivieren
- a2ensite kurswebsite.conf
- systemctl reload apache2
Zugriff vor dem Boolean testen (Denial provozieren)
- Test von der Kommandozeile
- curl -I http://proxy.it2XX.int/
Erwartet an dieser Stelle: 503 Service Unavailable, obwohl das Backend selbst erreichbar ist — Apache darf noch keine ausgehende Verbindung aufbauen.
- Denial im Audit-Log bestätigen
- ausearch -m avc -ts recent -c httpd
Erwartete Meldung u. a.: Zugriff verweigert für httpd_t auf name_connect.
Boolean für ausgehenden Verkehr setzen
- Aktuellen Zustand prüfen
- getsebool httpd_can_network_connect
- Boolean dauerhaft aktivieren
- setsebool -P httpd_can_network_connect on
- Zugriff erneut testen
- curl -I http://proxy.it2XX.int/
Erwartet: 200 OK, sofern das Backend erreichbar ist und selbst korrekt antwortet.
- Bei Fehlschlag
- Denials erneut prüfen
- ausearch -m avc -ts recent -c httpd
Hinweis: Falls das Backend auf einem ungewöhnlichen Port läuft (hier 8080) und dieser nicht bereits als http_port_t, http_cache_port_t o. ä. gelabelt ist, kann zusätzlich ein gezielterer Boolean nötig sein, z. B. httpd_can_network_connect_db bei Datenbank-Backends statt des pauschalen httpd_can_network_connect. Für den Kurs reicht der allgemeine Boolean als Einstieg, sollte aber als Beispiel für "möglichst granular statt pauschal erlauben" erwähnt werden.
Vergleichstabelle (auszufüllen nach Testlauf)
| Dienst / Szenario | Natives Policy-Modul | Setup-Aufwand | Ergebnis |
|---|---|---|---|
| sshd | ja, automatisch | niedrig | |
| Apache2 (Webroot, HTTPS) | ja, automatisch | niedrig | |
| Apache2 als Reverse Proxy | ja, über Boolean | niedrig |