---
title: "Pipeline de logs CrowdSec avec Vector : filtrer le bruit et capturer les vrais bans"
description: "Reconfigurer le pipeline Vector pour CrowdSec : filtrer le bruit CAPI/lists et capturer les bans effectifs du bouncer nginx-lua dans BetterStack."
url: "https://www.arleo.eu/posts/crowdsec-vector-pipeline/"
language: "fr"
datePublished: "2026-04-15T19:01:00+02:00"
dateModified: "2026-06-28T20:47:43Z"
tags: ["infrastructure","crowdsec","homelab","réseau","vector","monitoring"]
categories: ["infrastructure"]
contentSignal: "ai-train=no, search=yes, ai-input=yes"
---

# Pipeline de logs CrowdSec avec Vector : filtrer le bruit et capturer les vrais bans
Reconfigurer le pipeline Vector pour CrowdSec : filtrer le bruit CAPI/lists et capturer les bans effectifs du bouncer nginx-lua dans BetterStack.



## ⚡ En bref

Le pipeline Vector initial inondait BetterStack de ~500 events/24h, dont 434 pulls CAPI sans valeur de supervision locale. Ce travail reconfigure le filtre Vector pour ne garder que les bans à haute valeur (`cscli`) et corrige un angle mort majeur : les bans effectifs du bouncer nginx-lua n'apparaissaient nulle part dans BetterStack.

## 🧠 Pourquoi

La stack de sécurité repose sur trois composants qui travaillent ensemble :

- **nginx** avec le bouncer lua CrowdSec (`lua-resty-crowdsec`) qui bloque les requêtes en temps réel
- **CrowdSec** pour la détection et la gestion des décisions de ban
- **Vector** qui centralise les logs vers BetterStack pour la supervision

Après avoir mis en place le pipeline initial, deux problèmes sont apparus rapidement. D'abord, le signal était noyé dans le bruit : sur 500 events/24h, 434 venaient du pull CAPI communautaire horaire et 66 des listes tierces — ni les uns ni les autres ne représentent une menace détectée sur *cette* infrastructure. Ensuite, les bans effectifs du bouncer lua (blocages en temps réel dans nginx) n'apparaissaient nulle part dans BetterStack, ce qui créait un angle mort sur l'activité de sécurité réelle.

## 🔧 Ce qui a été fait

### Problème 1 : le bruit CAPI et les listes tierces

Sur une période de 24h, la répartition des events CrowdSec dans BetterStack était :

| Origin | Nombre | Nature |
|---|---|---|
| `CAPI` | 434 | Pull communautaire toutes les heures |
| `lists` | 66 | Listes tierces (firehol_greensnow, otx-webscanners…) |
| `cscli` | 0 | Bans locaux manuels — **jamais vus** |

Les events `CAPI` et `lists` arrivent en rafales toutes les heures pile (à `:09` de chaque heure) correspondant au cycle de synchronisation des listes communautaires. **Solution :** modifier le filtre Vector pour ne garder que `origin == "cscli"` :

```yaml
# Dans vector.yaml
crowdsec_decisions_filter:
  type: "filter"
  inputs:
    - "crowdsec_decisions_flatten"
  condition: |
    exists(.cs) && .cs.origin == "cscli"
```

### Problème 2 : les bans effectifs du bouncer lua invisibles

Le bouncer nginx-lua bloque les IPs en temps réel, mais ces blocages effectifs n'apparaissaient nulle part dans BetterStack. Pourtant, ils sont loggés par nginx dans `/var/log/nginx/error.log` :

```
2026/04/15 03:36:37 [alert] 67913#67913: *3949 [lua] crowdsec.lua:783: Allow(): \
  [Crowdsec] denied '43.130.106.18' with 'ban' (by bouncer), \
  client: 43.130.106.18, server: www.arleo.eu, \
  request: "GET / HTTP/2.0", host: "www.arleo.eu"
```

Le problème venait du filtre nginx existant dans Vector, qui droppait silencieusement tout message contenant le mot `crowdsec`. Puisque `error.log` est déjà inclus dans la source nginx, il ne faut pas créer une nouvelle source — il faut intercaler un transform **avant** le filtre pour tagger et rediriger ces events.

### Architecture du nouveau pipeline

