--- title: 'Security Stack : Caddy + CrowdSec' url: https://blog.guigpap.com/fr/infrastructure/security-stack/ url_md: https://blog.guigpap.com/fr/infrastructure/security-stack.md category: infrastructure date: '2026-01-20' maturite: production techno: - caddy - crowdsec - docker application: - infrastructure - operations --- # Security Stack : Caddy + CrowdSec > Reverse proxy avec TLS automatique et WAF collaboratif pour protéger l'infrastructure ## 1. Quoi ? — Définition et contexte La **Security Stack** est la couche de protection qui se place entre Internet et tous les services de l'infrastructure. Elle combine deux composants : | Composant | Rôle | Port | |-----------|------|------| | **Caddy** | Reverse proxy avec TLS automatique et HTTP/3 | 80, 443, 443/udp | | **CrowdSec** | WAF comportemental et threat intelligence collaborative | 8080 (LAPI), 7422 (AppSec) | | **Trivy** | Scanner CVE pour images Docker, appelé via `docker exec` | aucun port exposé | > **Note - Reverse proxy** > > Un **reverse proxy** est un serveur qui reçoit tout le trafic externe et le redistribue aux services internes. Cela permet d'avoir un seul point d'entrée sécurisé avec TLS, et d'exposer plusieurs services sur le même serveur. ### Architecture visuelle ```mermaid flowchart TD Internet([Internet]) subgraph Caddy["Caddy · security-stack"] direction TB C1["TLS termination · Let's Encrypt, auto-renew"] C2["HTTP/3 QUIC support"] C3["Security headers · HSTS, CSP, X-Frame-Options"] C4["CrowdSec bouncer module"] end subgraph CS["CrowdSec Engine"] direction TB S1["Parse Caddy access logs"] S2["Detect attacks · scenarios"] S3["Apply decisions · ban, captcha"] S4["Share threat intel · CAPI"] end subgraph Internal["Services internes · webproxy"] direction LR N["N8N"] G["Grafana"] O["Odoo"] NT["ntfy"] B["Blog"] end Internet -->|"ports 80, 443, 443/udp"| Caddy Caddy --> CS CS --> Internal ``` --- ## 2. Pourquoi ? — Enjeux et motivations ### Pourquoi Caddy plutôt que Nginx ou Traefik ? | Critère | Caddy | Nginx | Traefik | |---------|-------|-------|---------| | **TLS automatique** | Natif (Let's Encrypt) | Certbot externe | Natif | | **HTTP/3 QUIC** | Natif | Module expérimental | Non | | **Configuration** | Caddyfile simple | nginx.conf verbeux | YAML/Labels | | **Rechargement** | Sans interruption | Reload graceful | Sans interruption | | **CrowdSec** | Module bouncer officiel | Module lua | Plugin tiers | **Critères de décision :** 1. **Simplicité** — La syntaxe Caddyfile est lisible et concise 2. **TLS zéro-config** — Certificats Let's Encrypt automatiques, pas de cron Certbot 3. **Intégration CrowdSec native** — Module bouncer officiel, pas de workaround ### Qu'est-ce que CrowdSec apporte ? > **Tip - WAF comportemental** > > Un **WAF** (Web Application Firewall) analyse les requêtes HTTP pour détecter et bloquer les attaques : injections SQL, XSS, scans de vulnérabilités, brute force... CrowdSec est "comportemental" : il apprend des patterns d'attaque. | Fonction | Bénéfice | |----------|----------| | **Threat intelligence collaborative** | Les IPs malveillantes détectées par d'autres instances CrowdSec sont partagées | | **Scénarios de détection** | Règles prêtes pour HTTP, SSH, CVE connus | | **Bouncer temps réel** | Les IPs bannies sont bloquées avant d'atteindre le backend | | **Whitelists automatiques** | Bots légitimes (Googlebot, etc.) ne sont pas bloqués | --- ## 3. Comment ? — Mise en œuvre technique ### Configuration Caddyfile ```caddyfile { # Options globales email admin@guigpap.com # Module CrowdSec crowdsec { api_url http://crowdsec:8080 api_key {env.CROWDSEC_API_KEY} ticker_interval 15s } } # Redirection www → apex www.guigpap.com { redir https://guigpap.com{uri} permanent } # Site principal guigpap.com { crowdsec header { Strict-Transport-Security "max-age=31536000; includeSubDomains" X-Content-Type-Options "nosniff" X-Frame-Options "DENY" Referrer-Policy "strict-origin-when-cross-origin" } reverse_proxy blog:4321 } # N8N avec webhooks protégés n8n.guigpap.com { crowdsec # Bloquer les webhooks internes depuis l'extérieur @blocked_webhooks { path /webhook/notify/* path /webhook/prometheus/* path /webhook/claude/* path /webhook/claude-simple-chat path /webhook/docker-controller path /webhook/odoo/* } respond @blocked_webhooks 403 reverse_proxy n8n:5678 } # Autres services grafana.guigpap.com { crowdsec reverse_proxy grafana:3000 } odoo.guigpap.com { crowdsec reverse_proxy odoo:8069 } ``` ### Collections CrowdSec installées ```bash # Lister les collections actives docker exec crowdsec cscli collections list ``` | Collection | Rôle | |------------|------| | `crowdsecurity/caddy` | Parser des logs Caddy | | `crowdsecurity/base-http-scenarios` | Détection attaques HTTP courantes | | `crowdsecurity/http-cve` | Exploits CVE connus | | `crowdsecurity/appsec-virtual-patching` | Patching virtuel | | `crowdsecurity/linux` | Scénarios système Linux | | `crowdsecurity/sshd` | Détection brute force SSH | | `crowdsecurity/whitelist-good-actors` | Liste blanche bots légitimes | ### Comment fonctionne le bouncer 1. Caddy reçoit une requête 2. Le module bouncer interroge l'API CrowdSec (cache local) 3. Si l'IP est dans la liste de décisions → 403 Forbidden 4. Sinon → requête transmise au backend > **Tip - Latence** > > Le bouncer met en cache les décisions localement. L'interrogation de l'API CrowdSec n'ajoute que quelques millisecondes, uniquement pour les nouvelles IPs. ### Protection des webhooks internes | Webhook | Appelé par | Bloqué depuis Internet | |---------|------------|------------------------| | `/webhook/notify/*` | DIUN | Oui | | `/webhook/prometheus/*` | Alertmanager | Oui | | `/webhook/claude/*` | CLI Ollama | Oui | | `/webhook/claude-simple-chat`, `/webhook/claude-approve`, `/webhook/claude-reject` | N8N | Oui | | `/webhook/odoo/*` | Odoo internal | Oui | | `/webhook/docker-controller` | Telegram Orchestrator | Oui | | `/webhook/notification-hub` | N8N (Execute Workflow) | Oui | Les services internes appellent ces webhooks via `http://n8n:5678` (réseau Docker), pas via le domaine public. ### Rate limiting et AppSec WAF Les routes webhooks de `n8n.guigpap.com` sont rate-limitées à **30 requêtes/minute par IP** via le module `caddy-ratelimit`. Cela complète l'auth (Bearer / Header / HMAC) en empêchant le brute-force sur les endpoints exposés. CrowdSec AppSec écoute en parallèle sur le port 7422 et rejoue les règles `crowdsecurity/appsec-virtual-patching` + `appsec-generic-rules` avant que les requêtes atteignent les backends. ### Crawlers IA et `llms.txt` Le blog (`blog.guigpap.com`) sert volontairement deux artefacts pour les crawlers IA (ChatGPT, Gemini, Perplexity, etc.) : 1. **`/llms.txt`** et **`/llms-full.txt`** — index machine-readable des articles du blog, déclarés via header `Link` : ```caddyfile header Link "; rel=\"llms-txt\", ; rel=\"llms-full-txt\"" header X-Llms-Txt "/llms.txt" ``` 2. **Mirrors Markdown** — chaque article a un jumeau `.md` qui rend la version source. Caddy force `Content-Type: text/plain` au lieu de `text/markdown` parce que les parseurs des crawlers grand public laissent passer plain text mais ignorent souvent `text/markdown` : ```caddyfile @markdown_mirror path_regexp md_ext \.md$ header @markdown_mirror { Content-Type "text/plain; charset=utf-8" defer } ``` La CSP du blog autorise `'wasm-unsafe-eval'` (pour Pagefind, le moteur de recherche statique) et les `data:` URIs côté `font-src` (pour les sous-ensembles fontsource embarqués) : ```caddyfile header Content-Security-Policy "default-src 'self'; \ script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval'; \ style-src 'self' 'unsafe-inline'; \ img-src 'self' data:; \ font-src 'self' data:" ``` ### CrowdSec : image Debian + détection SSH L'engine CrowdSec utilise l'image `latest-debian` (et non Alpine) car le VPS Debian 13 expose ses logs SSH uniquement via journald — il n'y a pas de `/var/log/auth.log`. L'image Debian inclut `journalctl` ; l'image Alpine ne l'a pas. Le container monte `/var/log/journal`, `/run/log/journal` et `/etc/machine-id` du host. | Layer | Rôle | Application | |-------|------|-------------| | CrowdSec + Caddy bouncer | HTTP/HTTPS | Block via Caddy (403) | | CrowdSec SSH detection | Brute-force SSH | Détection seule (pas de bouncer host) | | **fail2ban** | Enforcement SSH | Ban via nftables (`inet f2b-table`) | | UFW | Firewall baseline | Ports 22, 80, 443 uniquement | > **Caution - SSH non bloqué par CrowdSec** > > CrowdSec détecte les scenarios SSH brute-force (incl. CVE-2024-6387) mais ne peut pas bloquer au niveau réseau — il n'y a pas de bouncer firewall sur le host. **fail2ban** est le seul service qui bloque réellement les attaquants SSH sur ce VPS. ### Commandes d'exploitation ```bash # Gestion CrowdSec docker exec crowdsec cscli decisions list # IPs bannies docker exec crowdsec cscli decisions delete --ip 1.2.3.4 # Débannir docker exec crowdsec cscli alerts list # Alertes récentes docker exec crowdsec cscli metrics # Statistiques # Gestion Caddy docker exec caddy caddy validate --config /etc/caddy/Caddyfile docker exec caddy caddy reload --config /etc/caddy/Caddyfile docker logs caddy --tail 100 # Trivy : scan d'une image docker exec trivy trivy image --format json \ --severity CRITICAL,HIGH --ignore-unfixed postgres:17 ``` --- ## 4. Et si ? — Perspectives et limites ### Retour d'expérience actuel > **Note - Déploiement récent** > > CrowdSec est installé récemment sur cette infrastructure. Le recul n'est pas encore suffisant pour quantifier les attaques bloquées ou identifier des patterns de faux positifs. ### Limites actuelles | Limite | Impact | Mitigation | |--------|--------|------------| | **Pas de HA Caddy** | Single point of failure | Monitoring + restart auto | | **CrowdSec non testé en charge** | Comportement inconnu sous attaque DDoS | Rate limiting amont possible | | **Faux positifs possibles** | IPs légitimes potentiellement bloquées | Whitelist manuelle à prévoir | ### Scénarios d'évolution **Si des faux positifs apparaissent** : - Créer des whitelists par IP ou par User-Agent - Ajuster les seuils des scénarios (`cscli scenarios inspect`) - Désactiver des scénarios trop agressifs **Si la charge augmente** : - Activer le mode streaming Caddy pour les gros fichiers - Ajouter un cache (Varnish) devant certains services - Considérer un CDN (Cloudflare) pour le contenu statique **Si une attaque DDoS survient** : - CrowdSec détecte mais ne suffit pas seul - Activer le mode Under Attack de Cloudflare - Contacter l'hébergeur pour mitigation amont ### Métriques à surveiller | Métrique | Source | Seuil d'attention | |----------|--------|-------------------| | IPs bannies/jour | `cscli decisions list` | > 100 = vérifier les scénarios | | Alertes HTTP | `cscli alerts list` | Spike soudain = attaque potentielle | | 403 responses | Logs Caddy | Taux anormal = faux positifs possibles | --- ## Pages liées ### Infrastructure - [Architecture VPS](/fr/infrastructure/architecture-vps/) — Vue d'ensemble - [Monitoring Stack](/fr/infrastructure/monitoring-stack/) — Métriques Caddy ### Référence - [Glossaire](/fr/reference/glossary/) — WAF, Bouncer, TLS, Reverse Proxy ## Metadonnees agent - Cet article est issu du blog GuiGPaP Lab. - Contexte global du blog: https://blog.guigpap.com/llms.txt - Contact auteur: https://odoo.guigpap.com/mon-cv - Licence: CC-BY-SA 4.0