WAF mit HAProxy und Coraza Rocky: Unterschied zwischen den Versionen
(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 | + | 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 | ||
| − | + | Zwei Rocky-Linux-Hosts. | |
| − | * HAProxy | + | * 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 | + | 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 == | == 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 | ||
| − | *firewall-cmd --permanent --add- | + | ;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 | ||
| − | + | 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 | *semanage port -l | grep http_port_t | ||
| − | ; | + | ;Kontexte der Zertifikatsdatei prüfen |
| − | * | + | *ls -Z /etc/haproxy/own-combined.pem |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
== 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: | ||
| − | + | 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 | + | ;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 | + | 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 | + | 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( | + | 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 | + | 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> | </pre> | ||
| Zeile 179: | Zeile 177: | ||
== Test == | == Test == | ||
;Normale Anfrage | ;Normale Anfrage | ||
| − | *curl - | + | *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 - | + | *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 | + | 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 | + | * firewalld lässt auf revproxy von außen ausschließlich HTTPS und SSH zu; www akzeptiert 443 nur von revproxy |
| − | * SELinux | + | * 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 | + | * 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.
- mkdir -p /etc/coraza-spoa
- curl -L -o /usr/local/bin/coraza-spoa https://github.com/corazawaf/coraza-spoa/releases/latest/download/coraza-spoa_linux_amd64
- chmod +x /usr/local/bin/coraza-spoa
- OWASP Core Rule Set und Basisregeln laden
- git clone --depth=1 https://github.com/coreruleset/coreruleset /etc/coraza-spoa/coreruleset
- curl -L -o /etc/coraza-spoa/coraza.conf https://raw.githubusercontent.com/corazawaf/coraza/main/coraza.conf-recommended
- cp /etc/coraza-spoa/coreruleset/crs-setup.conf.example /etc/coraza-spoa/crs-setup.conf
- 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
- curl -ik https://revproxy.it2XX.int/
- Angriffsversuch, der von Coraza/OWASP CRS erkannt werden sollte
- 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 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