Knoten einrichten
Knoten einrichten
Section titled “Knoten einrichten”ZPO installiert nichts selbst auf den Knoten — es erwartet eine bestimmte Grundkonfiguration und prüft sie beim Verbindungstest. Diese Seite beschreibt das Installationsskript, die Vorgaben im Detail und die Anbindung der Resolver.
Mindestversionen
Section titled “Mindestversionen”| Gegenstand | Wert |
|---|---|
| HAProxy | 2.8 oder neuer |
| HAProxy Data Plane API | 3.x (Pfad-Präfix /v3), über HTTPS mit Basic Auth |
| MikroTik RouterOS | 7.x mit REST-API |
| AdGuard Home | mit Web-API (/control/...) |
ZPO spricht die Data Plane API ausschließlich als v3. Ein Knoten mit 2.x antwortet auf ZPOs Anfragen mit 404 — der Verbindungstest meldet das.
HAProxy per Installationsskript
Section titled “HAProxy per Installationsskript”Der schnellste Weg zum HAProxy-Knoten ist das Installationsskript
setup-haproxy.sh. Es richtet einen frischen Ubuntu-Server in einem Schritt
so ein, wie ZPO ihn erwartet. Auf dem künftigen Knoten genügt:
curl -fsSL https://repo.techeve.de/zpo/setup-haproxy.sh | sudo bashMit festem Hostnamen:
curl -fsSL https://repo.techeve.de/zpo/setup-haproxy.sh | sudo bash -s -- --name haproxy-standortDas Skript liegt auch im Repository unter packaging/setup-haproxy.sh und im
Debian-Paket unter /usr/share/doc/zpo/setup-haproxy.sh; von dort lässt es
sich ebenso direkt starten (sudo ./setup-haproxy.sh --name haproxy-standort).
Optionen: --api-bind <ip> (Vorgabe: die erste eigene IP — nicht 0.0.0.0),
--api-port <port> (Vorgabe 5555), --api-user <name> (Vorgabe zpo),
--allow-from <netz>[,<netz>…] (Verwaltungs-Schnittstelle, LAPI und Metriken
nur aus diesen Netzen — die Adresse von ZPO und, auf dem Proxy mit der LAPI,
die der angebundenen Proxys; wirkt über ufw, ersetzt die Freigaben für alle
aus früheren Läufen und gilt für spätere Läufe weiter), --crowdsec (siehe
unten).
Nach einem Update von ZPO das Skript erneut ausführen, wenn ZPO am Knoten
„Grundkonfiguration veraltet” meldet: Seit 0.6.0 braucht die Grundkonfiguration
die Maps zpo_login.map und zpo_trusted.map (Login-Schutz, vertrauenswürdige
Netze), die Fehlerraten- und Verbindungsgrenze und — mit CrowdSec — WAF,
Prüfseite und Köderpfade.
Das Skript:
- setzt den Hostnamen,
- installiert HAProxy und die Data Plane API (3.4.3),
- legt
/etc/haproxy/mapsund/etc/haproxy/certsan, mit einer ersten Zeile in jeder Map-Datei, - erzeugt ein selbstsigniertes Zertifikat für die Verwaltung (in
/etc/zpo-tls) und gibt dessen Fingerabdruck aus, - schreibt
haproxy.cfgunddataplaneapi.yml(eine vorhandene Konfiguration wird vorher gesichert), - richtet den systemd-Dienst samt Härtung ein,
- gibt bei aktiver ufw-Firewall den API-Port frei,
- legt die Zugangsdaten in
/root/haproxy-credentialsab (nur für root lesbar) und prüft die Schnittstelle am Ende selbst.
Das Skript ist wiederholbar: ein zweiter Lauf ändert nichts, was schon stimmt, und erzeugt insbesondere kein neues Passwort.
Was das Skript meldet, was es bedeutet und wie man es behebt, steht unter Knoten: Fehlersuche.
Verwaltung über TLS und Fingerabdruck
Section titled “Verwaltung über TLS und Fingerabdruck”Die Verwaltungs-Schnittstellen sprechen TLS: die Data Plane API (Port
5555), die LAPI von CrowdSec (Port 8080) und die Steuerung der Prüfseite
(Port 5556). Alle drei tragen dasselbe selbstsignierte Zertifikat aus
/etc/zpo-tls/cert.pem, der Schlüssel liegt daneben. Eine CA gibt es nicht;
ZPO prüft das Zertifikat deshalb am Fingerabdruck (SHA-256), den das
Skript am Ende ausgibt und den man in ZPO beim Knoten einträgt. Stimmt der
Fingerabdruck nicht mehr, verweigert ZPO die Verbindung.
Die Metriken der CrowdSec-Security-Engine (Port 6060) bleiben HTTP: CrowdSec
kann dort kein TLS. Sie geben nur Zahlen preis, ohne Anmeldung, und sind mit
--allow-from auf das Netz von ZPO beschränkt.
Das Zertifikat wird nur neu erzeugt, wenn sich die Verwaltungsadresse ändert
(--api-bind). Dann ändert sich der Fingerabdruck, und er muss in ZPO erneut
eingetragen werden.
Bestehende Knoten umstellen. Ein Knoten, der noch über HTTP eingerichtet ist, spricht nach einem Lauf des neuen Skripts nur noch TLS. Deshalb in dieser Reihenfolge:
- ZPO auf die Version mit TLS-Unterstützung bringen.
- Auf dem Knoten
setup-haproxy.shmit denselben Optionen erneut ausführen. Der Lauf erzeugt das Zertifikat und stellt die Dienste um; die Ausgabe nennt den Fingerabdruck. - In ZPO den Knoten auf
https://…umstellen und den Fingerabdruck eintragen. Hat der Standort CrowdSec, die LAPI ebenso.
Zwischen Schritt 2 und 3 kann ZPO den Knoten nicht erreichen und meldet ihn
als gestört. Beides deshalb kurz hintereinander erledigen. Angebundene Proxys
(--crowdsec-join) brauchen dazu einen neuen Befehl von --crowdsec-proxy,
weil der Befehl jetzt den Fingerabdruck der LAPI enthält.
Die Grundkonfiguration im Detail
Section titled “Die Grundkonfiguration im Detail”Generisches Frontend statt ACLs
Section titled “Generisches Frontend statt ACLs”Eine Host-Route ist in HAProxy normalerweise eine ACL plus use_backend in der
Konfiguration — jede neue Domain wäre eine Änderung mit Reload. ZPO geht den
anderen Weg: ein generisches Frontend zpo_in schlägt den Hostnamen in einer
Map-Datei nach und wählt daraus das Backend. Map-Einträge lassen sich über die
Runtime-API zur Laufzeit setzen; eine neue Domain braucht keinen Reload.
use_backend %[var(txn.zpo_host),map(/etc/haproxy/maps/zpo_hosts.map)]Die Map-Dateien
Section titled “Die Map-Dateien”| Map | Inhalt | Wirkung |
|---|---|---|
zpo_hosts.map | Hostname → Backend-Name | Routing der Dienste |
zpo_ipgroups.map | CIDR → Name der Zugangsliste | Netze der Zugangslisten |
zpo_auth.map | <liste>:<base64(benutzer:passwort)> → 1 | Basic-Auth-Zugänge der Zugangslisten |
zpo_ratelimits.map | Hostname → <limit>/<liste>/<schalter>/<modus>/<fehlerprofil>/<fehlergrenze>/<verbindungsgrenze> | Rate-Limit, Fehlerraten- und Verbindungsgrenze (429), Zugangsliste und Schalter des Dienstes |
zpo_login.map | <host><pfad> → Höchstzahl | Login-Schutz: Anmeldeversuche je Client in zehn Minuten |
zpo_trusted.map | CIDR → Name der IP-Gruppe | vertrauenswürdige Netze — CrowdSec prüft sie nie |
zpo_acme.map | Token → Antwort | HTTP-Challenge: nur solange eine Bestellung läuft |
zpo_errors.map | <fehlerprofil>:<fall> → Fehler-Backend | eigene Fehlerseiten für abgewiesene Anfragen |
Der Modus der Zugangsliste entscheidet, was ein Client vorweisen muss:
ip_only das passende Netz (sonst 403), basic_only eine gültige
Anmeldung (sonst 401), and beides, or eines von beidem. Das Rate-Limit
zählt je Client-IP und Dienst — wer einen Dienst stark nutzt, stößt
dadurch nicht an das Limit eines anderen.
Alle liegen im maps_dir der Data Plane API und lassen sich reload-frei
über die Runtime-API ändern. Die Dateien dürfen nicht leer sein: HAProxy
lädt eine leere Map zwar, registriert sie aber nicht in der Runtime — und was
dort nicht steht, lässt sich nicht beschreiben. Deshalb die Platzhalter
(zpo-placeholder.invalid zeigt auf das Auffang-Backend zpo_unknown;
203.0.113.0/24 ist ein nie vergebenes Testnetz; Limit 0 heißt aus). Fehlt
eine der vier, meldet ZPO den Knoten mit dem Hinweis, das Einrichtungsskript
erneut auszuführen.
Zertifikatsverzeichnis
Section titled “Zertifikatsverzeichnis”Das Frontend bindet mit ssl crt /etc/haproxy/certs/ ein ganzes Verzeichnis.
Neue Zertifikate greifen dadurch ohne Reload. Das Verzeichnis braucht mindestens
eine Datei, sonst scheitert der Start — bis ZPO echte Zertifikate ausrollt,
genügt das selbstsignierte Platzhalter-Zertifikat.
Data Plane API und Runtime-Socket
Section titled “Data Plane API und Runtime-Socket”Die API (v3) verwaltet die haproxy.cfg und lädt HAProxy neu. Kernausschnitt
aus dataplaneapi.yml:
dataplaneapi: host: <interne-ip> port: 5555 user: - name: zpo insecure: false password: "$5$…" # Hash, kein Klartext resources: maps_dir: /etc/haproxy/maps ssl_certs_dir: /etc/haproxy/certshaproxy: config_file: /etc/haproxy/haproxy.cfg haproxy_bin: /usr/sbin/haproxy reload: reload_cmd: systemctl reload haproxy reload_delay: 5Der Runtime-Socket (stats socket /run/haproxy/admin.sock … level admin expose-fd listeners) ist der Weg, auf dem ZPO Map-Einträge und den Zustand
einzelner Server ohne Reload setzt.
Die Verwaltungs-Schnittstelle gehört nicht ins offene Netz: sie bindet an eine interne Adresse oder steht hinter dem VPN, über das ZPO den Standort erreicht.
Eigentum an der Konfiguration
Section titled “Eigentum an der Konfiguration”Nach der Einrichtung gehört die haproxy.cfg ZPO allein — von Hand geänderte
Zeilen überschreibt die Data Plane API beim nächsten Schreibzugriff. ZPO legt
Backends mit dem Präfix zpo_ an und fasst nur solche an; alles ohne dieses
Präfix bleibt unberührt.
Knoten in ZPO eintragen und testen
Section titled “Knoten in ZPO eintragen und testen”Der Proxy-Knoten bekommt in ZPO die Verwaltungs-Adresse (https://<ip>:5555),
den Fingerabdruck des Zertifikats, Benutzer und Passwort aus
/root/haproxy-credentials sowie die internen
Adressen (IPv4/IPv6), unter denen Clients im lokalen Netz den Proxy erreichen —
die landen in den lokalen DNS-Einträgen. Optional kommen die öffentlichen
Adressen dazu, unter denen der Proxy aus dem Internet erreichbar ist.
Der Verbindungstest meldet drei Stände:
- erreichbar — API antwortet, Zugangsdaten stimmen,
zpo_hosts.mapist da. - beeinträchtigt — die API antwortet, aber die Map-Datei fehlt. Der Hinweis nennt sie; bis dahin lässt sich auf dem Knoten nichts routen.
- nicht erreichbar — keine Antwort oder abgelehnte Anmeldung; der Grund steht am Knoten.
CrowdSec (optional)
Section titled “CrowdSec (optional)”CrowdSec erkennt Angriffe in den HAProxy-Logs und sperrt direkt in HAProxy. Je Standort gibt es eine LAPI (Local API, die Stelle, an der CrowdSec Erkennungen sammelt und Sperren verwaltet). Sie läuft auf einem Proxy des Standorts; weitere Proxys desselben Standorts melden dorthin und holen ihre Sperren von dort. Zwischen den Standorten gleicht ZPO die Sperren ab.
Auf dem ersten Proxy eines Standorts:
curl -fsSL https://repo.techeve.de/zpo/setup-haproxy.sh | sudo bash -s -- --crowdsecWas dabei entsteht:
- Security Engine mit der LAPI des Standorts (
crowdsec), die die HAProxy-Logs liest — aus/var/log/haproxy.log, wenn CrowdSec die Datei bei der Installation selbst erkennt (Ubuntu mit rsyslog), sonst aus dem Journal; dazu die Collectionscrowdsecurity/haproxy(Scanner, Exploits, empfindliche Dateien, Brute-Force) undcrowdsecurity/wordpress. Die LAPI lauscht auf der Verwaltungsadresse, Port 8080, die Metriken der Security Engine auf Port 6060. Die LAPI spricht TLS mit dem Zertifikat des Knotens wie die Data Plane API; die Metriken bleiben HTTP (siehe Verwaltung über TLS und Fingerabdruck). Istufwaktiv, gibt das Skript die Ports frei; wer kann, beschränkt sie auf die Adresse von ZPO (ufw allow from <zpo> to any port 8080,6060 proto tcp). - Hostname und Sperre im Log: HAProxy schreibt den Namen des Dienstes in
jede Logzeile, bei einer Sperre dazu
ban({name|ban}hinter den Zählern). Zwei eigene Parser werten das aus:zpo/haproxy-hostmacht den Namen zutarget_fqdn— Alarme incscli alerts inspectnennen dann den angegriffenen Dienst —, undzpo/blockednimmt Anfragen aus, die schon die Sperrseite bekamen. Sonst zählte CrowdSec deren 403 als neue Sonde, und jede Sperre verlängerte sich selbst. - HAProxy-Bouncer (
crowdsec-haproxy-spoa-bouncer), lokal auf Port 9000. HAProxy fragt ihn für jede Verbindung; eine gesperrte Adresse bekommt die Sperrseite (403), bevor Umleitung, Zugangsliste oder Rate-Limit greifen. Die Seite kommt aus dem Standardprofil der Fehlerseiten (Fall „Gesperrt (CrowdSec)”), bis ZPO sie ablegt steht dort ein schlichter Text. Die HTTP-01-Antworten für Let’s Encrypt gehen immer durch, ebenso Dienste, bei denen „Von CrowdSec prüfen lassen” aus ist. - Fail open: Steht der Bouncer, nimmt der Health-Check von HAProxy ihn aus dem Spiel, und die Dienste antworten ohne Prüfung weiter — ohne Wartezeit je Anfrage. Dass der Schutz dann fehlt, meldet ZPO.
- Zwei Zugänge für ZPO: der Lesezugang
zpo-monitor(Überwachung) und der Maschinen-Zugangzpo-sync(Abgleich der Sperren, Alarme für die Statistik). Adresse, Schlüssel und Maschinen-Passwort stehen am Ende der Ausgabe und in/root/haproxy-credentials. - Der Dienst in jedem Alarm: eine Kontext-Datei
(
/etc/crowdsec/contexts/zpo.yaml) gibt jedem Alarm den angegriffenen Dienst mit — die Statistik zeigt damit, wen es trifft.
Einmal eingerichtet, bleibt CrowdSec bei jedem weiteren Lauf des Skripts erhalten, auch ohne den Schalter.
Die Pakete kommen aus der Paketquelle von CrowdSec (packagecloud, nur
HTTPS). Steht apt hinter einem Cache-Proxy wie apt-cacher-ng, der HTTPS nicht
tunnelt, legt das Skript /etc/apt/apt.conf.d/99zpo-https-direct an:
HTTPS-Quellen gehen dann direkt, HTTP-Quellen weiter über den Cache.
In ZPO eintragen: unter Einrichtung → CrowdSec beim Standort „LAPI eintragen”: LAPI-Adresse (https), Fingerabdruck, Schlüssel und Maschinen-Passwort. Die Seite zeigt je Standort die LAPI und alle seine Proxys mit ihrem CrowdSec-Zustand, dazu den Abgleich zwischen den Standorten.
Weitere Proxys an einem Standort
Section titled “Weitere Proxys an einem Standort”Ein zweiter (dritter …) Proxy am selben Standort bekommt keine eigene LAPI, sondern meldet an die des Standorts. Zwei Schritte:
-
Auf dem Proxy mit der LAPI den Zugang anlegen — das ändert sonst nichts und gibt den Befehl für den neuen Proxy aus. Der Name ist der des Proxys in ZPO; daran erkennt ZPO, ob sein Bouncer abholt:
Terminal window sudo setup-haproxy.sh --crowdsec-proxy <name-des-neuen-proxys> -
Den ausgegebenen Befehl auf dem neuen Proxy ausführen (
--crowdsec-join <lapi> --crowdsec-fingerprint … --crowdsec-token … --crowdsec-bouncer-key …). Der Fingerabdruck lässt das Skript das Zertifikat der LAPI prüfen, bevor es ihr vertraut; stimmt er nicht, bricht der Lauf ab und ändert nichts. Er bekommt Agent, WAF und Bouncer: Der Agent liest die eigenen Logs und meldet an die LAPI des Standorts (Auto-Registrierung mit dem Token, nur aus privaten Netzen), der Bouncer holt seine Sperren von dort. In ZPO ist dafür nichts einzutragen — der Proxy erscheint unter Einrichtung → CrowdSec bei seinem Standort. Spätere Läufe des Skripts lassen ihn angebunden.
Der ausgegebene Befehl enthält Token und Bouncer-Schlüssel; er gehört nur auf
den neuen Proxy. Das Token steht auf dem Proxy mit der LAPI in
/etc/crowdsec/config.yaml.local (nur für root lesbar) und in
/root/haproxy-credentials. Mit ihm können sich Agents aus privaten Netzen
anmelden.
Update von 0.6: Hatten an einem Standort mehrere Proxys je eine eigene
LAPI in ZPO, übernimmt 0.7 die des ersten; die übrigen Proxys nennt eine
Warnung im Log. Sie werden mit --crowdsec-proxy/--crowdsec-join an die
übernommene LAPI angebunden.
Überwachung
Section titled “Überwachung”ZPO prüft im Takt des Abgleichs die LAPIs und die Proxys, dazu alle fünf Minuten an den Metriken von CrowdSec, ob der Schutz tatsächlich arbeitet.
Zustände der LAPI (beim Standort):
| Zustand | Bedeutung |
|---|---|
| aktiv | LAPI antwortet, der Zugang gilt, alle Bouncer holen ab — dazu die Zahl der Sperren aus eigener Erkennung und von Hand |
| LAPI antwortet nicht | neue Sperren kommen nicht mehr an, der Abgleich ruht; die Bouncer setzen die bekannten weiter durch |
| Zugang abgewiesen | der Schlüssel stimmt nicht (mehr) |
| Bouncer holt nichts ab | ein Bouncer, der schon abgeholt hat, holt seit fünf Minuten nichts mehr — der Hinweis nennt ihn; neue Sperren wirken an seinem Proxy nicht |
Zustände eines Proxys (beim Proxy-Knoten und unter Einrichtung → CrowdSec):
| Zustand | Bedeutung |
|---|---|
| aktiv | Bouncer läuft, der Agent liest und versteht die Logs |
| Bouncer steht | HAProxy erreicht den Bouncer nicht — Anfragen gehen ungeprüft durch |
| nicht verdrahtet | die Grundkonfiguration kennt den Bouncer nicht; das Skript mit --crowdsec bzw. --crowdsec-join ausführen |
| liest keine Logs | HAProxy hat Anfragen beantwortet, der Agent aber keine Logzeile gelesen — er erkennt nichts. Meist läuft rsyslog nicht oder die Log-Quelle fehlt (cscli metrics show acquisition) |
| versteht die Logs nicht | der Agent hat nicht einmal halb so viele Zeilen verstanden, wie HAProxy Anfragen weitergereicht hat — das Log-Format stimmt nicht; das Skript erneut ausführen |
Sind die Metriken nicht erreichbar (ältere Einrichtung, Firewall), bleibt
der Zustand „aktiv” mit dem Zusatz „Logs nicht überwacht”: Der Schutz wirkt,
ZPO sieht nur nicht, ob CrowdSec etwas erkennt. Abhilfe: das Skript mit
--crowdsec erneut ausführen. Weil der Vergleich ein Zeitfenster braucht,
fällt eine solche Störung nach fünf bis zehn Minuten auf.
Jeder Wechsel zwischen „aktiv” und einer Störung erzeugt ein Ereignis
(crowdsec_down, crowdsec_recovered) und damit einen Alarm.
Gesamtzustand der LAPI. Die LAPI ist das Herzstück eines Standorts: Fällt sie aus, wirken keine neuen Sperren. Unter Einrichtung → CrowdSec steht beim Standort deshalb ein Gesamtzustand — gesund, eingeschränkt oder gestört — mit den Prüfungen dahinter:
| Prüfung | eingeschränkt bzw. gestört, wenn … |
|---|---|
| LAPI erreichbar, Lesezugang gültig | die LAPI nicht antwortet oder den Schlüssel abweist (gestört) |
| Metriken der LAPI lesbar | ZPO Port 6060 der LAPI seit fünf Minuten nicht erreicht |
| Jeder Proxy holt Sperren ab | für einen aktiven Proxy des Standorts kein Bouncer abholt |
| Agents aller Proxys melden sich | weniger Agents anfragen, als der Standort aktive Proxys hat |
| CrowdSec auf allen Proxys in Ordnung | ein Proxy eine der Störungen oben meldet |
| Firewall-Bouncer holen Sperren ab | (nur wenn einer an der LAPI hängt) ein Firewall-Bouncer — etwa cs-routeros-bouncer, der Sperren in den MikroTik trägt — seit zehn Minuten nichts mehr abholt; nach einem Tag Stille gilt er als entfernt und fällt aus der Prüfung |
| Abgleich mit den anderen Standorten | (bei mehreren Standorten) der Maschinen-Zugang fehlt, der letzte Abruf älter als fünf Minuten ist oder Sperren anderer Standorte fehlen |
Über Bouncer und Agents urteilt ZPO erst, wenn es der LAPI ein paar Minuten zugesehen hat.
Benachrichtigung: Besteht eines dieser Probleme fünf Minuten lang, legt
ZPO ein Ereignis an (crowdsec_down, „CrowdSec am Standort … eingeschränkt”
mit dem Grund) und schickt es über die Alarmkanäle; ist es behoben, folgt eine
Entwarnung (crowdsec_recovered), sobald wieder alle Prüfungen laufen und in Ordnung sind. Ausfälle der LAPI selbst und der Proxys
melden die Zustände oben ohnehin.
Firewall-Bouncer erkennt ZPO am Namen (routeros-bouncer…,
cs-routeros-bouncer…, cs-firewall-bouncer…) und führt sie als zweite
Sperrebene; meldet sich einer von einer neuen Adresse, zählen seine
Einträge (name@adresse) zusammen.
Angebundene Systeme. Darunter stehen alle Bouncer und Maschinen, die bei
der LAPI anfragen — mit Zuordnung (Bouncer bzw. Agent eines Proxys, ZPO
selbst) und letzter Anfrage. Systeme, die ZPO nicht angelegt hat, etwa ein
Firewall-Bouncer auf einem App-Server, stehen als fremd da. Die LAPI verrät
über ihre Schnittstelle nicht, wer angemeldet ist; ZPO liest die Systeme
einmal je Minute aus ihren Metriken (Anfragen je Bouncer und Maschine). Es
kennt deshalb nur Systeme, die seit dem letzten Start der LAPI angefragt
haben, und ohne Adresse oder Version. Wer sieben Tage still ist, fällt aus der
Liste. Stellt man die Adresse der LAPI nur um (etwa von http:// auf
https://), bleibt die Liste erhalten — so fällt auch ein Firewall-Bouncer
auf, der danach nicht mehr abholt. Ein anderer Rechner oder Port gilt als
andere LAPI, dann beginnt die Liste von vorn. Zugänge entfernen: auf dem LAPI-Proxy cscli bouncers delete <name>
bzw. cscli machines delete <name>.
Echte Client-Adresse: CrowdSec sperrt die Adresse, die HAProxy als Quelle
sieht. Steht vor dem Proxy ein weiterer Proxy oder ein NAT, der die Adresse
ersetzt, sähe CrowdSec nur dessen Adresse. Private Adressen nimmt CrowdSec
von sich aus aus (crowdsecurity/whitelists) — ein vorgelagertes Gerät im
eigenen Netz wird also nicht gesperrt, die Angreifer dahinter allerdings auch
nicht.
Sperren zwischen den Standorten abgleichen
Section titled “Sperren zwischen den Standorten abgleichen”Jeder Standort führt seine eigene LAPI und arbeitet auch ohne Verbindung zu
den anderen weiter. ZPO gleicht die Sperren ab: Was eine LAPI selbst erkennt
oder von Hand gesperrt wird, trägt ZPO mit der Restlaufzeit in die LAPIs der
übrigen Standorte ein — als Herkunft zpo-sync, als Grund
zpo-sync/<standort> (cscli decisions list zeigt beides). Unter
Einrichtung → CrowdSec steht, wie viele Sperren von welchem Standort auf
welchen übertragen sind und welche zuletzt.
- Voraussetzung: bei der LAPI in ZPO zusätzlich das Maschinen-Passwort. Ohne es liefert der Standort seine Sperren nur an die anderen und bekommt keine.
- Aufheben am Ursprung hebt die Sperre überall auf. Aufheben an einem anderen Standort gilt nur dort — etwa um einen Kunden an einem Standort zuzulassen.
- Adressen aus dem eigenen Netz (privat, Loopback, Link-Local) bleiben an ihrem Standort: Ein Fehlalarm gegen ein internes Gerät soll nicht überall zugleich aussperren.
- Die Community-Blockliste von CrowdSec gleicht ZPO nicht ab — die holt jede LAPI selbst.
- Antwortet eine LAPI nicht, holt der Abgleich nach, sobald sie wieder da ist.
Die aktiven Entscheidungen aller Standorte zeigt die Ansicht CrowdSec-Sperren in einer Tabelle. Eine Adresse, die an mehreren Standorten gilt, steht dort einmal:
- Art: Ban ist eine echte Sperre — die Adresse kommt nicht durch, am Router auch nicht. Captcha ist Stufe 1: Die Adresse bekommt nur die Prüfseite und darf nach gelöster Aufgabe durch.
- Herkunft: der Standort, von dem die Sperre stammt (bei von Hand gesetzten mit dem Zusatz „von Hand”). Darunter kleine Marken: Alle Standorte, wenn sie an jedem erreichbaren Standort gilt, sonst die Standorte, an denen sie gilt.
- Läuft ab: ein Countdown bis zur Freigabe, darunter der genaue Zeitpunkt. Gilt die Adresse an mehreren Standorten, zählt das späteste Ende — erst dann ist sie überall wieder frei.
Über der Tabelle steht je Standort der Stand des Abgleichs: Zahl der eigenen und der übernommenen Sperren, der letzte Abruf durch ZPO und — falls die LAPI eine Weile nicht antwortete — der Rückstand: Sperren anderer Standorte, die dort noch fehlen und die ZPO nachträgt, sobald sie wieder antwortet.
Suchen und filtern: Das Suchfeld sucht in Adresse, Grund und Standort; dazu lässt sich nach Art (Ban, Captcha) und Standort filtern. Ein Klick auf eine Adresse zeigt nur sie — und darüber ihren Verlauf: die Vorfälle, die ZPO für die Sperrstufen zählt, mit Zeit, Standort und Grund, und dem Vorfall, der zur Sperre führte. Der Verlauf reicht so weit zurück wie das Zeitfenster der Sperrstufen; nach einem manuellen Aufheben beginnt er von vorn.
Aufheben geht mit den beiden Symbolen am Ende der Zeile: überall (offenes
Schloss mit Wellen) oder nur an einem Standort (offenes Schloss — bei
mehreren Standorten erscheint eine Auswahl). An den übrigen Standorten gilt die
Sperre dann weiter. Das geht über den Maschinen-Zugang zpo-sync und braucht
Schreibrecht auf die Knoten; es steht im Audit-Log. Von Hand am Knoten:
cscli decisions delete --ip <adresse>.
Eine einzelne Adresse oder ein Netz lässt sich auch ohne die Liste entsperren: unter Einrichtung → CrowdSec, Adresse entsperren — an einem Standort oder an allen.
Vertrauenswürdige Netze (Allowlist): Unter Einrichtung → CrowdSec,
Vertrauenswürdige Netze, oder an einer IP-Gruppe mit dem Haken
„vertrauenswürdig — nie von CrowdSec sperren”: etwa die öffentlichen Adressen
der eigenen Standorte, ein Monitoring-Dienst — oder ein Router, der Verkehr
aus anderen internen Netzen auf seine eigene Adresse umschreibt. Dann
erscheint der ganze interne Verkehr von dort unter einer einzigen Adresse, und
eine Sperre gegen sie schnitte alle diese Netze auf einmal ab. HAProxy fragt
für vertrauenswürdige Netze den Bouncer gar nicht (zpo_trusted.map), und
ZPO hebt Sperren gegen sie im Abgleich auf, statt sie zu verteilen. Die LAPI
bietet Allowlists nur zum Lesen an — deshalb dieser Weg statt
cscli allowlists. Was dort trotzdem von Hand hinterlegt ist, zeigt ZPO beim
Standort unter Allowlists in CrowdSec; diese gelten für alle Bouncer an
der LAPI, auch fremde.
HAProxy und CrowdSec in Docker
Section titled “HAProxy und CrowdSec in Docker”Das Skript richtet Ubuntu-Server ein; läuft HAProxy als Container, geht es mit den offiziellen Images ebenso — ohne eigene Variante:
-
CrowdSec (
crowdsecurity/crowdsec) als LAPI des Standorts, mit den Collectionscrowdsecurity/haproxyund den AppSec-Collections (VariableCOLLECTIONS) und denselben Dateien, die das Skript schreibt: die ZPO-Parser, die Kontext-Datei,zpo-waf.yamlund eine AppSec-Quelle auf0.0.0.0:7422(nur im Docker-Netz). Port 8080 und die Metriken (6060) auf einer internen Adresse veröffentlichen, die ZPO erreicht; 6060 auch unter der Verwaltungsadresse des Proxys. -
Logs: HAProxy loggt nach stdout. CrowdSec liest sie über die Docker-API (
source: docker,container_name: [<haproxy>],labels: type: haproxy) — am besten über einen Socket-Proxy, der nur lesende Zugriffe auf Container, Info und Ereignisse erlaubt, statt den Docker-Socket in den CrowdSec-Container zu hängen. Gemeinsame Log-Verzeichnisse sind nicht nötig. -
Bouncer (
crowdsecurity/spoa-bouncer) als eigener Container im Netz von HAProxy,CHALLENGE_HTTP_LISTEN=0.0.0.0:9100, AppSec über einecrowdsec-spoa-bouncer.yaml.localmitappsec_urlund dem Host*für die Sperrstufen (siehe unten). -
HAProxy: die CrowdSec-Blöcke des Skripts (Frontend, SPOE-Datei
zpo-crowdsec.cfg) und die Backendscrowdsec-spoa/crowdsec-challengemit dem Container-Namen statt 127.0.0.1 — mitresolversauf Dockers DNS (127.0.0.11) undinit-addr last,libc,none, damit HAProxy auch startet, wenn der Bouncer gerade fehlt. -
TLS: Ein Zertifikat für alle Verwaltungs-Schnittstellen des Proxys erzeugen (wie
management_tlsim Skript, mit allen Adressen und Container-Namen, über die es angesprochen wird, im SAN) und in die Container einhängen. Die Data Plane API und zpo-captcha alsprogramim HAProxy-Container bekommen die Optionen wie auf einem Knoten (--scheme https --tls-host … --tls-port … --tls-certificate … --tls-key …bzw.-tls-cert … -tls-key …). Das CrowdSec-Image löscht den TLS-Block der LAPI beim Start, solangeUSE_TLSnicht gesetzt ist; deshalb über seine Variablen:USE_TLS=true,LAPI_CERT_FILE,LAPI_KEY_FILE,CACERT_FILEundLOCAL_API_URL=https://127.0.0.1:8080. Der SPOA-Bouncer bekommtCROWDSEC_URL=https://…undca_cert_pathin seiner.yaml.local. -
zpo-captcha im Container von Hand aktualisieren: Die Binary stammt aus dem Paket
zpo-captchaund muss zur Version von ZPO passen. Eine ältere Binary kennt neuere Optionen nicht, bricht ab, und HAProxy beendet mitexit-on-failurealle Programme mit — der Proxy fällt aus. Deshalb vor dem Neustartzpo-captcha -versionundzpo-captcha -hprüfen.
Den Agent im CrowdSec-Image (Maschine localhost) erkennt ZPO als Agent
des Proxys.
WAF, Prüfseite und Köderpfade
Section titled “WAF, Prüfseite und Köderpfade”Mit --crowdsec richtet das Skript auch CrowdSec AppSec ein, lokal auf
127.0.0.1:7422: die Collections für virtuelle Patches, allgemeine Regeln, das
OWASP Core Rule Set und die Bot-Erkennung mit Prüfseite. Was davon für einen
Dienst gilt, stellt man am Dienst ein (WAF-Stufe, Browser-Prüfung,
Köderpfade, siehe Dienste); HAProxy reicht die Einstellung
über die Schalter in zpo_ratelimits.map an AppSec weiter
(AppSec-Config zpo/waf). Dienste ohne WAF fragt der Bouncer nur nach der
Adresse — AppSec sieht sie nie.
- Anfragen mit Körper bis 12 kB gehen samt Körper an AppSec, größere ohne. WebSockets (etwa die Proxmox-Konsole) sind davon nicht betroffen.
- Die Prüfseite liefert der Bouncer selbst aus (127.0.0.1:9100, Pfad
/crowdsec-internal/challenge/). Ferngesteuerte Browser (Selenium, Playwright, Browser-Erweiterungen mit Debugger) fallen durch — das ist der Sinn. - Wie Erkennungen zu Sperren werden, steht unter Zwei Sperrstufen. Eine Entscheidung „challenge” der LAPI macht der Bouncer ohnehin zur Sperre — prüfen kann nur AppSec.
- Köderpfade führen über das Szenario
zpo/trapsofort zur Sperre.
Ohne CrowdSec am Knoten wirken WAF, Prüfseite und Köderpfade dort nicht — die frühere WAF aus eigenen Regex-Regeln gibt es seit 0.6.0 nicht mehr.
Zwei Sperrstufen
Section titled “Zwei Sperrstufen”Seit 0.9.0 sperrt ZPO in zwei Stufen. Wer zum ersten Mal auffällt, ist oft kein Angreifer — ein Mensch hinter einem NAT, ein zu eifriger Client. Erst wer weitermacht, wird an der Firewall gesperrt.
| Wirkung | Wo | |
|---|---|---|
| Stufe 1 (erste Erkennung) | Prüfseite: eine kurze Rechenaufgabe im Browser (ALTCHA), danach durch | nur HAProxy |
| Stufe 2 (Wiederholung, Prüfseite ignoriert, Ausnutzungsversuch) | Sperre | HAProxy und Router |
So greift es ineinander:
- LAPI (
profiles.yaml, das Original liegt alsprofiles.yaml.vor-zpodaneben): Jede Erkennung gegen eine Adresse wird eine Entscheidung vom Typcaptchafür 4 h. Sofortbangibt es nur für Köder (zpo/trap) und wer die Prüfseite ignoriert (zpo/captcha-ignored: mehr als 60 Seitenaufrufe auf die Prüfseite in kurzer Folge). Als Sicherheitsnetz, falls ZPO nicht läuft, sperrt die LAPI ab dem fünften Alarm in sieben Tagen selbst — deshalb lässt ZPO höchstens „Stufe 2 ab dem 4. Vorfall” zu. Wer von Hand entsperrt wird, fängt wieder bei Stufe 1 an. - ZPO zählt die Vorfälle über alle Standorte und trägt ab dem
eingestellten (Vorgabe: zweiten) an der LAPI ein
banein; der Abgleich verteilt es. Szenarien aus der Liste „direkt Stufe 2” (Vorgabe: virtuelle Patches der WAF, also Versuche, bekannte Lücken auszunutzen) überspringen Stufe 1. Die Sperre dauert erst 24 h und verdoppelt sich bei jedem weiteren Vorfall bis zu einer Woche. Einstellbar unter Einrichtung → CrowdSec → Sperrstufen; dort lässt sich Stufe 1 auch ganz abschalten. - Router-Bouncer (cs-routeros-bouncer, cs-firewall-bouncer) nehmen nur
ban(supported_decisions_types: [ban]) — Stufe 1 erreicht den Router nie. Die Community-Liste istbanund landet weiter am Router. - HAProxy prüft das Cookie von zpo-captcha selbst (
hmac, Schlüssel inzpo_captcha.map) und schickt Browser ohne gültiges Cookie zur Prüfseite. Dienste mit „Captcha bei Verdacht erlauben” aus (Schaltern) lassen Stufe 1 durch. Die Prüfseite steht im Log alscaptcha. - Bouncer: Für Dienste mit WAF machte der Bouncer aus
captchaeinban, weil er keinen passenden Host kennt. Das Skript legt deshalbcrowdsec-spoa-bouncer.yaml.localmit einem Host*an (fallback_remediation: captcha, AppSec mitalways_send) — die Meldung „empty captcha provider” beim Start des Bouncers ist erwartet.
zpo-captcha ist ein kleines Programm von ZPO auf jedem Proxy (Paket
zpo-captcha aus der Paketquelle von ZPO, --repo, Vorgabe
https://repo.techeve.de). Die Prüfseite lauscht lokal auf 127.0.0.1:9200,
die Steuerung für ZPO auf der Verwaltungsadresse, Port 5556, mit den
Zugangsdaten der Data Plane API (/etc/zpo-captcha/env). ZPO überträgt
Schlüssel, Gültigkeit des Cookies, Schwierigkeit und die Prüfseiten der
Fehlerprofile (Fall „Prüfseite (Stufe 1)”, Platzhalter {{captcha}}) und
meldet einen Proxy, dessen zpo-captcha nicht antwortet, als „Prüfseite
fehlt” — samt Alarm. Ist ufw aktiv, gibt das Skript 5556 frei. Die
Steuerung spricht TLS mit demselben Zertifikat wie die Data Plane API, und
ZPO prüft es am Fingerabdruck des Knotens. Der Schlüssel, mit dem sich Cookies
fälschen ließen, geht damit nicht mehr im Klartext über das Verwaltungsnetz.
Läuft der Proxy in Docker: das Image registry.techeve.de/techeve/zpo/captcha
(Prüfseite auf 9200 im Container-Netz, Steuerung auf 5556,
ZPO_CAPTCHA_API_USER/ZPO_CAPTCHA_API_PASSWORD, Stand unter /data),
im Backend zpo-captcha der Container-Name statt 127.0.0.1, die
profiles.yaml, das Szenario und die .yaml.local des Bouncers wie oben.
Die Steuerung startet das Image ohne TLS. Mit Zertifikat das Zertifikat und den
Schlüssel unter /data/tls einhängen und hinten anhängen:
-tls-cert /data/tls/cert.pem -tls-key /data/tls/key.pem.
Firewall-Bouncer: Sperren auf dem MikroTik
Section titled “Firewall-Bouncer: Sperren auf dem MikroTik”Stufe 2 sperrt zusätzlich am Router: Ein Firewall-Bouncer holt die Sperren
(ban) von der LAPI des Standorts und trägt sie als Address-Lists samt
Filterregeln in den MikroTik ein. So prallt ein gesperrter Angreifer schon
an der Firewall ab, nicht erst an HAProxy. Das Setup-Skript richtet ihn nicht
ein; ZPO erkennt und überwacht ihn aber (siehe Überwachung). Bewährt hat
sich cs-routeros-bouncer (Projekt jmrplens/cs-routeros-bouncer), als
Binary mit systemd-Unit auf dem Proxy mit der LAPI oder als Container.
1. Am Router einen eigenen Zugang anlegen — im RouterOS-Terminal als admin, mit der Adresse des Rechners, auf dem der Bouncer läuft:
/user group add name=crowdsec-bouncer policy=read,write,api,sensitive,!ftp,!local,!ssh,!reboot,!policy,!test,!password,!sniff,!romon,!rest-api,!winbox,!web comment="cs-routeros-bouncer"/user add name=crowdsec-bouncer group=crowdsec-bouncer password=<passwort> address=<bouncer-ip>/32 comment="cs-routeros-bouncer"/ip service set api disabled=no address=<bouncer-ip>/32Die Gruppe hat genau die Rechte, die der Bouncer braucht (Address-Lists und Regeln schreiben, über die API). Der Benutzer darf sich nur von der Adresse des Bouncers anmelden, und der API-Dienst (Port 8728) ist nur für diese Adresse offen. Verlangt eine bestehende Firewall-Regel mehr, den API-Port für diese Adresse dort ebenfalls freigeben.
2. An der LAPI einen Bouncer-Zugang anlegen — auf dem Proxy mit der LAPI:
sudo cscli bouncers add routeros-bouncerDen ausgegebenen Schlüssel aufheben; er erscheint nur einmal. Der Name sollte
mit routeros-bouncer oder cs-routeros-bouncer beginnen, daran erkennt ZPO
einen Firewall-Bouncer.
3. Den Bouncer installieren — die Binary des Release nach
/usr/local/bin/cs-routeros-bouncer, die Konfiguration nach
/etc/cs-routeros-bouncer/cs-routeros-bouncer.yaml (Besitzer root, Modus
600, sie enthält beide Zugänge). Die wichtigen Teile:
crowdsec: api_url: "https://<verwaltungsadresse>:8080/" api_key: "<schlüssel aus schritt 2>" update_frequency: "10s" scopes: ["ip", "range"] supported_decisions_types: ["ban"] # nur Stufe 2, nie die Prüfseite retry_initial_connect: truemikrotik: address: "<router-ip>:8728" username: "crowdsec-bouncer" password: "<passwort aus schritt 1>"firewall: ipv4: { enabled: true, address_list: "crowdsec-banned" } ipv6: { enabled: true, address_list: "crowdsec6-banned" } filter: { enabled: true, chains: ["input"] } raw: { enabled: true, chains: ["prerouting"] } deny_action: "drop" rule_placement: "top"supported_decisions_types: ["ban"] ist wichtig: Ohne das sperrte der Router
schon in Stufe 1, und die Prüfseite käme nie an.
Die LAPI spricht TLS mit dem selbstsignierten Zertifikat des Knotens. Der
Bouncer vertraut ihm über SSL_CERT_FILE; die Option ca_cert_path im
Abschnitt crowdsec greift bei diesem Bouncer nicht. Die Unit:
[Unit]Description=CrowdSec RouterOS BouncerAfter=network-online.target crowdsec.serviceWants=network-online.target
[Service]Type=simpleExecStart=/usr/local/bin/cs-routeros-bouncer -c /etc/cs-routeros-bouncer/cs-routeros-bouncer.yamlEnvironment=SSL_CERT_FILE=/etc/zpo-tls/cert.pemRestart=alwaysRestartSec=10
[Install]WantedBy=multi-user.targetLäuft der Bouncer nicht auf dem Proxy mit der LAPI, das Zertifikat der LAPI
(/etc/zpo-tls/cert.pem dort) auf seinen Rechner kopieren und den Pfad in
SSL_CERT_FILE anpassen.
Als Container: CROWDSEC_URL=https://<lapi>:8080/, SSL_CERT_FILE auf das
eingehängte Zertifikat, die Zugänge über eine env_file mit Modus 600.
4. Prüfen:
sudo systemctl enable --now cs-routeros-bouncersudo journalctl -u cs-routeros-bouncer -n 20Nach dem Start gleicht der Bouncer die Listen ab und meldet
reconciliation complete; danach trägt er neue Sperren in wenigen Sekunden
ein. In ZPO erscheint er unter Einrichtung → CrowdSec bei der LAPI als
Firewall-Bouncer.
Resolver: MikroTik RouterOS
Section titled “Resolver: MikroTik RouterOS”ZPO schreibt statische Einträge über die REST-API (/rest/ip/dns/static).
Hat ein Dienst am Standort nur eine IPv4-Adresse, legt ZPO auf RouterOS
zusätzlich einen AAAA-Eintrag mit der Sperradresse ::ffff an. Ohne ihn
beantwortet der Router AAAA nicht leer, sondern fragt den Upstream und
reicht eine öffentliche IPv6 des Namens durch — Clients bevorzugen sie und
gingen am lokalen Proxy vorbei ins Internet. Mit der Sperradresse fallen
sie sofort auf IPv4 zurück; so empfiehlt es MikroTik selbst. Trägt der Proxy
eine interne IPv6, steht stattdessen sie im AAAA-Eintrag. AdGuard Home
braucht das nicht: Es beantwortet fehlende Typen eines umgeschriebenen
Namens ohnehin leer.
- URL:
https://<router>/rest - Zugang: ein Benutzer, dessen Gruppe die Richtlinien
read,writeundrest-apiumfasst — alle übrigen dürfen gesperrt sein.apigilt nur für die binäre API auf Port 8728/8729 und ist für ZPO nicht nötig. Ein eigenes Benutzerkonto nur für ZPO ist angeraten. - Dienst: Die REST-API läuft über den Webdienst des Routers —
www-ssl(bzw.wwwfürhttp://) muss unter IP → Services aktiv und von ZPO aus erreichbar sein. - Verbindungstest: Der Knopf Testen liest
/rest/system/resourceund/rest/ip/dns/static. Schlägt er fehl, steht der Grund am Knoten und im Protokoll von ZPO (connection test failed). - TLS: RouterOS spricht REST über HTTPS; bei selbstsignierten Geräte- Zertifikaten bleibt die Zertifikatsprüfung im Knoten abschaltbar.
Wem ein DNS-Eintrag gehört
Section titled “Wem ein DNS-Eintrag gehört”ZPO überschreibt keinen Eintrag, den jemand anderes angelegt hat.
- RouterOS: Jeder Eintrag, den ZPO schreibt, bekommt den Kommentar
#ZPO. Ein Eintrag, dessen Kommentar mit#ZPObeginnt, gehört ZPO — auch wenn ZPO ihn nicht kennt (etwa nach einer verlorenen Datenbank oder wenn du ihn von Hand so markierst). Solche Einträge übernimmt ZPO ohne Rückfrage. Der alte Vermerkzpo:<nummer>zählt ebenfalls und wird beim nächsten Schreiben ersetzt. - AdGuard Home hat kein Kommentarfeld. Hat ZPO einen Namen einmal geschrieben, gehört er ZPO. Beim ersten Mal ist fremd, was dort schon steht und nicht genau den gewünschten Adressen entspricht.
Steht unter einem Namen ein fremder Eintrag, schreibt ZPO nicht, sondern meldet einen Konflikt: Unter Abgleich erscheint der Eintrag mit dem Zustand „Konflikt“, den fremden Adressen und dem Knopf Übernehmen. Erst nach dieser Freigabe ersetzt ZPO die fremden Einträge; von da an gehören sie ZPO. Ohne Freigabe bleibt alles, wie es ist — auch kein stilles Nebeneinander, bei dem der Resolver beide Adressen reihum ausliefern würde.
Resolver: AdGuard Home
Section titled “Resolver: AdGuard Home”ZPO pflegt DNS-Rewrites über die Web-API (/control/rewrite/...).
- URL: die Basis-Adresse der Weboberfläche, z. B.
http://<host>:3000 - Zugang: Benutzername und Passwort eines Admin-Kontos der Oberfläche.
AdGuard Home kennt keine TTL je Rewrite; Antworten tragen die serverweite
blocked_response_ttl (Vorgabe: 10 Sekunden). ZPO liest den Wert beim
Verbindungstest aus und zeigt ihn an, ändert ihn aber nicht ungefragt — er gilt
für den ganzen Server, nicht nur für ZPOs Einträge.