Documentation Infrastructure

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)

#CoucheTechnologie
1DNSDNSSEC ECDSA P256-SHA256
2CDN + WAF CloudCloudflare WAF + DDoS + AI Crawl Control
3RéseauCrowdSec nftables Bouncer
4FirewallNetgear PR60X SPI
5WAF localCrowdSec AppSec + OWASP CRS 4.x
6IDS/IPSCrowdSec Agent + scénarios SSH/HTTP
7HTTPSTLS 1.3 + HSTS preload
8DNS-TLSDoH port 853
9ApplicationHugo (LoveIt) + CSP à nonces + cookies Secure
10MonitoringBetterStack + 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

BinaireService systemdRôleCadence
cf-synccf-sync.serviceDaemon principal (-mode ui) + console opérateur webContinu
cf-allowlist-synccf-allowlist-sync.timerSynchronise l’allowlist CrowdSec ↔ Cloudflare15 min
cf-cleanupcf-cleanup.serviceNettoyage des règles d’accès IP Cloudflare obsolètesManuel

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 status

Ancienne 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 :

FichierRôle historique
/usr/local/bin/crowdsec-cf-sync.pyScript principal (bans + report AbuseIPDB + escalade) — remplacé par cf-sync
/usr/local/bin/crowdsec-cf-syncV2.py, V3.pyItérations intermédiaires, jamais mises en prod comme service
/usr/local/bin/cloudflare-allowlist-update.pySync allowlist horaire (cron) — remplacé par cf-allowlist-sync
/usr/local/bin/cloudflare-cleanup-ip-rules.pyNettoyage 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 list

4. 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

FichierRôle
hugo.tomlConfig Hugo (baseURL, langues, thème)
content/<route>/index.{fr,en}.mdPages bilingues
themes/LoveIt/Thème
static/Assets statiques
deploy.shBuild + 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.sh

6. Cloudflare

Règles de sécurité (l’ordre compte)

#NomAction
1GOOD BITSIgnorer (allowlist)
2BLOCK ALL BADBloquer
3AI Crawl ControlBloquer (géré automatiquement)
4Filter NON EU/USChallenge

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 locaux
  • modsec-ban — bans WAF de 2 h
  • crowdsec-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 : utiliser cscli decisions list --origin cscli.
  • Le token Cloudflare et les credentials AbuseIPDB sont stockés hors dépôt dans /etc/secrets/, en chmod 600.