mirror of
https://github.com/furyhawk/cloudy.git
synced 2026-07-21 02:05:53 +00:00
feat: integrate CrowdSec with Traefik and add deployment instructions
This commit is contained in:
@@ -84,6 +84,13 @@ AUTHENTIK_EMAIL__FROM=authentik@localhost
|
|||||||
COMPOSE_PORT_HTTP=80
|
COMPOSE_PORT_HTTP=80
|
||||||
COMPOSE_PORT_HTTPS=443
|
COMPOSE_PORT_HTTPS=443
|
||||||
|
|
||||||
|
# CrowdSec (Swarm)
|
||||||
|
# Generate a value with: openssl rand -base64 32
|
||||||
|
CROWDSEC_LAPI_KEY=change-me
|
||||||
|
# Must match a bouncer key registered in CrowdSec, for example:
|
||||||
|
# cscli bouncers add traefik-bouncer --key <value>
|
||||||
|
CROWDSEC_BOUNCER_KEY=change-me
|
||||||
|
|
||||||
#=====================================================================#
|
#=====================================================================#
|
||||||
# LibreChat Configuration #
|
# LibreChat Configuration #
|
||||||
#=====================================================================#
|
#=====================================================================#
|
||||||
|
|||||||
@@ -44,6 +44,56 @@ make deploy-core
|
|||||||
docker service logs core_traefik --details
|
docker service logs core_traefik --details
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## CrowdSec Runbook (Swarm + Traefik)
|
||||||
|
|
||||||
|
### 1) Set keys in swarm env
|
||||||
|
In `swarm/.env`, set:
|
||||||
|
|
||||||
|
```env
|
||||||
|
CROWDSEC_LAPI_KEY=<openssl rand -base64 32>
|
||||||
|
CROWDSEC_BOUNCER_KEY=<openssl rand -base64 32>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2) Deploy CrowdSec stack
|
||||||
|
```bash
|
||||||
|
make deploy-crowdsec
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3) Register the Traefik bouncer
|
||||||
|
```bash
|
||||||
|
make register-crowdsec-bouncer
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4) (Re)deploy Traefik core
|
||||||
|
This ensures the global HTTPS forwardAuth middleware is active.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make deploy-core
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5) Verify health and wiring
|
||||||
|
```bash
|
||||||
|
docker service ls | grep crowdsec
|
||||||
|
docker service logs --since 10m crowdsec_traefik-bouncer
|
||||||
|
docker service logs --since 10m core_traefik
|
||||||
|
docker ps --filter label=com.docker.swarm.service.name=crowdsec_crowdsec
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6) Verify decisions and blocking
|
||||||
|
```bash
|
||||||
|
# Show parser/scenario metrics
|
||||||
|
docker exec -it $(docker ps --filter label=com.docker.swarm.service.name=crowdsec_crowdsec -q | head -n1) cscli metrics
|
||||||
|
|
||||||
|
# Add a temporary test decision for your client IP and verify HTTPS returns 403
|
||||||
|
docker exec -it $(docker ps --filter label=com.docker.swarm.service.name=crowdsec_crowdsec -q | head -n1) cscli decisions add --ip <YOUR_TEST_IP>
|
||||||
|
```
|
||||||
|
|
||||||
|
Cleanup test decision when done:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker exec -it $(docker ps --filter label=com.docker.swarm.service.name=crowdsec_crowdsec -q | head -n1) cscli decisions delete --ip <YOUR_TEST_IP>
|
||||||
|
```
|
||||||
|
|
||||||
### Notes:
|
### Notes:
|
||||||
```yaml
|
```yaml
|
||||||
# Declaring the user list
|
# Declaring the user list
|
||||||
|
|||||||
@@ -33,6 +33,37 @@ deploy-core: pull
|
|||||||
remove-core:
|
remove-core:
|
||||||
docker stack rm core
|
docker stack rm core
|
||||||
|
|
||||||
|
setup-crowdsec:
|
||||||
|
{ \
|
||||||
|
echo "Setting up crowdsec config on host..." ;\
|
||||||
|
mkdir -p /var/data/crowdsec ;\
|
||||||
|
cmp -s ./swarm/crowdsec/acquis.yaml /var/data/crowdsec/acquis.yaml 2>/dev/null || cp ./swarm/crowdsec/acquis.yaml /var/data/crowdsec/acquis.yaml ;\
|
||||||
|
}
|
||||||
|
|
||||||
|
deploy-crowdsec: pull setup-crowdsec
|
||||||
|
{ \
|
||||||
|
echo "Deploying the crowdsec stack..." ;\
|
||||||
|
set -a ;\
|
||||||
|
. ./swarm/.env ;\
|
||||||
|
set +a ;\
|
||||||
|
docker stack deploy --compose-file ./swarm/crowdsec.yml crowdsec ;\
|
||||||
|
}
|
||||||
|
|
||||||
|
register-crowdsec-bouncer:
|
||||||
|
{ \
|
||||||
|
echo "Registering crowdsec traefik bouncer..." ;\
|
||||||
|
set -a ;\
|
||||||
|
. ./swarm/.env ;\
|
||||||
|
set +a ;\
|
||||||
|
bash ./scripts/register-crowdsec-bouncer.sh ;\
|
||||||
|
}
|
||||||
|
|
||||||
|
deploy-crowdsec-all: deploy-crowdsec register-crowdsec-bouncer deploy-core
|
||||||
|
@echo "CrowdSec + bouncer registration + core deploy completed."
|
||||||
|
|
||||||
|
remove-crowdsec:
|
||||||
|
docker stack rm crowdsec
|
||||||
|
|
||||||
deploy-portainer: pull
|
deploy-portainer: pull
|
||||||
{ \
|
{ \
|
||||||
echo "Deploying the portainer stack..." ;\
|
echo "Deploying the portainer stack..." ;\
|
||||||
|
|||||||
Executable
+33
@@ -0,0 +1,33 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
BOUNCER_NAME="${CROWDSEC_BOUNCER_NAME:-traefik-bouncer}"
|
||||||
|
BOUNCER_KEY="${CROWDSEC_BOUNCER_KEY:-}"
|
||||||
|
SERVICE_NAME="crowdsec_crowdsec"
|
||||||
|
|
||||||
|
if [[ -z "$BOUNCER_KEY" ]]; then
|
||||||
|
echo "CROWDSEC_BOUNCER_KEY is not set. Export it or source swarm/.env first."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v docker >/dev/null 2>&1; then
|
||||||
|
echo "docker command not found in PATH."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
container_id="$(docker ps --filter "label=com.docker.swarm.service.name=${SERVICE_NAME}" --format '{{.ID}}' | head -n1)"
|
||||||
|
|
||||||
|
if [[ -z "$container_id" ]]; then
|
||||||
|
echo "No running CrowdSec LAPI task found for service ${SERVICE_NAME}."
|
||||||
|
echo "Deploy CrowdSec first: make deploy-crowdsec"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if docker exec "$container_id" cscli bouncers list 2>/dev/null | grep -q "$BOUNCER_NAME"; then
|
||||||
|
echo "Bouncer '${BOUNCER_NAME}' is already registered."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Registering bouncer '${BOUNCER_NAME}' on ${SERVICE_NAME}..."
|
||||||
|
docker exec "$container_id" cscli bouncers add "$BOUNCER_NAME" --key "$BOUNCER_KEY"
|
||||||
|
echo "Bouncer registration complete."
|
||||||
+7
-2
@@ -91,6 +91,8 @@ services:
|
|||||||
- traefik.http.middlewares.xbot.headers.stsIncludeSubdomains=true
|
- traefik.http.middlewares.xbot.headers.stsIncludeSubdomains=true
|
||||||
- traefik.http.middlewares.xbot.headers.permissionsPolicy="accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(), geolocation=(), gyroscope=(), layout-animations=(), legacy-image-formats=(), magnetometer=(), microphone=(), midi=(), oversized-images=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), sync-xhr=(), usb=(), wake-lock=(), xr-spatial-tracking=()"
|
- traefik.http.middlewares.xbot.headers.permissionsPolicy="accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(), geolocation=(), gyroscope=(), layout-animations=(), legacy-image-formats=(), magnetometer=(), microphone=(), midi=(), oversized-images=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), sync-xhr=(), usb=(), wake-lock=(), xr-spatial-tracking=()"
|
||||||
- traefik.http.middlewares.xbot.headers.referrerPolicy="no-referrer"
|
- traefik.http.middlewares.xbot.headers.referrerPolicy="no-referrer"
|
||||||
|
- traefik.http.middlewares.crowdsec-bouncer.forwardauth.address=http://traefik-bouncer:8080/api/v1/forwardAuth
|
||||||
|
- traefik.http.middlewares.crowdsec-bouncer.forwardauth.trustForwardHeader=true
|
||||||
- treafik.http.middlewares.neo4j_strip.stripprefix.prefixes=/neo4j
|
- treafik.http.middlewares.neo4j_strip.stripprefix.prefixes=/neo4j
|
||||||
# traefik-https the actual router using HTTPS
|
# traefik-https the actual router using HTTPS
|
||||||
- traefik.http.routers.traefik-public-https.rule=Host(`dashboard.${DOMAIN?Variable not set}`)
|
- traefik.http.routers.traefik-public-https.rule=Host(`dashboard.${DOMAIN?Variable not set}`)
|
||||||
@@ -106,8 +108,9 @@ services:
|
|||||||
- traefik.http.services.traefik-public.loadbalancer.server.port=8080
|
- traefik.http.services.traefik-public.loadbalancer.server.port=8080
|
||||||
# Pass the original Host header to the backend
|
# Pass the original Host header to the backend
|
||||||
# - "traefik.http.services.dashboard.loadbalancer.passhostheader=true"
|
# - "traefik.http.services.dashboard.loadbalancer.passhostheader=true"
|
||||||
# - crowdsec.enable=true
|
# CrowdSec log acquisition for Traefik is label-based via docker source.
|
||||||
# - crowdsec.labels.type=nginx
|
- crowdsec.enable=true
|
||||||
|
- crowdsec.labels.type=traefik
|
||||||
volumes:
|
volumes:
|
||||||
# Add Docker as a mounted volume, so that Traefik can read the labels of other services
|
# Add Docker as a mounted volume, so that Traefik can read the labels of other services
|
||||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
@@ -132,6 +135,8 @@ services:
|
|||||||
- --entrypoints.http.address=:80
|
- --entrypoints.http.address=:80
|
||||||
# Create an entrypoint "https" listening on port 443
|
# Create an entrypoint "https" listening on port 443
|
||||||
- --entrypoints.https.address=:443
|
- --entrypoints.https.address=:443
|
||||||
|
# Apply CrowdSec bouncer globally to all HTTPS routers discovered by Traefik.
|
||||||
|
- --entrypoints.https.http.middlewares=crowdsec-bouncer@swarm
|
||||||
# Redirect HTTP to HTTPS
|
# Redirect HTTP to HTTPS
|
||||||
- --entrypoints.http.http.redirections.entrypoint.to=https
|
- --entrypoints.http.http.redirections.entrypoint.to=https
|
||||||
- --entrypoints.http.http.redirections.entrypoint.scheme=https
|
- --entrypoints.http.http.redirections.entrypoint.scheme=https
|
||||||
|
|||||||
+32
-15
@@ -1,27 +1,38 @@
|
|||||||
services:
|
services:
|
||||||
crowdsec:
|
crowdsec:
|
||||||
image: crowdsecurity/crowdsec:slim
|
image: crowdsecurity/crowdsec:slim
|
||||||
restart: always
|
|
||||||
networks:
|
networks:
|
||||||
crowdsec:
|
- crowdsec-internal
|
||||||
environment:
|
environment:
|
||||||
DOCKER_HOST: tcp://socket-proxy:2375
|
DOCKER_HOST: tcp://socket-proxy:2375
|
||||||
COLLECTIONS: "crowdsecurity/nginx"
|
COLLECTIONS: "crowdsecurity/traefik"
|
||||||
ports:
|
CUSTOM_CS_LAPI_KEY: ${CROWDSEC_LAPI_KEY:?Variable not set}
|
||||||
- "127.0.0.1:8080:8080"
|
deploy:
|
||||||
depends_on:
|
restart_policy:
|
||||||
- 'socket-proxy'
|
condition: any
|
||||||
volumes:
|
volumes:
|
||||||
- ./crowdsec/acquis.yaml:/etc/crowdsec/acquis.yaml
|
- /var/data/crowdsec/acquis.yaml:/etc/crowdsec/acquis.yaml:ro
|
||||||
- crowdsec-db:/var/lib/crowdsec/data/
|
- crowdsec-db:/var/lib/crowdsec/data/
|
||||||
- crowdsec-config:/etc/crowdsec/
|
- crowdsec-config:/etc/crowdsec/
|
||||||
|
|
||||||
socket-proxy:
|
traefik-bouncer:
|
||||||
|
image: crowdsecurity/traefik-bouncer:latest
|
||||||
|
environment:
|
||||||
|
CROWDSEC_LAPI_URL: http://crowdsec:8080
|
||||||
|
CROWDSEC_LAPI_KEY: ${CROWDSEC_BOUNCER_KEY:?Variable not set}
|
||||||
|
expose:
|
||||||
|
- "8080"
|
||||||
networks:
|
networks:
|
||||||
crowdsec:
|
- crowdsec-internal
|
||||||
restart: always
|
- traefik-public
|
||||||
|
deploy:
|
||||||
|
restart_policy:
|
||||||
|
condition: any
|
||||||
|
|
||||||
|
socket-proxy:
|
||||||
image: lscr.io/linuxserver/socket-proxy:latest
|
image: lscr.io/linuxserver/socket-proxy:latest
|
||||||
container_name: socket-proxy
|
networks:
|
||||||
|
- crowdsec-internal
|
||||||
environment:
|
environment:
|
||||||
INFO: 1
|
INFO: 1
|
||||||
CONTAINERS: 1
|
CONTAINERS: 1
|
||||||
@@ -38,10 +49,13 @@ services:
|
|||||||
PLUGINS: 0
|
PLUGINS: 0
|
||||||
SERVICES: 0
|
SERVICES: 0
|
||||||
SESSION: 0
|
SESSION: 0
|
||||||
SWARM: 0
|
SWARM: 1
|
||||||
SYSTEM: 0
|
SYSTEM: 0
|
||||||
TASKS: 0
|
TASKS: 0
|
||||||
VOLUMES: 0
|
VOLUMES: 0
|
||||||
|
deploy:
|
||||||
|
restart_policy:
|
||||||
|
condition: any
|
||||||
volumes:
|
volumes:
|
||||||
- '/var/run/docker.sock:/var/run/docker.sock:ro'
|
- '/var/run/docker.sock:/var/run/docker.sock:ro'
|
||||||
read_only: true
|
read_only: true
|
||||||
@@ -53,5 +67,8 @@ volumes:
|
|||||||
crowdsec-config:
|
crowdsec-config:
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
crowdsec:
|
crowdsec-internal:
|
||||||
driver: bridge
|
driver: overlay
|
||||||
|
attachable: true
|
||||||
|
traefik-public:
|
||||||
|
external: true
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
source: docker
|
source: docker
|
||||||
use_container_labels: true ## https://docs.crowdsec.net/docs/next/data_sources/docker#use_container_labels
|
use_container_labels: true ## https://docs.crowdsec.net/docs/next/data_sources/docker#use_container_labels
|
||||||
|
# Traefik service must set these docker labels so CrowdSec can classify logs correctly:
|
||||||
|
# - crowdsec.enable=true
|
||||||
|
# - crowdsec.labels.type=traefik
|
||||||
check_interval: 10s ## How often to check for new containers https://docs.crowdsec.net/docs/next/appsec/quickstart/traefik
|
check_interval: 10s ## How often to check for new containers https://docs.crowdsec.net/docs/next/appsec/quickstart/traefik
|
||||||
Reference in New Issue
Block a user