Tuxvador's Blog
Présentation de WorkItOut — Trouve ton partenaire de sport Rapports quotidiens automatisés CrowdSec par e-mail Gestion programmatique du DNS avec l'API IONOS Premiers pas avec Proxmox VE pour votre laboratoire maison Mon Homelab Proxmox VE : Aperçu de l'infrastructure
EN

Rapports quotidiens automatisés CrowdSec par e-mail


Si vous exécutez CrowdSec dans votre homelab, vous vérifiez probablement le tableau de bord de temps en temps pour voir ce qui est bloqué. Mais la vraie valeur réside dans la surveillance passive — recevoir un résumé quotidien poussé vers vous sans avoir à penser à vérifier.

Ce guide explique comment mettre en place un rapport CrowdSec automatisé livré par e-mail chaque matin. L’approche utilise :

  • cscli pour l’extraction des données (alertes, décisions, métriques)
  • Un script Python pour formater le tout en un e-mail HTML responsive
  • Postfix relayé via le SMTP Gmail pour une livraison fiable
  • Une crontab root pour la planification quotidienne

Contenu du Rapport

Le rapport est un e-mail HTML avec un thème sombre (Catppuccin Mocha) qui s’affiche correctement sur mobile. Il comprend :

  • État du système — utilisation disque, RAM, uptime
  • Alertes du jour — regroupées par scénario avec compteurs, plus les principaux pays attaquants avec des émojis drapeaux
  • Meilleurs scénarios de tous les temps — les 25 détections les plus déclenchées depuis le démarrage de CrowdSec
  • Décisions actives — total des bannis avec répartition CAPI (liste de blocage communautaire) vs moteur local
  • Métriques par bouncer — octets et paquets rejetés par chaque bouncer (ct-102-nginx, proxmox-firewall)
  • Déclenchements de scénarios — overflows, instances, événements versés par scénario
  • Activité des parseurs — quels parseurs ont traité quel volume
  • Sources de logs — ce qui est ingéré avec les statistiques de la liste blanche

Prérequis

  • CrowdSec installé et en cours d’exécution avec LAPI activé
  • Postfix installé (apt install postfix)
  • Compte Gmail avec un mot de passe d’application généré (pas votre mot de passe habituel)

Étape 1 : Configurer Postfix pour le Relais SMTP Gmail

Postfix doit relayer les courriels sortants via le serveur SMTP de Gmail puisque votre IP de homelab est privée (RFC1918) et ne peut pas livrer directement.

# Installer les modules SASL pour l'authentification
apt install -y libsasl2-modules

# Configurer Postfix
postconf -e 'relayhost = [smtp.gmail.com]:587'
postconf -e 'smtp_tls_security_level = encrypt'
postconf -e 'smtp_sasl_auth_enable = yes'
postconf -e 'smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd'
postconf -e 'smtp_sasl_security_options = noanonymous'
postconf -e 'inet_protocols = ipv4'

# Stocker les identifiants
echo '[smtp.gmail.com]:587 your.email@gmail.com:your_app_password' > /etc/postfix/sasl_passwd
chmod 600 /etc/postfix/sasl_passwd
postmap /etc/postfix/sasl_passwd

# Redémarrer
systemctl restart postfix

Détails importants :

  • inet_protocols = ipv4 est nécessaire si votre conteneur n’a qu’une IPv6 de lien local. Sans cela, Postfix essaie d’abord l’IPv6 et obtient « Network is unreachable ».
  • Les identifiants sont stockés dans une base de données hachée (sasl_passwd.db), pas en texte clair.
  • Postfix écoute uniquement sur localhost (inet_interfaces = loopback-only), donc seuls les scripts locaux peuvent envoyer des courriels.

Étape 2 : Le Script de Rapport

Le script de rapport est un script Python qui appelle cscli pour rassembler les données et construit un e-mail HTML avec les deux parties MIME text/plain et text/html (pour les clients de messagerie qui ne rendent pas le HTML).

Logique Principale

import subprocess, json, datetime
from collections import Counter

# Récupérer les alertes du jour depuis LAPI
raw = subprocess.run(
    ["cscli", "alerts", "list", "-a", "--json"],
    capture_output=True, text=True, timeout=30
)
data = json.loads(raw.stdout)
today_alerts = [a for a in data
    if a.get("created_at", "").startswith(datetime.date.today().isoformat())]

# Regrouper par scénario
reasons = Counter(a.get("scenario", "unknown") for a in today_alerts)

Le script construit ensuite un tableau HTML des alertes regroupées par scénario, une répartition par pays et un tableau de métriques analysé à partir de la sortie de cscli metrics.

Envoi en tant qu’E-mail HTML

La commande standard sendmail ne gère que le texte brut. Pour le HTML, utilisez le module email de Python :

import email.mime.multipart, email.mime.text

msg = email.mime.multipart.MIMEMultipart("alternative")
msg["Subject"] = f"CrowdSec Daily Report — {hostname}{date}"
msg["From"] = f"CrowdSec Reporter <root@tuxvador.fr>"
msg["To"] = "you@email.com"

