
moselwal/crowdsec-bridge — CrowdSec LAPI direkt im TYPO3-Backend.
moselwal/crowdsec-bridge bringt den operativ relevanten Teil der CrowdSec Local API in ein TYPO3-14-Backend-Modul: Decisions anzeigen, IPs sperren und entsperren, Alerts inspizieren, LAPI-Heartbeat prüfen — alles ohne Container-Shell und ohne SSH. Read-only by default, Write nur für die dedizierte Operator-Rolle, jeder Schreibversuch wird forensisch im sys_log erfasst.
Sechs Bausteine
- Decisions-Tab — paginierte Liste aller aktiven Decisions (50/Seite), nach Erstellzeit absteigend. Spalten: ID, Scope, Type, Value, Restzeit, Origin, Scenario. Filter für Scope (
Ip/Range/Country/As), Type (ban/captcha) und Origin. - IP-Quick-Check — unter zwei Sekunden eine binäre Antwort, ob eine IP gesperrt ist, inklusive Decision-ID und Restzeit. Server-seitige IP-Validierung, jede Anfrage geht ins Audit-Log.
- Add Decision — Operatoren legen neue Decisions an: Scope, Type, Value, Dauer-Preset (1h/24h/7d/30d/Custom), Begründung (5–1000 Zeichen). Der Server setzt
origin = "typo3"hart — vom Client nicht überschreibbar. - Delete Decision — über TYPO3-natives Confirmation-Modal. Foreign-Origin-Decisions (von CrowdSec oder
csclierzeugt) sind gegen versehentliches Löschen geschützt; nur Admins mit explizitem Override dürfen sie löschen. - Alerts-Tab — paginierte Read-only-Liste der CrowdSec-Alerts (50/Seite). Filter für Source-Scope und Scenario (case-insensitive Substring). Detail-View mit Quelle (Scope, Value, IP, AS-Nummer, Land, Geo-Koordinaten), Labels und bis zu 50 verknüpften Decisions — direkt verlinkbar.
- LAPI-Heartbeat-Banner — Status, CrowdSec-Version, Bouncer-Name und Zeitstempel des letzten Checks. Niemals gecached.
Voraussetzungen
- TYPO3 14.3+
- PHP 8.5+
- CrowdSec 1.6+
psr/http-clientundpsr/http-factory1.0+- Optional: Redis oder Valkey plus moselwal/keyvalue-store für den optionalen Decision-List-Cache
- Extension-Key
crowdsec_bridge, NamespaceMoselwal\CrowdSecBridge, MIT-Lizenz
Architektur: strikte DDD-Vier-Schichten
Die Schichten sind per deptrac.yaml in der CI erzwungen. Aufwärts-Importe sind verboten, jeder Commit läuft durch das Gate.
Classes/
├── Domain/ # Reine Value Objects, Enums, Contracts. Null externe Dependencies.
├── Application/ # CQRS-Queries und -Commands plus Handler. Darf nur Domain importieren.
├── Infrastructure/ # HTTP-Client, Repositories, Audit-Logger, Cache-Decorator. Darf Application, Domain und Framework-Typen.
└── Presentation/ # Backend-Controller, Fluid-Templates, Event-Listener.
Der CQRS-Schnitt ist flach, aber konsistent: jede Backend-Interaktion läuft über ein *Query-/*Command-Objekt plus dedizierten *Handler. Handler bekommen Abhängigkeiten per Konstruktor-Injection — final readonly durchgängig.
Der HTTP-Client implementiert Psr\Http\Client\ClientInterface und wird per Constructor injiziert. Domain-Code ist Framework-frei und trivial unit-testbar.
Der optionale Cache ist ein Decorator über LapiDecisionRepository, der nur dann in den DI-Container gebunden wird, wenn moselwal/keyvalue-store installiert und cacheEnabled=1 gesetzt ist (siehe Configuration/Services.php). Ohne das Paket greift ein NullDecisionCacheInvalidator-No-op — die Write-Handler brauchen keine Runtime-Verzweigung.
Konfiguration: Bouncer-Key und Extension-Optionen
Bouncer-API-Key im CrowdSec-Container erzeugen:
cscli bouncers add typo3-bridge
Die Bridge sucht den Key in genau dieser Reihenfolge:
- Docker-Secret-Datei
/run/secrets/crowdsec_api_key— empfohlen für Production, SOPS-verschlüsselt und als Docker-Secret gemountet. - Environment-Variable
CROWDSEC_LAPI_KEY. $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['crowdsec_bridge']['lapiKey']— nur Development, niemals Production.
Läuft keine dieser Quellen, zeigt das Modul beim ersten Aufruf einen klaren Konfigurationsfehler.
Extension-Konfiguration
Verwaltet über Admin Tools → Settings → Extension Configuration → crowdsec_bridge.
lapiBaseUrlDefault
crowdsec. Voll qualifizierte URL zur CrowdSec Local API. Muss vom TYPO3-Container erreichbar sein.lapiTimeoutDefault
5. HTTP-Timeout in Sekunden für alle LAPI-Calls.allowDeleteForeignDecisionsDefault
0. Wenn1, dürfen TYPO3-Admins (nicht reguläre Operator) auch von CrowdSec/cscliangelegte Decisions löschen. Regulärecrowdsec_operator-Mitglieder dürfen das nie, egal welche Einstellung.cacheEnabledDefault
1. Aktiviert den Decision-List-Cache. Brauchtmoselwal/keyvalue-store; ohne das Paket bleibt die Einstellung wirkungslos.cacheTtlDefault
10Sekunden. Cache-Lebensdauer. Gültiger Bereich 1–300; Werte außerhalb werden beim Construct geclamped und im TYPO3-Log vermerkt.
Berechtigungen und Sicherheitsmodell
Die Bridge legt keine Backend-Gruppen automatisch an. Drei Rollen werden einmalig unter System → Backend-Benutzer → Gruppen erstellt und mit Modul-Zugriff belegt:
crowdsec_viewer— Read-onlyDecisions-Liste, Alerts-Liste, IP-Quick-Check, LAPI-Status.
crowdsec_operator— OperatorAlle Viewer-Rechte plus neue Decisions anlegen und eigene
typo3-origin Decisions löschen.- TYPO3-Admin (
$BE_USER->isAdmin() === true) Alle Operator-Rechte plus Löschen von Foreign-Origin-Decisions, wenn
allowDeleteForeignDecisions=1.
Alle Access-Checks laufen server-seitig. Die UI blendet Buttons aus, die ein Nutzer nicht aufrufen darf; der Controller erzwingt jeden Check zusätzlich, sodass direkte URL-Manipulation in HTTP 403 endet.
Defence-in-Depth gegen Writes
- Modul-Sichtbarkeit — TYPO3 Module-ACL versteckt das Modul vor Nutzern ohne zugeordnete Gruppe.
- Controller-Entry — jede Action ruft
OperatorPermissionsFactory::build()und weist Requests ab, die das passendeassertCan*nicht passieren. - CSRF — jeder Write-Endpoint validiert das TYPO3-Backend-Form-Token; fehlt es oder ist es ungültig, gibt es HTTP 403 und einen
crowdsec_bridge:csrf_denied-Audit-Eintrag. - Double-Submit — Add- und Delete-Forms haben ein server-seitig gespeichertes Submit-Token, das beim ersten POST konsumiert wird; ein zweiter triggert
crowdsec_bridge:duplicate_submit. - Input-Validierung — alle Werte werden in Domain-Value-Objects (
IpAddress,Duration,DecisionScope,DecisionType,Reason) normalisiert und validiert, bevor irgendein LAPI-Call rausgeht. - Foreign-Origin-Schutz —
OperatorPermissions::assertCanDelete(Decision)erzwingt Origin-Regeln server-seitig. - Rate-Limiting — maximal ein schreibender LAPI-Call pro HTTP-Request, keine Bulk-Endpoints (wer Batch will, nimmt
cscli).
Kein Secret-Material wird je in den Browser gerendert. Der Bouncer-API-Key lebt ausschließlich in PHP-State, aus Docker-Secret, ENV oder TYPO3_CONF_VARS (in dieser Reihenfolge).
Audit-Trail im sys_log
Die Bridge schreibt strukturierte Einträge ins sys_log. Im System → Log-Modul nach diesen type-Werten filtern, um Aktivität zu inspizieren:
crowdsec_bridge:quickcheckJeder IP-Quick-Check (Treffer oder kein Treffer). Channel
security, Severityinfo.crowdsec_bridge:decision_addErfolgreiche oder fehlgeschlagene Add-Versuche. Channel
security, Severityinfobzw.warning.crowdsec_bridge:decision_deleteErfolgreich, not-found oder fehlgeschlagen. Channel
security, Severityinfobzw.warning.crowdsec_bridge:write_deniedWrite-Versuch durch einen Nutzer ohne Operator-Rolle. Channel
security, Severitywarning.crowdsec_bridge:delete_deniedDelete-Versuch gegen eine Foreign-Origin-Zeile ohne Admin-Override. Channel
security, Severitywarning.crowdsec_bridge:csrf_deniedWrite-Versuch mit fehlendem oder ungültigem Form-Token. Channel
security, Severitywarning.crowdsec_bridge:duplicate_submitRe-Submit eines bereits konsumierten Form-Tokens. Channel
security, Severitywarning.
Der volle Audit-Kontext (Action, Status, Decision-Daten, ursprüngliche Origin, Latenz, Fehlermeldung) wird als JSON in die log_data-Spalte serialisiert — ready für Downstream-Analyse.
Optionaler Decision-List-Cache
Mit installierter moselwal/keyvalue-store und cacheEnabled=1 cached die Bridge paginierte Decision-List-Antworten mit kurzer TTL (Default 10 Sekunden). Typischer Effekt: wiederholte F5-Reloads bleiben unter 50 ms.
Invalidierung mit Generation-Counter
Atomares INCR auf einen einzigen KVS-Key. Nach jedem erfolgreichen Add oder Delete wird der Counter inkrementiert — alle vorher gecachten Keys werden in O(1) unerreichbar, ohne Scan. Alte Einträge laufen über TTL ab.
Failure-Handling
Jeder KVS-Fehler (Backend down, Timeout, Malformed Payload) degradiert still auf direkte LAPI-Calls. Der erste Fehler eines Requests landet auf warning im TYPO3-Logger; weitere Fehler innerhalb desselben Requests werden unterdrückt, um Log-Spam zu vermeiden.
Was nicht gecached wird
findById— jeder Decision-Lookup geht direkt zur LAPI, damit der Delete-Origin-Snapshot ehrlich bleibt.findByIp(Quick-Check) — Operatoren erwarten den live LAPI-Stand.- LAPI-Heartbeat — das Status-Banner muss immer aktuell sein.
- Alerts — CrowdSec-intern, in diesem Release nicht gecached.
Installation
composer require moselwal/crowdsec-bridge
vendor/bin/typo3 extension:setup
Optional den Decision-List-Cache zuschalten:
composer require moselwal/keyvalue-store
vendor/bin/typo3 cache:flush
Docker-Compose-Beispiel
Minimaler Snippet, der die Bridge an einen CrowdSec-Container und einen optionalen Redis-Cache koppelt:
services:
app:
image: typo3:14
depends_on:
- crowdsec
- redis
networks:
- internal
secrets:
- crowdsec_api_key
environment:
CROWDSEC_LAPI_KEY_FILE: /run/secrets/crowdsec_api_key
crowdsec:
image: crowdsecurity/crowdsec:latest
networks:
- internal
expose:
- "8080"
redis:
image: redis:7-alpine
networks:
- internal
expose:
- "6379"
networks:
internal:
secrets:
crowdsec_api_key:
file: ./secrets/dev/crowdsec_api_key
Production: Secret-Datei mit SOPS und age verschlüsseln, über Docker-Secrets mounten, weder Redis noch CrowdSec-Ports nach außen exposen.
CrowdSec ins TYPO3-Backend ziehen?
Wenn Sie CrowdSec bereits einsetzen und Decisions plus Alerts ohne Container-Shell direkt im TYPO3-Backend bedienen wollen — mit klarer Rollentrennung, Foreign-Origin-Schutz und vollständigem Audit-Trail im sys_log — sprechen Sie uns an. Wir koordinieren Bouncer-Setup, Backend-Gruppen, optionalen Decision-Cache und Operator-Onboarding.
Oder direkt schreiben: kontakt@moselwal.de
Setzen wir ein bei …
Dieses Paket gehört zur Operator-Schicht jeder TYPO3-Plattform, die hinter Caddy mit CrowdSec steht — Decisions und Alerts direkt im Backend statt im Container-Shell. In der betreuten Variante: AI-Ready CMS as a Service mit CrowdSec-Bouncer, Decision-Cache via keyvalue-store und sauber konfigurierten Backend-Rollen.