WAF mit HAProxy und Coraza Rocky: Unterschied zwischen den Versionen

Aus Xinux Wiki
Zur Navigation springen Zur Suche springen
Keine Bearbeitungszusammenfassung
Konsolidiert: Rocky-Coraza-Fassung als kanonische Seite (bisher drei Fast-Duplikate)
 
(28 dazwischenliegende Versionen von einem anderen Benutzer werden nicht angezeigt)
Zeile 1: Zeile 1:
= WAF-Demo: HAProxy mit Coraza auf Rocky Linux =
== WAF-Demo: HAProxy mit Coraza auf Rocky Linux ==


== Ziel ==
* Animation: [https://xinux.org/waf-haproxy-coraza.html WAF: HAProxy mit Coraza] ([https://xinux.org/waf-haproxy-coraza-einfach.html einfach])
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.
 
WAF mit HAProxy und Coraza (OWASP CRS) vor web.it2XX.xinmen.de, TLS-Terminierung auf waf.it2XX.xinmen.de.
 
==Maschine==
*Wir klonen das Rocky Template
 
==Vorgaben==
{| class="wikitable"
! Einstellung !! Wert
|-
| '''Name''' || waf.it2XX.xinmen.de
|-
| '''IP-Adresse''' || 10.88.2XX.5/24
|-
| '''Gateway''' || 10.88.2XX.1
|-
| '''Nameserver''' || 1.1.1.1
|-
| '''Search-Domain''' || it2XX.xinmen.de
|-
| '''Benutzername''' || kit
|-
| '''Benutzerpasswort''' || kit
|-
| '''Administrator''' || root
|-
| '''Administratorpasswort''' || radler
|-
| '''Netz''' || DMZ
|}
 
==Einstellungen==
;Selbterklärend
*rocky-setup.sh -f waf.it2XX.xinmen.de -a 10.88.2XX.5/24 -g 10.88.2XX.1 -n 1.1.1.1
 
==Zertifikate besorgen==
*wget https://xinux.de/downloads/script/get-cert.sh
*sudo bash get-cert.sh


== Topologie ==
== Topologie ==
;Beschreibung
* waf.it2XX.xinmen.de (10.88.2XX.5) – HAProxy terminiert TLS auf Port 443, coraza-spoa läuft lokal auf 127.0.0.1:9000
Zwei Rocky-Linux-Hosts.
* web.it2XX.xinmen.de (10.88.2XX.2) – bestehender Backend-Webserver, TLS auf Port 443


* revproxy.it2XX.int (10.88.2XX.41) – HAProxy terminiert TLS auf Port 443, coraza-spoa läuft lokal auf 127.0.0.1:9000
== HAProxy installieren ==
* www.it2XX.int (10.88.2XX.11) – Backend-Webserver, TLS auf Port 443
*dnf install -y haproxy git
 
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 ==
== Zertifikat auf dem Reverse Proxy vorbereiten ==
;Beschreibung
;Beschreibung
HAProxy erwartet Zertifikat und Key als eine gemeinsame PEM-Datei, nicht als zwei getrennte Dateien.
HAProxy erwartet Zertifikat und Key als eine gemeinsame PEM-Datei.


*cat /etc/ssl/own.crt /etc/ssl/own.key > /etc/haproxy/own-combined.pem
*cat /etc/ssl/own.crt /etc/ssl/own.key > /etc/haproxy/own-combined.pem
*chmod 600 /etc/haproxy/own-combined.pem
*chmod 600 /etc/haproxy/own-combined.pem


;SELinux-Kontext setzen
==Härtung 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"
*semanage fcontext -a -t cert_t "/etc/haproxy/own-combined\.pem"
*restorecon -v /etc/haproxy/own-combined.pem
*restorecon -v /etc/haproxy/own-combined.pem


== Backend-Webserver (www.it2XX.int) ==
== Härtung mit firewalld (revproxy) ==
;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=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
*firewall-cmd --reload


;Kontrolle
;Kontrolle
*firewall-cmd --list-all
*firewall-cmd --list-services
 
== 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)
== Härtung mit SELinux (revproxy) ==
;Kontrolle der Kontexte
*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
*ls -Z /etc/haproxy/own-combined.pem
unconfined_u:object_r:'''cert_t''':s0 /etc/haproxy/own-combined.pem


