WAF mit HAProxy und Coraza Rocky: Unterschied zwischen den Versionen

Aus Xinux Wiki
Zur Navigation springen Zur Suche springen
(Die Seite wurde neu angelegt: „= WAF-Demo: HAProxy mit Coraza auf Rocky Linux = == Ziel == Diese Übung zeigt eine minimale Web Application Firewall auf Basis von '''HAProxy''' und '''Coraz…“)
 
Zeile 2: Zeile 2:
  
 
== Ziel ==
 
== Ziel ==
Diese Übung zeigt eine minimale Web Application Firewall auf Basis von '''HAProxy''' und '''Coraza''' (OWASP CRS) vor einem einfachen Webserver. Gleichzeitig demonstriert der Aufbau, dass Rocky Linux bereits von Haus aus durch '''firewalld''' und '''SELinux''' gehärtet ist, bevor überhaupt eine WAF ins Spiel kommt. Bewusst nur eine einzige Komponente pro Aufgabe, ein Backend, eine Fehlerseite.
+
Diese Übung zeigt eine minimale Web Application Firewall auf Basis von '''HAProxy''' und '''Coraza''' (OWASP CRS) vor einem Backend-Webserver, inklusive TLS-Terminierung. Gleichzeitig demonstriert der Aufbau, dass Rocky Linux bereits von Haus aus durch '''firewalld''' und '''SELinux''' gehärtet ist, bevor überhaupt eine WAF ins Spiel kommt. Bewusst ein Backend, eine Fehlerseite, wenig Zusatzkomponenten.
  
 
== Topologie ==
 
== Topologie ==
 
;Beschreibung
 
;Beschreibung
Ein einzelner Rocky-Linux-Host waf01.it2XX.int übernimmt alle Rollen.
+
Zwei Rocky-Linux-Hosts.
  
* HAProxy lauscht extern auf Port 80
+
* revproxy.it2XX.int (10.88.2XX.41) – HAProxy terminiert TLS auf Port 443, coraza-spoa läuft lokal auf 127.0.0.1:9000
* coraza-spoa (WAF-Engine) lauscht nur auf 127.0.0.1:9000
+
* www.it2XX.int (10.88.2XX.11) – Backend-Webserver, TLS auf Port 443
* httpd (Backend) lauscht nur auf 127.0.0.1:8888
 
  
Nur Port 80 (und SSH für die Administration) ist von außen erreichbar. Die interne Kommunikation zwischen HAProxy, Coraza und dem Backend bleibt vollständig auf Loopback.
+
Nur revproxy ist von außen erreichbar. www nimmt ausschließlich Verbindungen von revproxy entgegen, kein direkter Zugriff von außen.
  
== Backend-Webserver vorbereiten ==
+
== Zertifikat auf dem Reverse Proxy vorbereiten ==
;httpd installieren
+
;Beschreibung
*dnf install -y httpd
+
HAProxy erwartet Zertifikat und Key als eine gemeinsame PEM-Datei, nicht als zwei getrennte Dateien.
  
;Backend auf einen unüblichen Port legen
+
*cat /etc/ssl/own.crt /etc/ssl/own.key > /etc/haproxy/own-combined.pem
Wir nutzen bewusst Port 8888 statt 80, um später mit SELinux arbeiten zu müssen statt uns auf den Standard-Kontext zu verlassen.
+
*chmod 600 /etc/haproxy/own-combined.pem
 +
 
 +
;SELinux-Kontext setzen
 +
Die Datei liegt außerhalb der Standardpfade, die per Policy bereits cert_t zugeordnet sind. Daher explizit setzen, wieder über semanage statt chcon, damit die Zuordnung auch einen restorecon-Lauf übersteht.
 +
 
 +
*semanage fcontext -a -t cert_t "/etc/haproxy/own-combined\.pem"
 +
*restorecon -v /etc/haproxy/own-combined.pem
 +
 
 +
== Backend-Webserver (www.it2XX.int) ==
 +
;Beschreibung
 +
Falls der Host noch nicht existiert: httpd mit TLS auf 443, eigenes Zertifikat.
  
<pre>
+
*dnf install -y httpd mod_ssl
# /etc/httpd/conf.d/backend.conf
+
*echo "Backend erreichbar" > /var/www/html/index.html
Listen 127.0.0.1:8888
+
*systemctl enable --now httpd
<VirtualHost 127.0.0.1:8888>
 
    DocumentRoot /var/www/backend
 
</VirtualHost>
 
</pre>
 
  
;Testseite anlegen
+
Auf diesem Host ist Port 443 bereits Teil der Standardliste von http_port_t, daher ist hier keine SELinux-Portanpassung nötig – anders als bei einem untypischen Port.
*mkdir -p /var/www/backend
 
*echo "Backend erreichbar" > /var/www/backend/index.html
 
  
 
== Härtung mit firewalld ==
 
== Härtung mit firewalld ==
;Beschreibung
+
;Auf revproxy: nur HTTPS und SSH von außen
Von außen darf ausschließlich HTTP (Port 80) und SSH erreichbar sein. Die internen Ports 8888 und 9000 hängen am Loopback und benötigen daher gar keine Freigabe.
+
*firewall-cmd --permanent --add-service=https
 +
*firewall-cmd --permanent --add-service=ssh
 +
*firewall-cmd --reload
  
*firewall-cmd --permanent --add-service=http
+
;Auf www: 443 ausschließlich von revproxy, kein offener https-Service
 +
*firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.88.2XX.41/32" port protocol="tcp" port="443" accept'
 
*firewall-cmd --permanent --add-service=ssh
 
*firewall-cmd --permanent --add-service=ssh
 
*firewall-cmd --reload
 
*firewall-cmd --reload
Zeile 46: Zeile 52:
 
== Härtung mit SELinux ==
 
== Härtung mit SELinux ==
 
;Beschreibung
 
;Beschreibung
httpd läuft standardmäßig unter dem Typ httpd_t. Port 8888 gehört aber nicht zur Standardliste von http_port_t, daher blockiert SELinux den Start zunächst, obwohl firewalld gar nicht betroffen ist – ein guter Anlass, den Unterschied zwischen Netzwerkfilter und Mandatory Access Control zu zeigen.
+
Auf revproxy muss HAProxy zusätzlich zum Bind auf 443 auch das eigene Zertifikat lesen können. Der Cert-Kontext aus dem vorherigen Schritt regelt genau das – ohne ihn verweigert SELinux den Start von HAProxy, auch wenn firewalld und die Dateiberechtigungen längst passen.
  
;Prüfen, welche Ports http_port_t bereits enthält
+
;Kontrolle, dass 443 zu http_port_t gehört (Standardfall, keine Änderung nötig)
 
*semanage port -l | grep http_port_t
 
*semanage port -l | grep http_port_t
  
;Port 8888 korrekt zu http_port_t hinzufügen
+
;Kontexte der Zertifikatsdatei prüfen
*semanage port -a -t http_port_t -p tcp 8888
+
*ls -Z /etc/haproxy/own-combined.pem
 
 
Damit kann sowohl httpd auf 8888 binden als auch HAProxy (Typ haproxy_t) sich dorthin verbinden, ohne die pauschale Boolean haproxy_connect_any aktivieren zu müssen. Bewusst kein chcon, sondern die persistente Policy-Änderung über semanage.
 
 
 
;httpd starten und aktivieren
 
*systemctl enable --now httpd
 
  
 
== HAProxy installieren ==
 
== HAProxy installieren ==
Zeile 64: Zeile 65:
 
== Coraza (coraza-spoa) installieren ==
 
== Coraza (coraza-spoa) installieren ==
 
;Beschreibung
 
;Beschreibung
coraza-spoa ist ein eigenständiger Go-Daemon, der als SPOE-Agent für HAProxy arbeitet. Es gibt kein Rocky-Paket, aber ein fertiges Release-Binary, sodass kein Compiler auf dem System nötig ist.
+
coraza-spoa ist ein eigenständiger Go-Daemon, der als SPOE-Agent für HAProxy arbeitet und lokal auf revproxy läuft. Es gibt kein Rocky-Paket, aber ein fertiges Release-Binary, sodass kein Compiler auf dem System nötig ist.
  
 
*mkdir -p /etc/coraza-spoa
 
*mkdir -p /etc/coraza-spoa
Zeile 82: Zeile 83:
  
 
applications:
 
applications:
   waf01:
+
   revproxy:
 
     directives: |
 
     directives: |
 
       Include /etc/coraza-spoa/coraza.conf
 
       Include /etc/coraza-spoa/coraza.conf
Zeile 109: Zeile 110:
  
 
== HAProxy-Konfiguration ==
 
== HAProxy-Konfiguration ==
;SPOE-Filter einbinden und Backend definieren
+
;TLS-Terminierung, SPOE-Filter und Backend
 
<pre>
 
<pre>
 
# /etc/haproxy/haproxy.cfg
 
# /etc/haproxy/haproxy.cfg
Zeile 121: Zeile 122:
 
     timeout client 30s
 
     timeout client 30s
 
     timeout server 30s
 
     timeout server 30s
 +
    errorfile 403 /etc/haproxy/errors/403-waf.http
  
 
frontend fe_web
 
frontend fe_web
     bind *:80
+
     bind 10.88.2XX.41:443 ssl crt /etc/haproxy/own-combined.pem
 
     filter spoe engine coraza config /etc/haproxy/coraza.cfg
 
     filter spoe engine coraza config /etc/haproxy/coraza.cfg
 
     http-request deny deny_status 403 if { var(txn.coraza.action) -m str deny }
 
     http-request deny deny_status 403 if { var(txn.coraza.action) -m str deny }
Zeile 129: Zeile 131:
  
 
backend backend_web
 
backend backend_web
     server web01 127.0.0.1:8888 check
+
     server web01 10.88.2XX.11:443 check ssl verify none
  
 
backend coraza-spoa
 
backend coraza-spoa
Zeile 136: Zeile 138:
 
     server spoa1 127.0.0.1:9000 check
 
     server spoa1 127.0.0.1:9000 check
 
</pre>
 
</pre>
 +
 +
;Hinweis
 +
verify none, weil www.it2XX.int im Kurslabor ein eigenes, nicht von einer bekannten CA signiertes Zertifikat verwendet. In einer produktiven Umgebung stattdessen die interne CA hinterlegen.
  
 
== SPOE-Konfiguration für Coraza ==
 
== SPOE-Konfiguration für Coraza ==
Zeile 152: Zeile 157:
  
 
spoe-message coraza-req
 
spoe-message coraza-req
     args app=str(waf01) src-ip=src src-port=src_port dst-ip=dst dst-port=dst_port method=method path=path query=query version=req.ver headers=req.hdrs body=req.body
+
     args app=str(revproxy) src-ip=src src-port=src_port dst-ip=dst dst-port=dst_port method=method path=path query=query version=req.ver headers=req.hdrs body=req.body
 
     event on-frontend-http-request
 
     event on-frontend-http-request
 
</pre>
 
</pre>
Zeile 158: Zeile 163:
 
== Eigene Fehlerseite ==
 
== Eigene Fehlerseite ==
 
;Beschreibung
 
;Beschreibung
Genau eine Fehlerseite für alle von der WAF geblockten Requests, kein zusätzliches Fehlerseiten-Set.
+
Genau eine Fehlerseite für alle von der WAF geblockten Requests.
  
 
<pre>
 
<pre>
Zeile 166: Zeile 171:
  
 
<html><body><h1>Anfrage blockiert</h1><p>Diese Anfrage wurde von der Web Application Firewall abgelehnt.</p></body></html>
 
<html><body><h1>Anfrage blockiert</h1><p>Diese Anfrage wurde von der Web Application Firewall abgelehnt.</p></body></html>
</pre>
 
 
;Fehlerseite in haproxy.cfg einbinden
 
In den defaults-Block ergänzen:
 
 
<pre>
 
errorfile 403 /etc/haproxy/errors/403-waf.http
 
 
</pre>
 
</pre>
  
Zeile 179: Zeile 177:
 
== Test ==
 
== Test ==
 
;Normale Anfrage
 
;Normale Anfrage
*curl -i http://waf01.it2XX.int/
+
*curl -ik https://revproxy.it2XX.int/
  
 
;Angriffsversuch, der von Coraza/OWASP CRS erkannt werden sollte
 
;Angriffsversuch, der von Coraza/OWASP CRS erkannt werden sollte
*curl -i "http://waf01.it2XX.int/?id=1' OR '1'='1"
+
*curl -ik "https://revproxy.it2XX.int/?id=1' OR '1'='1"
  
Die erste Anfrage liefert die Backend-Testseite mit Status 200, die zweite die eigene 403-Fehlerseite, ohne dass die Anfrage überhaupt das Backend erreicht.
+
Die erste Anfrage liefert die Backend-Testseite mit Status 200, die zweite die eigene 403-Fehlerseite, ohne dass die Anfrage überhaupt www.it2XX.int erreicht. Das -k ist nur wegen des selbstsignierten own.crt für den Test nötig.
  
 
== Zusammenfassung ==
 
== Zusammenfassung ==
* firewalld lässt von außen ausschließlich HTTP und SSH zu
+
* firewalld lässt auf revproxy von außen ausschließlich HTTPS und SSH zu; www akzeptiert 443 nur von revproxy
* SELinux verhindert, dass httpd oder HAProxy Ports außerhalb der freigegebenen Policy nutzen, unabhängig von firewalld
+
* SELinux sorgt dafür, dass HAProxy nur mit korrekt gelabeltem Zertifikat startet, unabhängig von firewalld und Dateiberechtigungen
* HAProxy mit Coraza blockiert bekannte Angriffsmuster, bevor sie das Backend erreichen
+
* HAProxy mit Coraza terminiert TLS und blockiert bekannte Angriffsmuster, bevor sie www.it2XX.int erreichen
 
* Genau eine Fehlerseite für alle WAF-Blocks
 
* Genau eine Fehlerseite für alle WAF-Blocks

Version vom 6. August 2026, 04:38 Uhr

WAF-Demo: HAProxy mit Coraza auf Rocky Linux

Ziel

Diese Übung zeigt eine minimale Web Application Firewall auf Basis von HAProxy und Coraza (OWASP CRS) vor einem Backend-Webserver, inklusive TLS-Terminierung. Gleichzeitig demonstriert der Aufbau, dass Rocky Linux bereits von Haus aus durch firewalld und SELinux gehärtet ist, bevor überhaupt eine WAF ins Spiel kommt. Bewusst ein Backend, eine Fehlerseite, wenig Zusatzkomponenten.

Topologie

Beschreibung

Zwei Rocky-Linux-Hosts.

  • revproxy.it2XX.int (10.88.2XX.41) – HAProxy terminiert TLS auf Port 443, coraza-spoa läuft lokal auf 127.0.0.1:9000
  • www.it2XX.int (10.88.2XX.11) – Backend-Webserver, TLS auf Port 443

Nur revproxy ist von außen erreichbar. www nimmt ausschließlich Verbindungen von revproxy entgegen, kein direkter Zugriff von außen.

Zertifikat auf dem Reverse Proxy vorbereiten

Beschreibung

HAProxy erwartet Zertifikat und Key als eine gemeinsame PEM-Datei, nicht als zwei getrennte Dateien.

  • cat /etc/ssl/own.crt /etc/ssl/own.key > /etc/haproxy/own-combined.pem
  • chmod 600 /etc/haproxy/own-combined.pem
SELinux-Kontext setzen

Die Datei liegt außerhalb der Standardpfade, die per Policy bereits cert_t zugeordnet sind. Daher explizit setzen, wieder über semanage statt chcon, damit die Zuordnung auch einen restorecon-Lauf übersteht.

  • semanage fcontext -a -t cert_t "/etc/haproxy/own-combined\.pem"
  • restorecon -v /etc/haproxy/own-combined.pem

Backend-Webserver (www.it2XX.int)

Beschreibung

Falls der Host noch nicht existiert: httpd mit TLS auf 443, eigenes Zertifikat.

  • dnf install -y httpd mod_ssl
  • echo "Backend erreichbar" > /var/www/html/index.html
  • systemctl enable --now httpd

Auf diesem Host ist Port 443 bereits Teil der Standardliste von http_port_t, daher ist hier keine SELinux-Portanpassung nötig – anders als bei einem untypischen Port.

Härtung mit firewalld

Auf revproxy
nur HTTPS und SSH von außen
  • firewall-cmd --permanent --add-service=https
  • firewall-cmd --permanent --add-service=ssh
  • firewall-cmd --reload
Auf www
443 ausschließlich von revproxy, kein offener https-Service
  • firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.88.2XX.41/32" port protocol="tcp" port="443" accept'
  • firewall-cmd --permanent --add-service=ssh
  • firewall-cmd --reload
Kontrolle
  • firewall-cmd --list-all

Härtung mit SELinux

Beschreibung

Auf revproxy muss HAProxy zusätzlich zum Bind auf 443 auch das eigene Zertifikat lesen können. Der Cert-Kontext aus dem vorherigen Schritt regelt genau das – ohne ihn verweigert SELinux den Start von HAProxy, auch wenn firewalld und die Dateiberechtigungen längst passen.

Kontrolle, dass 443 zu http_port_t gehört (Standardfall, keine Änderung nötig)
  • semanage port -l | grep http_port_t
Kontexte der Zertifikatsdatei prüfen
  • ls -Z /etc/haproxy/own-combined.pem

HAProxy installieren

  • dnf install -y haproxy

Coraza (coraza-spoa) installieren

Beschreibung

coraza-spoa ist ein eigenständiger Go-Daemon, der als SPOE-Agent für HAProxy arbeitet und lokal auf revproxy läuft. Es gibt kein Rocky-Paket, aber ein fertiges Release-Binary, sodass kein Compiler auf dem System nötig ist.

OWASP Core Rule Set und Basisregeln laden
Coraza-Konfiguration
# /etc/coraza-spoa/config.yaml
bind: 127.0.0.1:9000
loglevel: info

applications:
  revproxy:
    directives: |
      Include /etc/coraza-spoa/coraza.conf
      SecRuleEngine On
      Include /etc/coraza-spoa/crs-setup.conf
      Include /etc/coraza-spoa/coreruleset/rules/*.conf
systemd-Unit
# /etc/systemd/system/coraza-spoa.service
[Unit]
Description=Coraza SPOA für HAProxy
After=network.target

[Service]
ExecStart=/usr/local/bin/coraza-spoa -config /etc/coraza-spoa/config.yaml
Restart=on-failure

[Install]
WantedBy=multi-user.target
  • systemctl daemon-reload
  • systemctl enable --now coraza-spoa

HAProxy-Konfiguration

TLS-Terminierung, SPOE-Filter und Backend
# /etc/haproxy/haproxy.cfg
global
    log /dev/log local0
    maxconn 2000

defaults
    mode http
    timeout connect 5s
    timeout client 30s
    timeout server 30s
    errorfile 403 /etc/haproxy/errors/403-waf.http

frontend fe_web
    bind 10.88.2XX.41:443 ssl crt /etc/haproxy/own-combined.pem
    filter spoe engine coraza config /etc/haproxy/coraza.cfg
    http-request deny deny_status 403 if { var(txn.coraza.action) -m str deny }
    default_backend backend_web

backend backend_web
    server web01 10.88.2XX.11:443 check ssl verify none

backend coraza-spoa
    mode tcp
    option spop-check
    server spoa1 127.0.0.1:9000 check
Hinweis

verify none, weil www.it2XX.int im Kurslabor ein eigenes, nicht von einer bekannten CA signiertes Zertifikat verwendet. In einer produktiven Umgebung stattdessen die interne CA hinterlegen.

SPOE-Konfiguration für Coraza

# /etc/haproxy/coraza.cfg
[coraza]
spoe-agent coraza-agent
    messages         coraza-req
    option var-prefix coraza
    option set-on-error error
    timeout hello     2s
    timeout idle      2m
    timeout processing 500ms
    use-backend       coraza-spoa
    log               global

spoe-message coraza-req
    args app=str(revproxy) src-ip=src src-port=src_port dst-ip=dst dst-port=dst_port method=method path=path query=query version=req.ver headers=req.hdrs body=req.body
    event on-frontend-http-request

Eigene Fehlerseite

Beschreibung

Genau eine Fehlerseite für alle von der WAF geblockten Requests.

# /etc/haproxy/errors/403-waf.http
HTTP/1.1 403 Forbidden
Content-Type: text/html

<html><body><h1>Anfrage blockiert</h1><p>Diese Anfrage wurde von der Web Application Firewall abgelehnt.</p></body></html>
  • systemctl enable --now haproxy

Test

Normale Anfrage
Angriffsversuch, der von Coraza/OWASP CRS erkannt werden sollte

Die erste Anfrage liefert die Backend-Testseite mit Status 200, die zweite die eigene 403-Fehlerseite, ohne dass die Anfrage überhaupt www.it2XX.int erreicht. Das -k ist nur wegen des selbstsignierten own.crt für den Test nötig.

Zusammenfassung

  • firewalld lässt auf revproxy von außen ausschließlich HTTPS und SSH zu; www akzeptiert 443 nur von revproxy
  • SELinux sorgt dafür, dass HAProxy nur mit korrekt gelabeltem Zertifikat startet, unabhängig von firewalld und Dateiberechtigungen
  • HAProxy mit Coraza terminiert TLS und blockiert bekannte Angriffsmuster, bevor sie www.it2XX.int erreichen
  • Genau eine Fehlerseite für alle WAF-Blocks