part1 = email.mime.text.MIMEText(plain_text, "plain")
part2 = email.mime.text.MIMEText(html_content, "html")
msg.attach(part1)
msg.attach(part2)

p = subprocess.Popen(["/usr/sbin/sendmail", "-t", "-f", SENDER],
                     stdin=subprocess.PIPE)
p.communicate(msg.as_bytes())

Le type multipart alternative indique aux clients de messagerie de préférer le HTML, mais de revenir au texte brut si le HTML n’est pas pris en charge.

Étape 3 : Planifier l’Exécution

Ajoutez une entrée crontab pour root (car cscli nécessite l’accès au socket LAPI) :

echo '0 8 * * * /usr/local/bin/crowdsec-report.py > /dev/null 2>&1' | crontab -

Ceci s’exécute quotidiennement à 08h00. L’e-mail arrive dans votre boîte de réception en quelques secondes.

À quoi Ressemble un Rapport Exemple

L’e-mail est stylisé avec un thème sombre Catppuccin Mocha :

Subject: CrowdSec Daily Report — SECURITY — 2026-05-25

┌────────────────────────────────────────────────────────┐
│  🛡️ CrowdSec Security Report                         │
│  SECURITY — 2026-05-25                                │
├────────────────────────────────────────────────────────┤
│  🚨 Today: 24 alerts                                  │
│  http-probing          8    http-bad-user-agent   5    │
│  http-sensitive-files  3    http-wordpress-scan    3    │
│  ...                                                  │
│  🇺🇸 US 5  🇨🇳 CN 3  🇷🇺 RU 2  🇳🇱 NL 2              │
│                                                        │
│  🔨 Active decisions: 29,951 (29,931 bans)            │
│  CAPI: 29,930  Local engine: 21                       │
│                                                        │
│  🛡️ ct-102-nginx: 28K dropped │ proxmox-firewall: 3  │
│  ⚡ Overflows: http-probing(19) http-bad-user(22) ... │
└────────────────────────────────────────────────────────┘

Sur mobile (application Gmail), il s’affiche comme une mise en page propre en forme de carte avec le fond sombre.

Dépannage

Échec de l’authentification SASL

# Installer les modules manquants
apt install libsasl2-modules

# Vérifier que sasl_passwd est correctement haché
postmap -q '[smtp.gmail.com]:587' hash:/etc/postfix/sasl_passwd

Délai de connexion expiré sur le port 587

Si votre conteneur n’a pas de connectivité IPv6 :

postconf -e 'inet_protocols = ipv4'
systemctl restart postfix

Courriels bloqués dans la file d’attente

# Vérifier la file d'attente
mailq

# Vider la file
postqueue -f

# Supprimer les messages bloqués
postsuper -d ALL

# Vérifier les logs
journalctl -u postfix -n 20 --no-pager

Configuration de l’Acquisition de Logs

CrowdSec n’analyse que les logs que vous lui dites de lire. La configuration d’acquisition dans /etc/crowdsec/acquis.d/ définit les fichiers et leur type de log.

Chaque type de log correspond à une chaîne de parseurs. Les sources syslog standard (SSH, Postfix, logs système) utilisent type: syslog qui passe par syslog-logs → distribue aux parseurs enfants en fonction du nom du programme (ex. sshd ou postfix/smtpd).

Certains services utilisent des formats de log non standard. Par exemple, les logs du démon Proxmox PVE utilisent des horodatages RFC3339 (2025-12-23T04:07:45+01:00) au lieu du format syslog traditionnel. Ceux-ci nécessitent une étiquette type séparée :

---
# Sources syslog standard
filenames:
  - /var/log/remote/proxmox/postfix.log
  - /var/log/syslog
labels:
  type: syslog
---
# Source non syslog (horodatages RFC3339)
filenames:
  - /var/log/remote/proxmox/pvedaemon.log
  - /var/log/remote/proxmox/pveproxy.log
labels:
  type: pvedaemon

Avec type: pvedaemon, le log contourne syslog-logs et passe par le gestionnaire non-syslog, qui définit evt.Parsed.program = "pvedaemon" directement à partir de l’étiquette. Le parseur fulljackz/proxmox-logs peut alors effectuer la correspondance.

Pour vérifier que vos parseurs sont actifs :

cscli metrics | grep -A20 'Acquisition'

Vous devriez voir toutes vos sources de logs avec « Lines parsed » > 0 pour celles qui ont des parseurs actifs.

Aller Plus Loin

  • Personnalisez le rapport — ajoutez des métriques de pare-feu, des statuts d’alertes Grafana ou des dates d’expiration de certificats
  • Destinataires multiples — ajoutez plus d’adresses à la liste des destinataires du script
  • Digeste hebdomadaire — au lieu de quotidien, changez la crontab en 0 8 * * 1 (lundi seulement)
  • Template HTML — injectez des captures d’écran du tableau de bord Grafana via des captures d’écran de navigateur

Le script complet est disponible sur le conteneur SECURITY à /usr/local/bin/crowdsec-report.py.