From 54d486264b629081b55460c866fd5f708e2b8011 Mon Sep 17 00:00:00 2001 From: furyhawk Date: Sun, 19 Jul 2026 20:16:36 +0800 Subject: [PATCH] plan:crowdSec --- plan/plan-crowdsecSwarm.prompt.md | 160 ++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 plan/plan-crowdsecSwarm.prompt.md diff --git a/plan/plan-crowdsecSwarm.prompt.md b/plan/plan-crowdsecSwarm.prompt.md new file mode 100644 index 0000000..c418ffc --- /dev/null +++ b/plan/plan-crowdsecSwarm.prompt.md @@ -0,0 +1,160 @@ +## 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 + +1. **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-db` and `crowdsec-config` (Swarm supports named volumes) + +2. **Add `traefik-bouncer` service** in the same stack: + - Image: `crowdsecurity/traefik-bouncer:latest` + - Environment: + - `CROWDSEC_LAPI_URL=http://crowdsec:8080` + - `CROWDSEC_LAPI_KEY=${CROWDSEC_BOUNCER_KEY:?Variable not set}` + - Expose port `8080` internally (no `mode: host`, just internal for overlay routing) + - Network: `crowdsec-internal` + `traefik-public` (so Traefik can reach it) + - Deploy: `restart_policy` (condition: any) + +3. **Adapt socket-proxy service** for Swarm: + - Remove `container_name`, `restart: always` + - Add `deploy:` block + - Add `networks: [crowdsec-internal]` + - Set `SWARM: 1` in socket-proxy env (was `SWARM: 0`) so it handles Swarm Docker API calls + - Keep current limited permissions (CONTAINERS: 1, etc.) + +4. **Network definitions:** + - `crowdsec-internal`: driver `overlay`, attachable: true (for LAPI ↔ bouncer ↔ socket-proxy) + - `traefik-public`: external: true (reuse existing) + +#### Phase B — Update `swarm/core.yml` (Traefik config) + +5. **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 + ``` + +6. **Apply middleware to HTTPS entrypoint** — add to Traefik command args: + ``` + - --entrypoints.https.http.middlewares=crowdsec-bouncer@swarm + ``` + This applies globally to ALL HTTPS routers (dashboard + all services). + +7. **Remove or update the old commented-out crowdsec labels:** + ``` + # - crowdsec.enable=true + # - crowdsec.labels.type=nginx + ``` + Replace with a comment explaining the forwardAuth approach. + +#### Phase C — Config files + +8. **Create `/var/data/crowdsec/` config directory** (documented, can be a setup make target): + - `acquis.yaml` already exists at `swarm/crowdsec/acquis.yaml` — it uses `source: docker` and `use_container_labels: true` which 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/traefik` collection includes the Traefik parser + +9. **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 + +10. **Add `deploy-crowdsec` target to `makefile`:** + ```makefile + 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-crowdsec` target. + +11. **Document required `.env` variables:** + - `CROWDSEC_LAPI_KEY=` — LAPI admin key + - `CROWDSEC_BOUNCER_KEY= cscli bouncers add traefik-bouncer>` — bouncer API key + - Or automate bouncer registration via a setup script + +#### Phase E — Post-deploy setup (manual or scripted) + +12. **Register the bouncer** with CrowdSec LAPI after first deploy: + - Either document manual step: `docker service ps crowdsec_crowdsec --no-trunc` to get container ID, then `docker exec -it 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 ` in a setup script + +13. **Add Traefik container label** for CrowdSec acquisition (in `swarm/core.yml`): + - Traefik itself needs `crowdsec.enable=true` label so CrowdSec picks up its logs + - This is a Docker label (not a Traefik label) — add via `deploy.labels` on the Traefik service + +14. **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 + +1. **Stack validation:** `docker stack deploy --compose-file ./swarm/crowdsec.yml crowdsec --dry-run` (or manually `docker compose -f swarm/crowdsec.yml config` to validate syntax) +2. **Service health:** `docker service ls | grep crowdsec` → all services running +3. **LAPI health:** `curl http://localhost:8080/health` from manager node (if LAPI port exposed temporarily) +4. **Bouncer connectivity:** Check `docker service logs crowdsec_traefik-bouncer` for successful LAPI connection +5. **Traefik middleware:** `docker service logs core_traefik` should show no errors about missing middleware +6. **End-to-end:** Visit a protected HTTPS service → confirm it loads. Check CrowdSec metrics: `docker exec -it cscli metrics` +7. **Block test:** Simulate an attack or manually add a decision: `docker exec -it cscli decisions add --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: 1` for 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 + +1. **Bouncer key registration:** Will use manual `cscli bouncers add` post-deploy. Could automate later with an init container or a swarm cronjob. +2. **Host config directory:** `/var/data/crowdsec/` needs to exist on the Swarm node before deploy. Will add a `setup-crowdsec` make target or document as prerequisite. +3. **Local/development env:** There's also `local.yml` and `swarm/local_core.yml` — should crowdsec be added to those too? Skipping for now unless the user asks.