En bref
Runbook de l’infrastructure d’arleo.eu : architecture de sécurité, scripts de synchronisation, configuration OpenResty et chaîne de déploiement Hugo.
Périmètre : un NUC Intel sous Linux (host), une VM KVM pour la génération du site, Cloudflare en frontal. Prérequis pour reproduire : accès root sur l’hôte, un compte Cloudflare avec API token, un compte CrowdSec. Non couvert ici : l’installation initiale de CrowdSec et de Cloudflare (voir la documentation officielle de chaque outil) ainsi que les secrets, stockés hors dépôt.
Chaque section est autonome : les chemins de fichiers, les services systemd et les commandes de vérification sont donnés en tête de section.
1. Architecture de sécurité (10 couches)
| # | Couche | Technologie |
|---|---|---|
| 1 | DNS | DNSSEC ECDSA P256-SHA256 |
| 2 | CDN + WAF Cloud | Cloudflare WAF + DDoS + AI Crawl Control |
| 3 | Réseau | CrowdSec nftables Bouncer |
| 4 | Firewall | Netgear PR60X SPI |
| 5 | WAF local | CrowdSec AppSec + OWASP CRS 4.x |
| 6 | IDS/IPS | CrowdSec Agent + scénarios SSH/HTTP |
| 7 | HTTPS | TLS 1.3 + HSTS preload |
| 8 | DNS-TLS | DoH port 853 |
| 9 | Application | Hugo (LoveIt) + CSP à nonces + cookies Secure |
| 10 | Monitoring | BetterStack + CrowdSec poller + Vector |
Le WAF local a migré de ModSecurity vers CrowdSec AppSec sur OpenResty : voir CrowdSec AppSec + OpenResty pour la motivation et la configuration du bouncer.
2. Security Automation Go — orchestration CrowdSec ↔ Cloudflare
Dépôt : github.com/jmrGrav/security-automation-go (Apache-2.0, actif)
Des exemples de scripts téléchargeables et directement utilisables (dont l’historique Python complet) sont dans Scripts Infrastructure.
Remplace l’ancien script Python crowdsec-cf-sync.py (et ses variantes V2/V3) par un daemon Go modulaire, avec console opérateur web intégrée. La bascule Python → Go a été faite service par service, en parallèle (l’ancien service tournait encore pendant que le nouveau était validé), puis l’ancien a été désactivé une fois le comportement confirmé équivalent en production.
Comment ça marche
Le cœur du système est un pipeline en 5 étapes (internal/orchestrator/pipeline) : admission → découverte → planification → exécution → reporting. Chaque étape est un composant testable indépendamment, avec des clients typés pour Cloudflare (REST + GraphQL), CrowdSec (LAPI/cscli) et AbuseIPDB.
Points de conception notables :
- Ban automatique sous condition de corroboration : un score de confiance AbuseIPDB de 100 seul ne suffit plus à bannir — il faut en plus un signal local corroborant (rafale WAF/erreurs HTTP observée sur ce serveur). Ça évite de bannir sur la seule réputation externe d’une IP jamais vue ici.
- Registre “Trusted Networks” : source unique de vérité pour les ASN/CIDR de confiance (bots légitimes, plages connues), consommée par toutes les décisions de ban/report — plus de logique d’exception dispersée dans le script.
- Cycle de vie des bans Cloudflare : les bans expirent et sont levés automatiquement (plus de nettoyage manuel des règles CF périmées).
- État en SQLite chiffré (
runtime.db, AES-GCM), plus les fichiers JSON à plat (recidivists.json,cidr-banned.json, etc.) de l’ancienne version — un seul store, transactionnel. - Rejeu WAF Cloudflare : le daemon interroge périodiquement l’historique des événements WAF Cloudflare (GraphQL), classe chaque hit (cible protégée / signal bénin / suspect), et ne reporte/bannit que ce qui dépasse les seuils configurés.
Services actifs
| Binaire | Service systemd | Rôle | Cadence |
|---|---|---|---|
cf-sync | cf-sync.service | Daemon principal (-mode ui) + console opérateur web | Continu |
cf-allowlist-sync | cf-allowlist-sync.timer | Synchronise l’allowlist CrowdSec ↔ Cloudflare | 15 min |
cf-cleanup | cf-cleanup.service | Nettoyage des règles d’accès IP Cloudflare obsolètes | Manuel |
Console opérateur : http://127.0.0.1:9091 (loopback uniquement — jamais exposée publiquement). Tableau de bord SOC, timeline live des événements, vue “Providers” (intégrations + Trusted Networks), investigation par IP (“Focus Incident”), notes d’audit. Authentification par mot de passe (bcrypt, assistant de configuration au premier lancement) ; identifiants et secrets chiffrés en base, jamais en clair sur disque.
Métriques Prometheus : http://127.0.0.1:9092/metrics (loopback uniquement).
Vérifier
# Statut des services
systemctl status cf-sync
systemctl status cf-allowlist-sync.timer
# Logs en temps réel (journald, plus de fichier plat /var/log/crowdsec/cf-sync.log)
journalctl -fu cf-sync
journalctl -fu cf-allowlist-sync
# Diagnostic intégré (config, connectivité CrowdSec/Cloudflare, état DB)
cf-sync -mode doctor
cf-sync -mode statusAncienne version Python — historique, retirée
Service arrêté depuis le 9 juin 2026 (crowdsec-cf-sync.service, disabled/inactive). Les fichiers restent sur disque pour référence, ne pas les réactiver ni les éditer :
| Fichier | Rôle historique |
|---|---|
/usr/local/bin/crowdsec-cf-sync.py | Script principal (bans + report AbuseIPDB + escalade) — remplacé par cf-sync |
/usr/local/bin/crowdsec-cf-syncV2.py, V3.py | Itérations intermédiaires, jamais mises en prod comme service |
/usr/local/bin/cloudflare-allowlist-update.py | Sync allowlist horaire (cron) — remplacé par cf-allowlist-sync |
/usr/local/bin/cloudflare-cleanup-ip-rules.py | Nettoyage règles CF — équivalent Go : cf-cleanup |
*.bak* (une dizaine de fichiers) | Sauvegardes successives pendant le développement du script Python — purement historiques |
Les fichiers d’état JSON qu’elles produisaient (/var/log/crowdsec/recidivists.json, cidr-banned.json, modsec-banned.json, abuseipdb-reported.json) ne sont plus mis à jour ; l’état vit désormais dans runtime.db (SQLite).
3. Allowlist CrowdSec
Nom : my_allowlist
Synchronisation : cf-allowlist-sync (systemd timer, toutes les 15 min — voir section 2)
Sources : IPs BetterStack + plages Cloudflare IPv4/IPv6
Cette allowlist est la source unique de vérité : elle est poussée vers Cloudflare, jamais éditée côté Cloudflare — toute modification faite côté Cloudflare sera écrasée.
# Voir le contenu
cscli allowlists inspect my_allowlist
# Nombre d'entrées
cscli allowlists list4. OpenResty — CSP (Content Security Policy)
Fichier : /usr/local/openresty/nginx/conf/nginx.conf (map $csp_header)
La CSP publique fonctionne à nonces dynamiques depuis la v2. Le détail de la migration, et pourquoi les hashes ont été abandonnés, est dans De 46 hashes à zéro.
Fix httpoxy (avril 2026)
httpoxy est une vulnérabilité CGI/FastCGI (CVE-2016-5385 et consorts) : un en-tête Proxy: envoyé par le client est mappé par le serveur vers la variable d’environnement HTTP_PROXY, que beaucoup de bibliothèques HTTP côté application respectent ensuite comme configuration de proxy sortant — ouvrant la voie à une attaque de type man-in-the-middle. Le correctif neutralise cette variable avant qu’elle n’atteigne l’application.
Ajouté dans /usr/local/openresty/nginx/conf/fastcgi.conf :
fastcgi_param HTTP_PROXY "";5. Hugo CMS
Racine : /home/jm/hugo-site/ (VM KVM 192.168.122.69)
Thème : LoveIt (submodule git)
Build : hugo --minify via deploy.sh
Déploiement : rsync vers /var/www/hugo/ sur le host NUC
Fichiers importants
| Fichier | Rôle |
|---|---|
hugo.toml | Config Hugo (baseURL, langues, thème) |
content/<route>/index.{fr,en}.md | Pages bilingues |
themes/LoveIt/ | Thème |
static/ | Assets statiques |
deploy.sh | Build + rsync + purge Cloudflare |
Publier une modification
# 1. Vérifier le rendu en local
cd ~/hugo-site && hugo server -D --bind 0.0.0.0
# 2. Build de contrôle
hugo --minify
# 3. Déploiement complet (build + rsync + purge CF)
~/deploy.sh6. Cloudflare
Règles de sécurité (l’ordre compte)
| # | Nom | Action |
|---|---|---|
| 1 | GOOD BITS | Ignorer (allowlist) |
| 2 | BLOCK ALL BAD | Bloquer |
| 3 | AI Crawl Control | Bloquer (géré automatiquement) |
| 4 | Filter NON EU/US | Challenge |
GOOD BITS — conditions
- BetterStack Uptime Bot
- Catégories de bots légitimes (Search Engine, Monitoring, Security…)
- CloudflareBrowserRenderingCrawler
ip.src in $allowed_ip
AI Crawl Control — bots bloqués
GPTBot, ClaudeBot, Bytespider, CCBot, ChatGPT-User, FacebookBot, Meta-ExternalAgent, Perplexity, MistralAI, OAI-SearchBot, AmazonBot…
Deux exceptions à connaître : BingBot passe par GOOD BITS (catégorie Search Engine Crawler), et DeepSeekBot est bloqué en amont par Filter NON EU/US puisqu’il est hébergé en Chine.
Règles d’accès IP (dynamiques)
Gérées automatiquement par cf-sync (voir section 2), à ne pas éditer à la main :
crowdsec-local-ban— bans CrowdSec locauxmodsec-ban— bans WAF de 2 hcrowdsec-cidr-ban— blocs /24
7. Monitoring & alertes
Status page : status.arleo.eu
Badge footer : https://status.arleo.eu/fr/badge
Token Cloudflare Analytics : /etc/secrets/ (non publié)
8. Certificats SSL
Renouvellement automatique géré par Cloudflare. Note Qualys SSL Labs : A+.
9. Points de vigilance
- Le script inline du challenge Cloudflare (
__CF$cv$params) change à chaque requête : l’erreur CSP qui en résulte est inévitable et non bloquante. - La allowlist CrowdSec (
my_allowlist) est la source unique de vérité, synchronisée vers Cloudflare. Toute modification faite côté Cloudflare sera écrasée. - Les bans
cscli(escalade récidivistes) n’apparaissent pas dans l’API REST CrowdSec avec?limit=1000: utilisercscli decisions list --origin cscli. - Le token Cloudflare et les credentials AbuseIPDB sont stockés hors dépôt dans
/etc/secrets/, enchmod 600.