8.8 KiB
Plan: CrowdSec for Docker Swarm + Traefik
Integrate CrowdSec (detect + block) into the Docker Swarm Traefik deployment using a forwardAuth bouncer pattern, with log acquisition via socket-proxy.
Architecture
Internet → Traefik (port 443)
│
├── forwardAuth middleware → traefik-bouncer → CrowdSec LAPI
│ (decisions DB)
│
└── router → service (if allowed)
CrowdSec LAPI reads Traefik logs via Docker API → socket-proxy → Docker socket
All HTTPS traffic passes through the bouncer; the bouncer checks each request's IP against LAPI decisions. Non-HTTP entrypoints (smtp, mqtt, etc.) are not affected.
Steps
Phase A — Rewrite swarm/crowdsec.yml for Swarm
-
Convert CrowdSec LAPI service to Swarm format:
- Remove
restart: always,depends_on,container_name(unsupported in Swarm) - Replace with
deploy:block:restart_policy(condition: any), no placement constraint (can run on any manager/worker) - Change network to
overlay(name:crowdsec-internal, attachable: true) - Change
COLLECTIONS: "crowdsecurity/nginx"→"crowdsecurity/traefik" - Remove host port binding (
127.0.0.1:8080:8080) — LAPI only needs to be reachable internally by the bouncer - Change bind mount
./crowdsec/acquis.yaml:...to absolute host path/var/data/crowdsec/acquis.yaml:/etc/crowdsec/acquis.yaml - Add
CUSTOM_CS_LAPI_KEY=${CROWDSEC_LAPI_KEY:?Variable not set}env var for deterministic API key - Keep named volumes
crowdsec-dbandcrowdsec-config(Swarm supports named volumes)
- Remove
-
Add
traefik-bouncerservice in the same stack:- Image:
crowdsecurity/traefik-bouncer:latest - Environment:
CROWDSEC_LAPI_URL=http://crowdsec:8080CROWDSEC_LAPI_KEY=${CROWDSEC_BOUNCER_KEY:?Variable not set}
- Expose port
8080internally (nomode: host, just internal for overlay routing) - Network:
crowdsec-internal+traefik-public(so Traefik can reach it) - Deploy:
restart_policy(condition: any)
- Image:
-
Adapt socket-proxy service for Swarm:
- Remove
container_name,restart: always - Add
deploy:block - Add
networks: [crowdsec-internal] - Set
SWARM: 1in socket-proxy env (wasSWARM: 0) so it handles Swarm Docker API calls - Keep current limited permissions (CONTAINERS: 1, etc.)
- Remove
-
Network definitions:
crowdsec-internal: driveroverlay, attachable: true (for LAPI ↔ bouncer ↔ socket-proxy)traefik-public: external: true (reuse existing)
Phase B — Update swarm/core.yml (Traefik config)
-
Add crowdsec-bouncer middleware definition to Traefik labels in core.yml:
- traefik.http.middlewares.crowdsec-bouncer.forwardauth.address=http://traefik-bouncer:8080/api/v1/forwardAuth - traefik.http.middlewares.crowdsec-bouncer.forwardauth.trustForwardHeader=true -
Apply middleware to HTTPS entrypoint — add to Traefik command args:
- --entrypoints.https.http.middlewares=crowdsec-bouncer@swarmThis applies globally to ALL HTTPS routers (dashboard + all services).
-
Remove or update the old commented-out crowdsec labels:
# - crowdsec.enable=true # - crowdsec.labels.type=nginxReplace with a comment explaining the forwardAuth approach.
Phase C — Config files
-
Create
/var/data/crowdsec/config directory (documented, can be a setup make target):acquis.yamlalready exists atswarm/crowdsec/acquis.yaml— it usessource: dockeranduse_container_labels: truewhich is correct for Traefik log acquisition- Add Traefik container label expectations as comments in acquis.yaml (Traefik needs label
crowdsec.enable=true) - No additional parser config needed —
crowdsecurity/traefikcollection includes the Traefik parser
-
Create
swarm/traefik/crowdsec-middleware.yml(optional file-provider config):- Alternative location to define the forwardAuth middleware if we want to keep core.yml clean
- But defining on Traefik's own labels in core.yml is simpler and follows existing pattern
- Will use Traefik labels (step 5) unless the user prefers file provider
Phase D — Makefile and .env
-
Add
deploy-crowdsectarget tomakefile:deploy-crowdsec: pull { \ echo "Deploying the crowdsec stack..." ;\ set -a ;\ . ./swarm/.env ;\ set +a ;\ docker stack deploy --compose-file ./swarm/crowdsec.yml crowdsec ;\ }Also add
remove-crowdsectarget. -
Document required
.envvariables:CROWDSEC_LAPI_KEY=<generate with: openssl rand -base64 32>— LAPI admin keyCROWDSEC_BOUNCER_KEY=<register via: docker exec crowdsec_crowdsec.1.<id> cscli bouncers add traefik-bouncer>— bouncer API key- Or automate bouncer registration via a setup script
Phase E — Post-deploy setup (manual or scripted)
-
Register the bouncer with CrowdSec LAPI after first deploy:
- Either document manual step:
docker service ps crowdsec_crowdsec --no-truncto get container ID, thendocker exec -it <container> cscli bouncers add traefik-bouncer - Or add a one-shot init approach using
crowdsecurity/bouncer-helper - Or generate the bouncer key upfront and use
cscli bouncers add traefik-bouncer --key <key>in a setup script
- Either document manual step:
-
Add Traefik container label for CrowdSec acquisition (in
swarm/core.yml):- Traefik itself needs
crowdsec.enable=truelabel so CrowdSec picks up its logs - This is a Docker label (not a Traefik label) — add via
deploy.labelson the Traefik service
- Traefik itself needs
-
Verify protection — hit a monitored service with a test attack pattern and confirm 403 from the bouncer
Relevant files
| File | Action | Details |
|---|---|---|
swarm/crowdsec.yml |
Rewrite | Full Swarm stack: crowdsec LAPI + traefik-bouncer + socket-proxy |
swarm/core.yml |
Modify | Add crowdsec-bouncer middleware label, entrypoint middleware, Traefik crowdsec label |
swarm/crowdsec/acquis.yaml |
Minor update | Add comments about Traefik label requirements |
makefile |
Modify | Add deploy-crowdsec and remove-crowdsec targets |
swarm/.env |
Modify | Add CROWDSEC_LAPI_KEY and CROWDSEC_BOUNCER_KEY vars |
Verification
- Stack validation:
docker stack deploy --compose-file ./swarm/crowdsec.yml crowdsec --dry-run(or manuallydocker compose -f swarm/crowdsec.yml configto validate syntax) - Service health:
docker service ls | grep crowdsec→ all services running - LAPI health:
curl http://localhost:8080/healthfrom manager node (if LAPI port exposed temporarily) - Bouncer connectivity: Check
docker service logs crowdsec_traefik-bouncerfor successful LAPI connection - Traefik middleware:
docker service logs core_traefikshould show no errors about missing middleware - End-to-end: Visit a protected HTTPS service → confirm it loads. Check CrowdSec metrics:
docker exec -it <crowdsec-container> cscli metrics - Block test: Simulate an attack or manually add a decision:
docker exec -it <crowdsec-container> cscli decisions add --ip <test-ip>→ confirm that IP gets 403
Decisions
- Bouncer approach: ForwardAuth (separate traefik-bouncer container) over Traefik plugin — avoids custom Traefik builds, works cleanly with Swarm
- Protection scope: All HTTPS services via
--entrypoints.https.http.middlewares=crowdsec-bouncer@swarm— applies globally - Log acquisition: Docker logs via socket-proxy (existing pattern), collection changed from
crowdsecurity/nginx→crowdsecurity/traefik - Socket-proxy: Updated with
SWARM: 1for Swarm Docker API compatibility - LAPI exposure: Internal only (no host port) — bouncer reaches LAPI via overlay network
Out of scope
- CrowdSec AppSec / WAF component (can be added later)
- Non-HTTP entrypoints (smtp, mqtt, etc.) — not protected by the bouncer
- CrowdSec dashboard / Prometheus metrics integration (can be added later)
- Retroactively adding the crowdsec middleware to existing service routers (entrypoint-level handles this globally)
- Bouncer key registration automation script (documented manual step for now; can be automated later)
Further Considerations
- Bouncer key registration: Will use manual
cscli bouncers addpost-deploy. Could automate later with an init container or a swarm cronjob. - Host config directory:
/var/data/crowdsec/needs to exist on the Swarm node before deploy. Will add asetup-crowdsecmake target or document as prerequisite. - Local/development env: There's also
local.ymlandswarm/local_core.yml— should crowdsec be added to those too? Skipping for now unless the user asks.