Explanation
Architecture overview¶
CrowdSec sits in kup6s as the application-layer (L7) defense-in-depth tier.
The Hetzner edge handles L3/L4 volumetric filtering.
CrowdSec adds IP reputation enforcement and, optionally in Phase 3, rule-based WAF inspection.
Component layout¶
The request path through the cluster:
Internet
│ Hetzner DDoS edge filters L3/L4 volumetrics
▼
Hetzner Cloud LB ─── L4 routing + PROXY-Protocol-v2 forwarding
│
▼
Traefik (websecure entrypoint)
│ Default middleware: crowdsec-crowdsec-bouncer@kubernetescrd
│ Plugin extracts source IP from the PROXY header
│ Plugin polls LAPI every 60 s in stream mode
▼
Backend pods (ArgoCD, Mailu, Forgejo, Alpinecity, ...)
The engine runs in parallel:
CrowdSec engine (pod: crowdsec-lapi)
├── LAPI HTTP API on port 8080
├── AppSec component on port 7422 (not yet attached to routes)
└── Postgres backend (CNPG cluster crowdsec-db, 2 instances)
Decisions in LAPI:
├── CAPI (default, ~2-30k IPs: ssh:exploit, http:scan, http:exploit, ...)
└── Premium blocklists (after Console enrollment)
Plugin flow per request¶
The plugin processes each incoming request in this order:
The client sends an HTTPS request which reaches Hetzner LB and then a Traefik pod.
Traefik decodes the PROXY-Protocol header and recovers the real client source IP.
The plugin extracts the source IP.
The plugin checks the source IP against
ClientTrustedIPs(RFC1918 ranges). On match, the request passes immediately.The plugin checks the decision cache. On match with
action: "ban", the plugin returns HTTP 403 and never contacts the backend.Otherwise the request continues to the backend service.
Fail-modes¶
The plugin is configured fail-open at multiple levels. This trades a brief security gap for service availability during component outages.
Component |
Status “down” |
Plugin behavior |
|---|---|---|
LAPI unreachable |
Pod crash, Postgres outage |
Cache stays valid for TTL, then fail-open |
AppSec unreachable |
Not relevant in Phase 2 (no routes attached) |
|
Postgres outage |
CNPG failover window |
LAPI memory cache serves decisions, no new persistence |
See also
How-to: LAPI or Postgres outage describes recovery steps for each scenario.
Where the data flows¶
The plugin maintains a local cache and pulls deltas from LAPI:
The plugin sends
GET /v1/decisions/streamto LAPI every 60 seconds.LAPI responds with a delta since the last poll:
new: [...],deleted: [...].LAPI itself draws decisions from multiple sources. CAPI provides the standard community feed via an outbound connection (enabled by chart default). Premium blocklists arrive after Console enrollment via
app.crowdsec.net. Local scenarios from agent log parsing remain disabled inkup6s(agent.enabled: false).
Why “agent disabled”¶
Phase 1 disabled the CrowdSec agent (agent.enabled: false in engine.ts).
The reason: local log parsing requires choosing and tuning detection scenarios, which warrants its own iteration.
A future iteration could enable the agent to parse Mailu pod logs for SMTP brute-force detection, but Phase 2 does not include this.