feat(crowdsec): añadir stack de detección de intrusiones con Grafana

- docker-compose.yml: CrowdSec + bouncer de Traefik + Postgres + Grafana
- setup.sh: configuración inicial de colecciones, bouncers y whitelist
- metrics-export.sh: exportación de métricas al endpoint de Prometheus
- traefik-crowdsec-bouncer.yml: config dinámica de Traefik para el bouncer
- stack.env: plantilla con API keys y configuración

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Eduardo David Paredes Vara
2026-05-25 05:48:58 +00:00
parent 38587699f9
commit 49a7e16da1
6 changed files with 808 additions and 0 deletions
+481
View File
@@ -0,0 +1,481 @@
# CrowdSec - Sistema de Seguridad y Prevención de Intrusiones
CrowdSec es un IPS/IDS colaborativo que analiza logs, detecta comportamientos maliciosos y bloquea atacantes
en tiempo real. Usa inteligencia comunitaria para compartir reputación de IPs entre todos los usuarios.
## 📋 Descripción
Este stack despliega:
- **CrowdSec Engine**: Motor de detección que analiza logs de Traefik y otros contenedores
- **CrowdSec Bouncer (Traefik Plugin)**: Middleware que bloquea IPs maliciosas a nivel HTTP (L7)
- **Grafana**: Dashboard web para visualizar alertas, decisiones y métricas (protegido con Authentik)
- Baneos automáticos contra ataques de fuerza bruta, escaneos, CVEs conocidos y más
## 🚀 Despliegue
### Prerequisitos
1. **Red Docker**: La red `proxy` debe existir
```bash
docker network create proxy
```
2. **Registro DNS**: Configura el registro A para el dashboard
- `grafana.tudominio.com` → IP del servidor
3. **Directorios**: Créalos automáticamente con el setup:
```bash
cd crowdsec && ./setup.sh
```
### Paso 1: Configurar variables de entorno
Copia la plantilla y edita las variables:
```bash
cp crowdsec/stack.env crowdsec/.env
nano crowdsec/.env
```
Variables críticas:
```env
# Clave API del bouncer (generada automáticamente por setup.sh o pon una manual)
CROWDSEC_BOUNCER_API_KEY=tu-key-de-64-caracteres
# Dominio del dashboard Grafana
CROWDSEC_GRAFANA_DOMAIN=grafana.tudominio.com
# Colecciones a instalar
CROWDSEC_COLLECTIONS=crowdsecurity/traefik
# Rutas de datos
CROWDSEC_CONFIG_PATH=/opt/crowdsec/config
CROWDSEC_DATA_PATH=/opt/crowdsec/data
TRAEFIK_LOG_PATH=/opt/traefik/logs
```
### Paso 2: Ejecutar el script de setup
```bash
cd crowdsec
./setup.sh
```
El script:
1. Genera una clave aleatoria para el bouncer (si no está definida)
2. Crea los directorios necesarios en `/opt/crowdsec/` y `/opt/traefik/logs`
3. Genera la configuración dinámica de Traefik (`crowdsec-bouncer.yml`)
4. Crea los archivos `acquis.yaml` para leer logs de Traefik y Docker
5. Aplica contextos SELinux si es necesario
### Paso 3: (Re)desplegar Traefik
**Importante**: Traefik necesita reiniciarse para cargar el plugin de CrowdSec y los nuevos volúmenes.
Desde Portainer:
1. Ve a **Stacks** → stack `traefik`
2. Actualiza el archivo `.env` de Traefik para incluir `TRAEFIK_LOG_DIR=/opt/traefik/logs`
3. Haz clic en **Update the stack**
O desde CLI:
```bash
cd Traefik
docker compose up -d
```
### Paso 4: Desplegar CrowdSec
Desde Portainer:
1. Ve a **Stacks** → **Add stack**
2. Nombre: `crowdsec`
3. Selecciona **Repository**
4. Configura:
- Repository URL: `<tu-repositorio>`
- Repository reference: `main`
- Compose path: `crowdsec/docker-compose.yml`
5. Carga `crowdsec/.env` como archivo de variables de entorno
6. Haz clic en **Deploy the stack**
O desde CLI:
```bash
cd crowdsec
docker compose up -d
```
### Paso 5: Verificar el despliegue
```bash
# Ver contenedores
docker ps | grep crowdsec
# Ver logs del engine
docker logs crowdsec
# Verificar decisiones activas
docker exec crowdsec cscli decisions list
# Verificar métricas
docker exec crowdsec cscli metrics
# Verificar estado del bouncer
docker exec crowdsec cscli bouncers list
```
## 🔐 Configurar Authentik para el Dashboard (Grafana)
El dashboard de Grafana (`grafana.tudominio.com`) está protegido con `ths-authentik@docker`. Para que funcione:
### En el panel de Authentik:
1. **Crear Proxy Provider**:
- Ve a **Applications** → **Providers** → **Create**
- Tipo: **Proxy Provider**
- Name: `CrowdSec Grafana`
- External host: `https://grafana.tudominio.com`
- Authentication flow: `default-authentication-flow`
- Authorization flow: `default-provider-authorization-implicit-consent`
- Mode: `Forward auth (single application)`
2. **Crear Application**:
- Ve a **Applications** → **Create**
- Name: `CrowdSec Grafana`
- Slug: `grafana`
- Provider: selecciona el creado en paso 1
- UI Settings: opcional (icono, descripción)
3. **Asociar al Embedded Outpost**:
- Ve a **Outposts** → `authentik Embedded Outpost` → **Edit**
- Asegúrate de que la aplicación `CrowdSec Grafana` está seleccionada
- Haz clic en **Update**
> El middleware `ths-authentik@docker` ya está configurado en las labels del servicio `crowdsec-grafana`. Si otros stacks también lo usan, comparte la misma configuración de Authentik.
## 🛡️ Proteger servicios con CrowdSec
### Método 1: Middleware global (recomendado)
Añade el middleware `crowdsec-bouncer@file` a los routers de Traefik que quieras proteger:
```yaml
labels:
# ... labels existentes ...
traefik.http.routers.mi-app.middlewares: "ths-authentik@docker,crowdsec-bouncer@file"
```
> Importante: los middlewares se encadenan en orden. `crowdsec-bouncer` primero bloquea IPs maliciosas, luego `ths-authentik` autentica.
### Método 2: Solo CrowdSec (sin Authentik)
Para servicios que no requieren login pero sí protección anti-ataques (ej: APIs):
```yaml
labels:
traefik.http.routers.mi-api.middlewares: "crowdsec-bouncer@file"
```
### Lista de servicios recomendados para proteger
| Prioridad | Servicio | Motivo |
|-----------|----------|--------|
| Alta | Traefik Dashboard | Expuesto, blanco de escaneos |
| Alta | Authentik | Puerta de entrada SSO, blanco de brute force |
| Alta | Gitea | Expuesto a clonaciones maliciosas |
| Media | n8n | Webhooks públicos, riesgo de SSRF |
| Media | Nextcloud | Login expuesto, brute force |
| Media | Homepage | Aunque es solo dashboard, expuesto |
## 🔍 Comandos útiles
### Ver estado de CrowdSec
```bash
# Estadísticas generales
docker exec crowdsec cscli metrics
# Decisiones activas (baneos)
docker exec crowdsec cscli decisions list
# Alertas recientes
docker exec crowdsec cscli alerts list --limit 20
# Inspeccionar una IP específica
docker exec crowdsec cscli decisions inspect --ip 1.2.3.4
# Ver bouncers registrados
docker exec crowdsec cscli bouncers list
```
### Gestión de decisiones
```bash
# Banear una IP manualmente (1 hora)
docker exec crowdsec cscli decisions add --ip 1.2.3.4 --duration 1h --reason "manual"
# Desbanear una IP
docker exec crowdsec cscli decisions delete --ip 1.2.3.4
# Listar escenarios activos
docker exec crowdsec cscli scenarios list
```
### Allowlists (IPs que nunca se banean)
Usar `cscli allowlists` (disponible desde CrowdSec v1.6+) en lugar del antiguo truco de `decisions add --duration 99999h`. Las allowlists son permanentes y sobreviven a reinicios.
```bash
# Crear una allowlist nueva
docker exec crowdsec cscli allowlists create <nombre> -d "Descripción"
# Añadir una IP o CIDR a una allowlist
docker exec crowdsec cscli allowlists add <nombre> <ip-o-cidr>
# Quitar una IP de una allowlist
docker exec crowdsec cscli allowlists remove <nombre> <ip-o-cidr>
# Listar todas las allowlists y su contenido
docker exec crowdsec cscli allowlists list
docker exec crowdsec cscli allowlists inspect <nombre>
```
#### Allowlists configuradas en este servidor
| Allowlist | Contenido | Propósito |
|-----------|-----------|-----------|
| `vpn-local` | `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `10.8.0.0/24` | Redes privadas y WireGuard VPN |
| `cloudflare` | Todos los rangos IPv4 de Cloudflare | Tráfico legítimo de Cloudflare (si se usa Cloudflare Tunnel) |
| `telegram-bot` | IPs añadidas dinámicamente con `/allow <ip>` | IPs gestionadas por el bot de Telegram |
Para recrearlas desde cero:
```bash
# vpn-local
docker exec crowdsec cscli allowlists create vpn-local -d "Redes privadas y VPN"
docker exec crowdsec cscli allowlists add vpn-local 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 10.8.0.0/24
# cloudflare
docker exec crowdsec cscli allowlists create cloudflare -d "Rangos IPv4 de Cloudflare"
docker exec crowdsec cscli allowlists add cloudflare \
173.245.48.0/20 103.21.244.0/22 103.22.200.0/22 103.31.4.0/22 \
141.101.64.0/18 108.162.192.0/18 190.93.240.0/20 188.114.96.0/20 \
197.234.240.0/22 198.41.128.0/17 162.158.0.0/15 104.16.0.0/13 \
104.24.0.0/14 172.64.0.0/13 131.0.72.0/22
# telegram-bot (vacía, el bot la gestiona)
docker exec crowdsec cscli allowlists create telegram-bot -d "IPs allowlisteadas via bot de Telegram"
```
### Actualizar la blocklist comunitaria
```bash
# Actualizar manualmente
docker exec crowdsec cscli hub update
# Ver si hay actualizaciones pendientes
docker exec crowdsec cscli hub list
```
## ⚙️ Configuración adicional
### Añadir más colecciones
Edita `crowdsec/.env` y añade colecciones separadas por coma:
```env
CROWDSEC_COLLECTIONS=crowdsecurity/traefik,crowdsecurity/http-cve,crowdsecurity/whitelist-good-actors
```
Luego redeploya el stack. Colecciones recomendadas:
| Colección | Descripción |
|-----------|-------------|
| `crowdsecurity/traefik` | Parsea logs de acceso de Traefik |
| `crowdsecurity/http-cve` | Detecta intentos de explotación de CVEs HTTP |
| `crowdsecurity/whitelist-good-actors` | Whitelist de servicios legítimos (Google, CloudFlare, etc.) |
| `crowdsecurity/nginx` | Escenarios extra para servidores web |
| `crowdsecurity/base-http-scenarios` | Escaneos básicos HTTP (path traversal, SQLi, etc.) |
### Configurar whitelist de IPs locales
Para que CrowdSec no banee IPs de tu red local/VPN, edita `/opt/crowdsec/config/parsers/s02-enrich/whitelists.yaml`:
```bash
docker exec crowdsec cscli parsers install crowdsecurity/whitelists
```
O añade manualmente en `notifications/http.yaml` el parámetro `whitelist_ips`.
### Añadir adquisición de logs adicionales
Edita `/opt/crowdsec/config/acquis.yaml` para añadir fuentes de log adicionales:
```yaml
---
# Logs de Traefik (ya configurado)
source: file
filenames:
- /var/log/traefik/access.log
labels:
type: traefik
# Logs de Authentik (ejemplo adicional)
# source: docker
# container_name:
# - ths-authentik-server
# labels:
# type: authentik
```
## 🤖 Integración con el bot de Telegram
El bot `f2b-telegram-bot` (en `/home/felidae/f2b-telegram-bot/`) gestiona fail2ban, firewalld **y CrowdSec** de forma unificada. El comando `/allow <ip>` ejecuta en una sola operación:
1. Añade la IP a la allowlist persistente del bot
2. Sincroniza `ignoreip` en fail2ban
3. Permite la IP en firewalld (ipset)
4. Elimina cualquier decisión activa en CrowdSec (`cscli decisions delete`)
5. Añade la IP a la allowlist `telegram-bot` en CrowdSec (nunca volverá a banearse)
### Comandos relevantes del bot
| Comando | Efecto en CrowdSec |
|---------|-------------------|
| `/allow <ip>` | Unban + añade a allowlist `telegram-bot` |
| `/unallow <ip>` | Quita de allowlist `telegram-bot` |
| `/unban <ip>` | Solo unban (sin añadir a allowlist) |
| `/health` | Muestra estado de LAPI + disponibilidad Docker |
### Problema CGNAT y mitigación
Si el servidor está en una red sin IP fija o compartida (CGNAT), un atacante en la misma red puede
provocar que tu IP legítima quede baneada por CrowdSec. La mitigación implementada:
1. **Bot de Telegram**: `/allow <ip>` desbanea y allowlistea de inmediato en fail2ban + firewalld + CrowdSec.
2. **WireGuard VPN**: Acceso por `10.8.0.0/24`, en la allowlist `vpn-local` → nunca baneada.
3. **Cloudflare Tunnel** (opcional, ver `cloudflared/README.md`): elimina el problema de raíz al ocultar la IP real del servidor.
### Configuración del bot para CrowdSec
Variables de entorno relevantes en `.env` del bot:
```env
CROWDSEC_ENABLED=true
CROWDSEC_ALLOWLIST_NAME=telegram-bot
```
Requiere que el bot se ejecute en el mismo host que el contenedor `crowdsec` (acceso a `docker exec`).
---
## 🔒 Seguridad
- **API local solo en loopback**: La API de CrowdSec (`cscli lapi`) solo escucha en `127.0.0.1:8080`
- **Bouncer integrado en Traefik**: No expone puertos adicionales, todo el filtrado ocurre dentro de Traefik
- **Dashboard protegido por Authentik**: Grafana requiere autenticación SSO previa
- **Motor con capacidades mínimas**: Solo `NET_BIND_SERVICE`, sin `CAP_SYS_ADMIN` ni privilegios elevados
- **SELinux**: Volúmenes con contexto `:Z` para entornos con SELinux habilitado
- **Key del bouncer**: Generada aleatoriamente y almacenada solo en `.env` (gitignorado)
## 🛠️ Troubleshooting
### El bouncer no bloquea
1. Verifica que el plugin está cargado en Traefik:
```bash
docker logs traefik | grep crowdsec
```
2. Verifica que el middleware está configurado:
```bash
docker exec traefik wget -qO- http://localhost:8080/api/http/middlewares | grep crowdsec
```
3. Verifica que CrowdSec tiene decisiones activas:
```bash
docker exec crowdsec cscli decisions list
```
4. Comprueba conectividad entre Traefik y CrowdSec:
```bash
docker exec traefik wget -qO- http://crowdsec:8080/v1/heartbeat
```
### CrowdSec no detecta ataques
1. Verifica que los logs de Traefik se están generando:
```bash
ls -lh /opt/traefik/logs/access.log
tail -f /opt/traefik/logs/access.log
```
2. Verifica el acquis de CrowdSec:
```bash
docker exec crowdsec cat /etc/crowdsec/acquis.yaml
```
3. Revisa los logs del engine:
```bash
docker logs -f crowdsec
```
4. Simula un ataque de prueba:
```bash
curl -H "User-Agent: Nikto" https://tu-dominio.com/
docker exec crowdsec cscli decisions list
```
### El dashboard pide login pero Authentik no redirige
1. Verifica que el middleware está correctamente referenciado en las labels:
```bash
docker inspect crowdsec-grafana | grep -A 20 Labels
```
2. Revisa que la aplicación existe en Authentik y está asociada al Embedded Outpost
3. Comprueba los logs de Authentik:
```bash
docker logs ths-authentik-server
```
## 💾 Backups
Incluye en tus backups de Kopia (ya configurados en `backups/kopia/`):
```bash
# Datos de CrowdSec (decisiones, bouncers, config)
/opt/crowdsec/data/
# Configuración personalizada
/opt/crowdsec/config/
```
## 🔄 Actualizaciones
Para actualizar CrowdSec:
1. En Portainer, ve al stack `crowdsec`
2. Edita `.env` y actualiza la versión si es necesario:
```env
CROWDSEC_IMAGE=crowdsecurity/crowdsec:latest
GRAFANA_IMAGE=grafana/grafana:latest
```
3. Haz clic en **Update the stack**
4. O desde CLI:
```bash
cd crowdsec
docker compose pull
docker compose up -d
```
> ⚠️ Antes de actualizar versiones mayores de CrowdSec, revisa el [changelog](https://github.com/crowdsecurity/crowdsec/releases).
## 📚 Recursos
- [Documentación oficial de CrowdSec](https://docs.crowdsec.net/)
- [CrowdSec + Traefik Plugin](https://plugins.traefik.io/plugins/6335349fe93a526d1ec2aed6/crowdsec-bouncer)
- [CrowdSec Hub (colecciones)](https://hub.crowdsec.net/)
- [Grafana Docs](https://grafana.com/docs/grafana/latest/)
- [CrowdSec Discord](https://discord.gg/crowdsec)
+126
View File
@@ -0,0 +1,126 @@
services:
crowdsec:
image: ${CROWDSEC_IMAGE}
pull_policy: always
container_name: crowdsec
restart: unless-stopped
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
environment:
COLLECTIONS: ${CROWDSEC_COLLECTIONS}
BOUNCER_KEY_TRAEFIK: ${CROWDSEC_BOUNCER_API_KEY}
LEVEL: ${CROWDSEC_LOG_LEVEL}
TZ: ${TZ:-America/Mexico_City}
POSTGRES_HOST: crowdsec-postgres
POSTGRES_PORT: "5432"
POSTGRES_DB: ${CROWDSEC_POSTGRES_DB}
POSTGRES_USER: ${CROWDSEC_POSTGRES_USER}
POSTGRES_PASSWORD: ${CROWDSEC_POSTGRES_PASSWORD}
volumes:
- ${CROWDSEC_CONFIG_PATH}:/etc/crowdsec:Z
- crowdsec_lib:/var/lib/crowdsec
- ${TRAEFIK_LOG_PATH}:/var/log/traefik:ro
- /var/log:/var/log/host:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
expose:
- "8080"
- "7422"
ports:
- "127.0.0.1:8080:8080"
networks:
- crowdsec_internal
- proxy
healthcheck:
test: ["CMD", "cscli", "lapi", "status"]
interval: 30s
timeout: 5s
retries: 3
crowdsec-postgres:
image: ${CROWDSEC_POSTGRES_IMAGE}
container_name: crowdsec-postgres
restart: unless-stopped
environment:
POSTGRES_USER: ${CROWDSEC_POSTGRES_USER}
POSTGRES_PASSWORD: ${CROWDSEC_POSTGRES_PASSWORD}
POSTGRES_DB: ${CROWDSEC_POSTGRES_DB}
volumes:
- ${CROWDSEC_POSTGRES_PATH}:/var/lib/postgresql/data:Z
networks:
- crowdsec_internal
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${CROWDSEC_POSTGRES_USER} -d ${CROWDSEC_POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 5
crowdsec-prometheus:
image: prom/prometheus:latest
container_name: crowdsec-prometheus
restart: unless-stopped
command:
- "--config.file=/etc/prometheus/prometheus.yml"
- "--storage.tsdb.path=/prometheus"
- "--storage.tsdb.retention.time=7d"
- "--web.enable-lifecycle"
volumes:
- ${CROWDSEC_PROMETHEUS_CONFIG}/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- ${CROWDSEC_PROMETHEUS_PATH}:/prometheus:Z
networks:
- crowdsec_internal
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:9090/-/healthy"]
interval: 30s
timeout: 5s
retries: 3
crowdsec-grafana:
image: ${GRAFANA_IMAGE}
container_name: crowdsec-grafana
restart: unless-stopped
environment:
GF_SERVER_ROOT_URL: https://${CROWDSEC_GRAFANA_DOMAIN}
GF_AUTH_PROXY_ENABLED: "true"
GF_AUTH_PROXY_HEADER_NAME: X-Authentik-Username
GF_AUTH_PROXY_HEADER_EMAIL: X-Authentik-Email
GF_AUTH_PROXY_HEADER_GROUPS: X-Authentik-Groups
GF_AUTH_PROXY_AUTO_SIGN_UP: "true"
GF_AUTH_PROXY_ENABLE_LOGIN_TOKEN: "false"
GF_AUTH_DISABLE_LOGIN_FORM: "true"
GF_AUTH_SIGNOUT_REDIRECT: https://${AUTHENTIK_DOMAIN:-auth.sherlockhomeless.net}/application/o/${CROWDSEC_GRAFANA_SLUG:-grafana}/end-session/
volumes:
- ${CROWDSEC_GRAFANA_PATH}:/var/lib/grafana:Z
- ${CROWDSEC_GRAFANA_PROVISIONING}:/etc/grafana/provisioning:ro
- ${CROWDSEC_GRAFANA_DASHBOARDS}:/var/lib/grafana/dashboards:ro
networks:
- crowdsec_internal
- proxy
labels:
traefik.enable: "true"
traefik.docker.network: "${TRAEFIK_DOCKER_NETWORK}"
traefik.http.routers.crowdsec-grafana.rule: "Host(`${CROWDSEC_GRAFANA_DOMAIN}`)"
traefik.http.routers.crowdsec-grafana.entrypoints: "${TRAEFIK_ENTRYPOINT_SECURE}"
traefik.http.routers.crowdsec-grafana.tls: "true"
traefik.http.routers.crowdsec-grafana.tls.certresolver: "${TRAEFIK_CERTRESOLVER}"
traefik.http.routers.crowdsec-grafana.service: "crowdsec-grafana"
traefik.http.routers.crowdsec-grafana.middlewares: "crowdsec-bouncer@file,${TRAEFIK_AUTH_MIDDLEWARE}"
traefik.http.services.crowdsec-grafana.loadbalancer.server.port: "3000"
homepage.group: "Observabilidad"
homepage.name: "Grafana"
homepage.href: "https://${CROWDSEC_GRAFANA_DOMAIN}"
homepage.description: "Dashboards CrowdSec y monitorizacion"
homepage.icon: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/grafana.svg"
homepage.siteMonitor: "https://${CROWDSEC_GRAFANA_DOMAIN}"
networks:
crowdsec_internal:
driver: bridge
proxy:
external: true
volumes:
crowdsec_lib:
+55
View File
@@ -0,0 +1,55 @@
#!/usr/bin/env bash
# ============================================================
# crowdsec-metrics-export.sh
# Exporta métricas de CrowdSec a PostgreSQL para Grafana.
# Ejecutar cada 5 minutos via cron.
# ============================================================
set -euo pipefail
PG_DSN="postgresql://crowdsec:crowdsec_metrics_2026@crowdsec-postgres:5432/crowdsec"
# -----------------------------------------------------------
# 1. Decisiones activas (IPs bloqueadas)
# -----------------------------------------------------------
docker exec crowdsec cscli decisions list -o json 2>/dev/null | \
python3 -c "
import sys, json
data = json.load(sys.stdin)
for entry in data:
for d in entry.get('decisions', []):
ip = d.get('value','').replace(chr(39),chr(39)+chr(39))
scenario = d.get('scenario','').replace(chr(39),chr(39)+chr(39))
dtype = d.get('type','').replace(chr(39),chr(39)+chr(39))
origin = d.get('origin','').replace(chr(39),chr(39)+chr(39))
scope = d.get('scope','').replace(chr(39),chr(39)+chr(39))
dur = d.get('duration','').replace(chr(39),chr(39)+chr(39))
print(f'{ip}|{scenario}|{dtype}|{origin}|{scope}|{dur}')
" 2>/dev/null | sort -u > /tmp/cs_decisions.txt
# Limpiar tabla y re-insertar
docker exec crowdsec-postgres psql "$PG_DSN" -c "DELETE FROM decisions" 2>/dev/null
DECISION_COUNT=0
while IFS='|' read -r ip reason action origin scope dur; do
[ -z "$ip" ] && continue
docker exec crowdsec-postgres psql "$PG_DSN" -c "
INSERT INTO decisions (ip, reason, action, origin, scope, duration)
VALUES ('$ip', '$reason', '$action', '$origin', '$scope', '$dur')
" 2>/dev/null || true
DECISION_COUNT=$((DECISION_COUNT + 1))
done < /tmp/cs_decisions.txt
# -----------------------------------------------------------
# 2. Métricas agregadas
# -----------------------------------------------------------
ALERTS=$(docker exec crowdsec cscli alerts list -o json 2>/dev/null | python3 -c "import sys,json; print(len(json.load(sys.stdin)))" 2>/dev/null || echo 0)
docker exec crowdsec-postgres psql "$PG_DSN" -c "
INSERT INTO metrics (metric_name, metric_value, labels) VALUES
('active_decisions', $DECISION_COUNT, '{}'::jsonb),
('active_alerts', $ALERTS, '{}'::jsonb)
" 2>/dev/null || true
echo "$(date '+%Y-%m-%d %H:%M:%S') crowdsec-metrics: $DECISION_COUNT decisions, $ALERTS alerts"
rm -f /tmp/cs_decisions.txt
+97
View File
@@ -0,0 +1,97 @@
#!/usr/bin/env bash
set -euo pipefail
# ============================================================
# CrowdSec Setup Script
# ============================================================
# Este script prepara los directorios, genera la clave del
# bouncer y despliega la configuracion dinamica de Traefik.
# ============================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ENV_FILE="${SCRIPT_DIR}/.env"
echo "=== CrowdSec Setup ==="
# 1. Cargar .env
if [ ! -f "${ENV_FILE}" ]; then
echo "ERROR: No se encontro ${ENV_FILE}"
echo "Copia stack.env a .env y editalo:"
echo " cp ${SCRIPT_DIR}/stack.env ${ENV_FILE}"
echo " nano ${ENV_FILE}"
exit 1
fi
set -a
source "${ENV_FILE}"
set +a
# 2. Generar clave del bouncer si no esta definida
if [ "${CROWDSEC_BOUNCER_API_KEY}" = "change-me-to-a-random-64-char-hex" ] || [ -z "${CROWDSEC_BOUNCER_API_KEY}" ]; then
CROWDSEC_BOUNCER_API_KEY=$(openssl rand -hex 32)
echo "INFO: Generada nueva CROWDSEC_BOUNCER_API_KEY"
sed -i "s/^CROWDSEC_BOUNCER_API_KEY=.*/CROWDSEC_BOUNCER_API_KEY=${CROWDSEC_BOUNCER_API_KEY}/" "${ENV_FILE}"
fi
# 3. Crear directorios necesarios
sudo mkdir -p "${CROWDSEC_CONFIG_PATH}"
sudo mkdir -p "${CROWDSEC_DATA_PATH}"
sudo mkdir -p "${TRAEFIK_LOG_PATH}"
# 4. Ajustar permisos para SELinux
if command -v selinuxenabled &>/dev/null && selinuxenabled; then
sudo chcon -Rt svirt_sandbox_file_t "${CROWDSEC_CONFIG_PATH}" 2>/dev/null || true
sudo chcon -Rt svirt_sandbox_file_t "${CROWDSEC_DATA_PATH}" 2>/dev/null || true
sudo chcon -Rt svirt_sandbox_file_t "${TRAEFIK_LOG_PATH}" 2>/dev/null || true
fi
# 5. Generar Traefik dynamic config con la key del bouncer
TRAEFIK_DYNAMIC_DIR="/opt/traefik/dynamic"
TEMPLATE="${SCRIPT_DIR}/traefik-crowdsec-bouncer.yml"
OUTPUT="${TRAEFIK_DYNAMIC_DIR}/crowdsec-bouncer.yml"
if [ -f "${TEMPLATE}" ]; then
sudo mkdir -p "${TRAEFIK_DYNAMIC_DIR}"
sed "s/{{CROWDSEC_BOUNCER_API_KEY}}/${CROWDSEC_BOUNCER_API_KEY}/g" "${TEMPLATE}" | sudo tee "${OUTPUT}" > /dev/null
echo "INFO: Traefik dynamic config generado en ${OUTPUT}"
else
echo "WARN: No se encontro ${TEMPLATE}, la config dinamica de Traefik debe crearse manualmente"
fi
# 6. Generar acquis.yaml para CrowdSec (lee logs de Traefik)
ACQUIS_FILE="${CROWDSEC_CONFIG_PATH}/acquis.yaml"
if [ ! -f "${ACQUIS_FILE}" ]; then
sudo tee "${ACQUIS_FILE}" > /dev/null <<EOF
---
source: file
filenames:
- /var/log/traefik/access.log
labels:
type: traefik
EOF
echo "INFO: acquis.yaml generado en ${ACQUIS_FILE}"
fi
# 7. Generar acquis.yaml para Docker logs
ACQUIS_DOCKER="${CROWDSEC_CONFIG_PATH}/acquis-docker.yaml"
if [ ! -f "${ACQUIS_DOCKER}" ]; then
sudo tee "${ACQUIS_DOCKER}" > /dev/null <<EOF
---
source: docker
EOF
echo "INFO: acquis-docker.yaml generado para logs de contenedores"
fi
# ============================================================
echo ""
echo "=== Setup completado ==="
echo ""
echo "Siguientes pasos:"
echo " 1. (Re)desplegar Traefik para activar los plugins y access logs"
echo " 2. Desplegar CrowdSec: cd ${SCRIPT_DIR} && docker compose up -d"
echo " 3. Verificar: docker logs crowdsec"
echo " 4. El middleware se usa asi: crowdsec-bouncer@file"
echo ""
echo "Para proteger un servicio, agrega el middleware en sus labels de Traefik:"
echo ' traefik.http.routers.<svc>.middlewares: "crowdsec-bouncer@file"'
echo ""
echo "Dashboard Grafana (requiere Authentik): https://${CROWDSEC_GRAFANA_DOMAIN}"
+34
View File
@@ -0,0 +1,34 @@
##### Imágenes #####
CROWDSEC_IMAGE=crowdsecurity/crowdsec:latest
CROWDSEC_POSTGRES_IMAGE=docker.io/library/postgres:16-alpine
GRAFANA_IMAGE=grafana/grafana:latest
##### CrowdSec Engine #####
CROWDSEC_BOUNCER_API_KEY=change-me-to-a-random-64-char-hex
CROWDSEC_FW_BOUNCER_KEY=change-me-to-another-random-64-char-hex
CROWDSEC_LOG_LEVEL=info
CROWDSEC_LOCAL_API_PORT=8080
##### Base de datos PostgreSQL (métricas para Grafana) #####
CROWDSEC_POSTGRES_DB=crowdsec
CROWDSEC_POSTGRES_USER=crowdsec
CROWDSEC_POSTGRES_PASSWORD=change-me
CROWDSEC_POSTGRES_PATH=/opt/crowdsec/postgres
##### Directorios de datos #####
CROWDSEC_CONFIG_PATH=/opt/crowdsec/config
CROWDSEC_DATA_PATH=/opt/crowdsec/data
CROWDSEC_PROMETHEUS_CONFIG=/opt/crowdsec/prometheus
CROWDSEC_PROMETHEUS_PATH=/opt/crowdsec/prometheus-data
CROWDSEC_GRAFANA_PATH=/opt/crowdsec/grafana
CROWDSEC_GRAFANA_PROVISIONING=/opt/crowdsec/grafana/provisioning
CROWDSEC_GRAFANA_DASHBOARDS=/opt/crowdsec/grafana/dashboards
TRAEFIK_LOG_PATH=/opt/traefik/logs
##### Traefik / dominios #####
TRAEFIK_DOCKER_NETWORK=proxy
CROWDSEC_GRAFANA_DOMAIN=grafana.sherlockhomeless.net
TRAEFIK_ENTRYPOINT_SECURE=websecure
TRAEFIK_CERTRESOLVER=letsencrypt
TRAEFIK_AUTH_MIDDLEWARE=ths-authentik@docker
TRAEFIK_CROWDSEC_MIDDLEWARE=crowdsec-bouncer@file
+15
View File
@@ -0,0 +1,15 @@
http:
middlewares:
crowdsec-bouncer:
plugin:
crowdsec-bouncer:
enabled: true
crowdsecLapiUrl: "http://crowdsec:8080"
crowdsecLapiKey: "{{CROWDSEC_BOUNCER_API_KEY}}"
crowdsecMode: live
updateIntervalSeconds: "10"
defaultDecisionSeconds: "60"
httpTimeoutSeconds: "2"
crowdsecLapiTLSInsecureVerify: false
forwardedHeadersTrustedIPs:
- "0.0.0.0/0"