```
     ┌────────────────────────┐
     │    nginx error.log      │
     └──────────┬──────────────┘
                │
                ▼
     ┌─────────────────────────────────┐
     │   better_stack_nginx_parser      │
     │   (parse tous les logs nginx)    │
     └──────────┬──────────────────────┘
                │
                ▼
     ┌─────────────────────────────────┐
     │  crowdsec_nginx_ban_extractor    │
     │  (détecte [Crowdsec] denied)     │
     │  tague cs_nginx_ban = true/false │
     └────┬─────────────────┬───────────┘
          │                 │
   cs_nginx_ban==true  cs_nginx_ban==false
          │                 │
          ▼                 ▼
  crowdsec_nginx_   better_stack_
  ban_filter        nginx_filter
          │                 │
          ▼                 ▼
  Sink CrowdSec     Sink nginx
  BetterStack       BetterStack
```

### Le transform extractor

```yaml
crowdsec_nginx_ban_extractor:
  type: "remap"
  inputs:
    - "better_stack_nginx_parser_XXXXX"
  source: |
    msg = string(.message) ?? ""
    if contains(msg, "[Crowdsec] denied") && contains(msg, "with 'ban'") {
      m = parse_regex(msg, r'\[Crowdsec\] denied \'(?P<banned_ip>[^\']+)\' with \'ban\'') ?? {}
      ip   = string(m.banned_ip) ?? "?"
      req  = string(.nginx.request) ?? "-"
      host = string(.nginx.host) ?? string(.nginx.server) ?? "-"
      .cs_nginx_ban = true
      .cs_banned_ip = ip
      .cs_origin    = "nginx-bouncer"
      .platform     = "CrowdSec"
      .message      = "Ban " + ip + " | " + req + " | " + host
      del(.file)
      del(.level)
      del(.nginx.cid)
      del(.nginx.pid)
      del(.nginx.tid)
    } else {
      .cs_nginx_ban = false
    }
```

Le champ `.message` est construit pour être immédiatement lisible dans le tail BetterStack :

```
Ban 43.130.106.18 | GET / HTTP/2.0 | www.arleo.eu
```

### Modification du filtre nginx existant

Ajout de la condition d'exclusion des bans déjà reroutés :

```yaml
better_stack_nginx_filter_XXXXX:
  type: "filter"
  inputs:
    - "crowdsec_nginx_ban_extractor"   # ← pointe sur le nouveau transform
  condition: |
    !contains(string(.message) ?? "", "crowdsec") &&
    !contains(string(.message) ?? "", "Initialisation done") &&
    !contains(string(.message) ?? "", "APPSEC is enabled") &&
    !((.nginx.status == 499) && contains(string(.nginx.path) ?? "", "empty.php")) &&
    !contains(string(.message) ?? "", "lua tcp socket read timed out") &&
    !(.cs_nginx_ban == true)           # ← exclusion des bans reroutés
```

### Sink CrowdSec unifié

Les deux flux (bans `cscli` et bans lua) convergent vers le même sink :

```yaml
crowdsec_betterstack_sink:
  type: "http"
  inputs:
    - "crowdsec_decisions_filter"   # bans cscli
    - "crowdsec_nginx_ban_filter"   # bans lua nginx
```

### Bonus : token Cloudflare bloqué par son propre NUC

En marge du travail sur Vector, le script `crowdsec-cf-sync.py` était en échec silencieux depuis plusieurs jours avec des erreurs `HTTP 401 Authentication error`. La cause : le token Cloudflare avait une restriction IP `not_in` qui incluait explicitement l'IP WAN du serveur lui-même. Toutes les requêtes API émises depuis le NUC étaient rejetées par Cloudflare.

**Correction :** retirer l'IP du serveur de la liste `not_in` du token via l'API Cloudflare. Le script a immédiatement repris sa synchronisation normale (13 bans actifs re-synchronisés).

## 🏁 Conclusion

Le rapport signal/bruit est passé de ~500 events/24h majoritairement inutiles à uniquement les événements qui méritent attention : bans `cscli` (décisions manuelles ou scénarios locaux) et bans nginx-bouncer (blocages effectifs en temps réel avec IP, requête et vhost). Le pipeline donne enfin une vision fidèle de l'activité de sécurité réelle du serveur.

**Pour aller plus loin :**

- 💡 Ajouter un compteur de bans nginx-bouncer dans un dashboard BetterStack pour visualiser les pics de blocage en temps réel
- 💡 Étendre le filtre `cscli` pour inclure aussi les bans issus de scénarios personnalisés locaux (`origin == "crowdsec"` avec scope IP)


## Tags

- infrastructure
- crowdsec
- homelab
- réseau
- vector
- monitoring

## Categories

- infrastructure