== HAProxy installieren ==
== Härtung - Haproxy ausgehende Verbindungen erlauben ==
*dnf install -y haproxy
*setsebool -P haproxy_connect_any on
*systemctl restart haproxy


== Coraza (coraza-spoa) installieren ==
== Coraza (coraza-spoa) installieren ==
;Beschreibung
;coraza-spoa Binary installieren
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.
*dnf install -y golang git
*git clone --depth=1 https://github.com/corazawaf/coraza-spoa /usr/local/src/coraza-spoa
*cd /usr/local/src/coraza-spoa
*go run mage.go build
*cp build/coraza-spoa /usr/local/bin/coraza-spoa
*chmod +x /usr/local/bin/coraza-spoa


*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
;OWASP Core Rule Set und Basisregeln laden
*mkdir -p /etc/coraza-spoa
*git clone --depth=1 https://github.com/coreruleset/coreruleset /etc/coraza-spoa/coreruleset
*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
*curl -L -o /etc/coraza-spoa/coraza.conf https://raw.githubusercontent.com/corazawaf/coraza/main/coraza.conf-recommended
Zeile 77: Zeile 93:


;Coraza-Konfiguration
;Coraza-Konfiguration
*vim /etc/coraza-spoa/config.yaml
<pre>
<pre>
# /etc/coraza-spoa/config.yaml
# /etc/coraza-spoa/config.yaml
bind: 127.0.0.1:9000
bind: 127.0.0.1:9000
loglevel: info
log_level: info
log_file: /dev/stdout
log_format: console


applications:
applications:
   revproxy:
   - name: revproxy
    log_level: info
    log_file: /dev/stdout
    log_format: console
     directives: |
     directives: |
       Include /etc/coraza-spoa/coraza.conf
       Include @coraza.conf-recommended
      SecRuleEngine On
       Include /etc/coraza-spoa/crs-setup.conf
       Include /etc/coraza-spoa/crs-setup.conf
       Include /etc/coraza-spoa/coreruleset/rules/*.conf
       Include /etc/coraza-spoa/coreruleset/rules/*.conf
      SecRuleEngine On
    response_check: false
    transaction_ttl_ms: 5000                           
</pre>
</pre>


;systemd-Unit
;systemd-Unit
*vim /etc/systemd/system/coraza-spoa.service
<pre>
<pre>
# /etc/systemd/system/coraza-spoa.service
# /etc/systemd/system/coraza-spoa.service
Zeile 110: Zeile 135:


== HAProxy-Konfiguration ==
== HAProxy-Konfiguration ==
;TLS-Terminierung, SPOE-Filter und Backend
;Konfiguration
*vim /etc/haproxy/haproxy.cfg
<pre>
<pre>
# /etc/haproxy/haproxy.cfg
# /etc/haproxy/haproxy.cfg
Zeile 125: Zeile 151:


frontend fe_web
frontend fe_web
     bind 10.88.2XX.41:443 ssl crt /etc/haproxy/own-combined.pem
     bind 10.88.2XX.5: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 131: Zeile 157:


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


backend coraza-spoa
backend coraza-spoa
Zeile 138: Zeile 164:
     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 ==
*vim /etc/haproxy/coraza.cfg
<pre>
<pre>
# /etc/haproxy/coraza.cfg
[coraza]
[coraza]
spoe-agent coraza-agent
spoe-agent coraza-agent
Zeile 162: Zeile 185:


== Eigene Fehlerseite ==
== Eigene Fehlerseite ==
;Beschreibung
*mkdir -p /etc/haproxy/errors
Genau eine Fehlerseite für alle von der WAF geblockten Requests.
*vim /etc/haproxy/errors/403-waf.http
 
<pre>
<pre>
# /etc/haproxy/errors/403-waf.http
HTTP/1.1 403 Forbidden
HTTP/1.1 403 Forbidden
Content-Type: text/html
Content-Type: text/html
Zeile 172: Zeile 193:
<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>
 
*haproxy -c -f /etc/haproxy/haproxy.cfg
*systemctl restart haproxy
*systemctl enable --now haproxy
*systemctl enable --now haproxy


== Test ==
== Test ==
;Normale Anfrage
*curl -ik https://waf.it2XX.xinmen.de/
*curl -ik https://revproxy.it2XX.int/
*curl -ik "https://waf.it2XX.xinmen.de/?id=1' OR '1'='1"
 
;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 ==
Erste Anfrage: Status 200 von web.it2XX.xinmen.de. Zweite Anfrage: 403-Fehlerseite, web.it2XX.xinmen.de wird nicht erreicht.
* 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

Aktuelle Version vom 31. Oktober 2026, 15:14 Uhr

WAF-Demo: HAProxy mit Coraza auf Rocky Linux

WAF mit HAProxy und Coraza (OWASP CRS) vor web.it2XX.xinmen.de, TLS-Terminierung auf waf.it2XX.xinmen.de.

Maschine

  • Wir klonen das Rocky Template

Vorgaben

Einstellung Wert
Name waf.it2XX.xinmen.de
IP-Adresse 10.88.2XX.5/24
Gateway 10.88.2XX.1
Nameserver 1.1.1.1
Search-Domain it2XX.xinmen.de
Benutzername kit
Benutzerpasswort kit
Administrator root
Administratorpasswort radler
Netz DMZ

Einstellungen

Selbterklärend
  • rocky-setup.sh -f waf.it2XX.xinmen.de -a 10.88.2XX.5/24 -g 10.88.2XX.1 -n 1.1.1.1

Zertifikate besorgen

Topologie

  • waf.it2XX.xinmen.de (10.88.2XX.5) – HAProxy terminiert TLS auf Port 443, coraza-spoa läuft lokal auf 127.0.0.1:9000
  • web.it2XX.xinmen.de (10.88.2XX.2) – bestehender Backend-Webserver, TLS auf Port 443

HAProxy installieren

  • dnf install -y haproxy git

Zertifikat auf dem Reverse Proxy vorbereiten

Beschreibung

HAProxy erwartet Zertifikat und Key als eine gemeinsame PEM-Datei.

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

Härtung SELinux-Kontext setzen

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

Härtung mit firewalld (revproxy)

  • firewall-cmd --permanent --add-service=https
  • firewall-cmd --reload
Kontrolle
  • firewall-cmd --list-services

Härtung mit SELinux (revproxy)

Kontrolle der Kontexte
  • semanage port -l | grep http_port_t
  • ls -Z /etc/haproxy/own-combined.pem
unconfined_u:object_r:cert_t:s0 /etc/haproxy/own-combined.pem

Härtung - Haproxy ausgehende Verbindungen erlauben

  • setsebool -P haproxy_connect_any on
  • systemctl restart haproxy

Coraza (coraza-spoa) installieren

coraza-spoa Binary installieren
  • dnf install -y golang git
  • git clone --depth=1 https://github.com/corazawaf/coraza-spoa /usr/local/src/coraza-spoa
  • cd /usr/local/src/coraza-spoa
  • go run mage.go build
  • cp build/coraza-spoa /usr/local/bin/coraza-spoa
  • chmod +x /usr/local/bin/coraza-spoa


OWASP Core Rule Set und Basisregeln laden
Coraza-Konfiguration
  • vim /etc/coraza-spoa/config.yaml
# /etc/coraza-spoa/config.yaml
bind: 127.0.0.1:9000
log_level: info
log_file: /dev/stdout
log_format: console

applications:
  - name: revproxy
    log_level: info
    log_file: /dev/stdout
    log_format: console
    directives: |
      Include @coraza.conf-recommended
      Include /etc/coraza-spoa/crs-setup.conf
      Include /etc/coraza-spoa/coreruleset/rules/*.conf
      SecRuleEngine On
    response_check: false
    transaction_ttl_ms: 5000                             
systemd-Unit
  • vim /etc/systemd/system/coraza-spoa.service
# /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

Konfiguration
  • vim /etc/haproxy/haproxy.cfg
# /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.5: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.2:443 check ssl verify none

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

SPOE-Konfiguration für Coraza

  • vim /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

  • mkdir -p /etc/haproxy/errors
  • vim /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>
  • haproxy -c -f /etc/haproxy/haproxy.cfg
  • systemctl restart haproxy
  • systemctl enable --now haproxy

Test

Erste Anfrage: Status 200 von web.it2XX.xinmen.de. Zweite Anfrage: 403-Fehlerseite, web.it2XX.xinmen.de wird nicht erreicht.