Aller au contenu principal

Traefik - Reverse Proxy moderne

Traefik est le reverse proxy au cœur de l'infrastructure Docker du homelab. Il gère le routage de toutes les requêtes HTTP/HTTPS vers les conteneurs appropriés, avec gestion automatique des certificats SSL et intégration de la sécurité via CrowdSec.

Architecture globale

L'infrastructure utilise deux instances Traefik distinctes, chacune sur sa propre interface réseau :

  • traefik-public (192.168.1.2) : Services publics accessibles sur Internet (*.tellserv.fr)
  • traefik-private (192.168.1.3) : Services locaux réservés au réseau interne (*.local.tellserv.fr)

Cette séparation permet :

  • Isolation réseau : Les services privés ne sont jamais exposés publiquement
  • Sécurité renforcée : Politiques de sécurité différenciées selon l'exposition
  • Gestion simplifiée : Chaque instance a sa propre configuration et ses propres règles

Prérequis réseau

La VM hébergeant Traefik dispose de deux NICs (Network Interface Cards) :

  • NIC 1 (192.168.1.2) : Interface publique pour traefik-public
  • NIC 2 (192.168.1.3) : Interface locale pour traefik-private

Éléments communs aux deux instances

Image et version

Les deux instances utilisent l'image officielle Traefik v3 :

image: traefik:v3

Réseau Docker

Les deux instances se connectent au réseau Docker externe traefik_network qui permet la communication avec tous les conteneurs à proxifier :

networks:
- traefik_network

Ce réseau doit être créé au préalable avec :

docker network create traefik_network

Gestion des certificats SSL

Les deux instances utilisent Let's Encrypt avec le challenge DNS Cloudflare pour générer automatiquement des certificats SSL wildcard.

Avantages du challenge DNS :

  • Certificats wildcard (*.tellserv.fr, *.local.tellserv.fr)
  • Pas besoin d'exposition HTTP publique pour la validation
  • Fonctionne même pour les services internes

Configuration commune :

environment:
- TZ=Europe/Paris
- CF_DNS_API_TOKEN=${CF_DNS_API_TOKEN}

Le token API Cloudflare doit avoir les permissions :

  • Zone / DNS / Edit
  • Zone / Zone / Read

Provider Docker

Les deux instances utilisent le provider Docker pour la découverte automatique des services :

volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro

Traefik surveille les conteneurs et crée automatiquement les routes basées sur les labels Docker.

Configuration dynamique

Chaque instance charge des fichiers de configuration dynamique depuis un répertoire dédié :

volumes:
- ./dynamic-public:/etc/traefik/dynamic:ro # Pour traefik-public
- ./dynamic-private:/etc/traefik/dynamic:ro # Pour traefik-private

Ces fichiers permettent de définir des middlewares, des routers et des services sans redémarrer Traefik.

Dashboard Traefik

Les deux instances exposent leur dashboard via un sous-domaine dédié :

  • traefik-public : traefik-public.local.tellserv.fr
  • traefik-private : traefik-private.local.tellserv.fr

Le dashboard permet de visualiser en temps réel :

  • Les routers actifs et leurs règles
  • Les services détectés
  • Les middlewares appliqués
  • L'état de santé des backends

Politique de redémarrage

restart: unless-stopped

Les conteneurs redémarrent automatiquement sauf s'ils ont été arrêtés manuellement.

Accès à l'hôte Docker

extra_hosts:
- "host.docker.internal:host-gateway"

Cette configuration permet aux conteneurs Traefik d'accéder à l'hôte Docker via le nom host.docker.internal, utile pour proxifier des services tournant directement sur l'hôte.

Instance traefik-public

Rôle et utilisation

L'instance traefik-public gère tous les services accessibles depuis Internet :

  • Applications web publiques
  • APIs exposées
  • Services authentifiés mais accessibles de l'extérieur

Binding réseau

ports:
- "192.168.1.2:80:80"
- "192.168.1.2:443:443"

Traefik écoute uniquement sur l'IP 192.168.1.2, correspondant à la première NIC de la VM.

Entry points

Port 80 (HTTP) :

web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https

Redirection automatique de HTTP vers HTTPS pour toutes les requêtes.

Port 443 (HTTPS) :

websecure:
address: ":443"
http:
middlewares:
- crowdsec-bouncer@file
- secheaders@file
- ratelimit@file
transport:
respondingTimeouts:
idleTimeout: 300s

Trois middlewares appliqués par défaut sur tous les services publics :

  1. crowdsec-bouncer : Blocage des IPs malveillantes détectées par CrowdSec
  2. secheaders : Headers de sécurité HTTP (HSTS, CSP, etc.)
  3. ratelimit : Limitation du nombre de requêtes

Middlewares publics

Fichier : dynamic-public/middlewares.yml

ratelimit :

ratelimit:
rateLimit:
average: 100
burst: 50
period: 1s

Autorise en moyenne 100 requêtes/seconde avec des pics jusqu'à 50 requêtes supplémentaires.

secheaders :

secheaders:
headers:
stsSeconds: 31536000
forceSTSHeader: true

Force HSTS (HTTP Strict Transport Security) pendant 1 an, obligeant les navigateurs à toujours utiliser HTTPS.

evasive :

evasive:
rateLimit:
average: 3
burst: 5
period: 1s

Rate limiting strict pour les endpoints sensibles (3 req/s en moyenne, 5 en burst).

Intégration CrowdSec

CrowdSec est un système de détection et prévention d'intrusions communautaire. Traefik-public intègre le bouncer CrowdSec pour bloquer automatiquement les IPs malveillantes.

Middleware CrowdSec (appliqué sur websecure) :

middlewares:
- crowdsec-bouncer@file

Le bouncer interroge l'API CrowdSec locale pour vérifier si l'IP source est bannie. En cas de match, la requête est bloquée avec un code HTTP 403.

Certificats SSL

Stockage des certificats :

volumes:
- ./letsencrypt-public:/letsencrypt

Configuration ACME dans traefik-public.yml :

certificatesResolvers:
cloudflare:
acme:
email: your-email@example.com
storage: /letsencrypt/cloudflare_acme.json
keyType: EC256
dnsChallenge:
provider: cloudflare
resolvers:
- "1.1.1.1:53"
- "8.8.8.8:53"

Les certificats sont automatiquement renouvelés 30 jours avant expiration.

Logging

log:
level: DEBUG
filePath: "/var/log/traefik/traefik.log"
accessLog:
filePath: "/var/log/traefik/access.log"
format: json

Logs stockés dans /var/log/traefik/ sur l'hôte Docker :

  • traefik.log : Logs système de Traefik (démarrage, erreurs, recharges)
  • access.log : Logs d'accès au format JSON (requêtes HTTP)

Configuration provider Docker

providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
network: traefik_network
  • exposedByDefault: false : Les conteneurs ne sont pas automatiquement exposés, il faut explicitement ajouter des labels Traefik
  • network: traefik_network : Traefik utilisera ce réseau pour communiquer avec les conteneurs

Exemple de labels Docker

Pour exposer un service via traefik-public :

services:
myapp:
image: myapp:latest
labels:
- "traefik.enable=true"
- "traefik.http.routers.myapp.rule=Host(`app.tellserv.fr`)"
- "traefik.http.routers.myapp.entrypoints=websecure"
- "traefik.http.routers.myapp.tls.certresolver=cloudflare"
- "traefik.http.services.myapp.loadbalancer.server.port=80"
networks:
- traefik_network

Instance traefik-private

Rôle et utilisation

L'instance traefik-private gère tous les services réservés au réseau local :

  • Interfaces d'administration (Proxmox, Cockpit)
  • Dashboards internes
  • Services de monitoring
  • Outils de développement

Binding réseau

ports:
- "192.168.1.3:80:80"
- "192.168.1.3:443:443"

Traefik écoute uniquement sur l'IP 192.168.1.3, correspondant à la seconde NIC de la VM.

Entry points

Port 80 (HTTP) :

weblocal:
address: ":80"
http:
redirections:
entryPoint:
to: local
scheme: https

Redirection automatique de HTTP vers HTTPS (entrypoint local).

Port 443 (HTTPS) :

local:
address: ":443"
http:
middlewares:
- localonly@file

Un seul middleware appliqué par défaut : localonly qui restreint l'accès aux IPs locales.

Middlewares privés

Fichier : dynamic-private/middlewares.yml

localonly :

localonly:
ipWhiteList:
sourceRange:
- "127.0.0.1/32"
- "192.168.1.0/24"
- "100.64.0.0/10"
- "172.18.0.0/16"

Liste blanche d'IPs autorisées :

  • 127.0.0.1/32 : Localhost
  • 192.168.1.0/24 : Réseau LAN principal
  • 100.64.0.0/10 : Réseau Tailscale (VPN)
  • 172.18.0.0/16 : Réseau Docker interne

Toute requête provenant d'une IP hors de ces plages est rejetée avec un code HTTP 403.

ratelimit, secheaders, evasive :

Identiques à traefik-public, disponibles pour être appliqués au besoin sur des services spécifiques.

Certificats SSL

Stockage des certificats :

volumes:
- ./letsencrypt-private:/letsencrypt

Configuration ACME dans traefik-private.yml :

certificatesResolvers:
cloudflare:
acme:
email: your-email@example.com
storage: /letsencrypt/cloudflare_acme.json
keyType: EC256
dnsChallenge:
provider: cloudflare

Bien que les services soient locaux, ils bénéficient de certificats SSL valides grâce au challenge DNS.

Logging

log:
level: DEBUG
filePath: "/var/log/traefik-local/traefik.log"
accessLog:
filePath: "/var/log/traefik-local/access.log"
format: json

Logs stockés dans /var/log/traefik-local/ sur l'hôte Docker.

Configuration provider Docker

Identique à traefik-public :

providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
network: traefik_network

Services exposés

Fichiers de configuration statiques dans dynamic-private/ :

cockpit.yml : Proxification de Cockpit (interface web d'administration système) proxmox.yml : Proxification de l'interface Proxmox

Ces fichiers définissent des routers et services pour des applications ne tournant pas dans Docker.

Exemple de labels Docker

Pour exposer un service via traefik-private :

services:
monitoring:
image: grafana/grafana:latest
labels:
- "traefik.enable=true"
- "traefik.http.routers.monitoring.rule=Host(`grafana.local.tellserv.fr`)"
- "traefik.http.routers.monitoring.entrypoints=local"
- "traefik.http.routers.monitoring.tls.certresolver=cloudflare"
- "traefik.http.services.monitoring.loadbalancer.server.port=3000"
networks:
- traefik_network

Sécurité et bonnes pratiques

Séparation public/privé

  • Ne jamais exposer de services d'administration via traefik-public
  • Toujours vérifier l'entrypoint utilisé dans les labels Docker
  • Privilégier traefik-private pour tout ce qui n'a pas besoin d'être public

Middlewares de sécurité

  • CrowdSec : Actif uniquement sur traefik-public, bloque les attaques automatisées
  • localonly : Appliqué par défaut sur traefik-private
  • ratelimit : Protection anti-DDoS basique
  • secheaders : Renforcement de la sécurité côté navigateur

Gestion des certificats

  • Rotation automatique : Let's Encrypt renouvelle les certificats tous les 90 jours
  • Backup : Sauvegarder régulièrement les fichiers cloudflare_acme.json
  • Monitoring : Vérifier les logs pour détecter les échecs de renouvellement

Logging et monitoring

  • Logs accessibles : Montés en volumes sur l'hôte pour analyse
  • Format JSON : Facilite le parsing et l'intégration avec des outils de monitoring
  • Niveau DEBUG : Utile pour le troubleshooting, peut être réduit en production

Limites de cette configuration

Bien que fonctionnelle et sécurisée, cette architecture présente certaines limites à connaître :

Réseau Docker partagé

Problème : Les deux instances Traefik (public et private) utilisent le même réseau Docker (traefik_network). Cela signifie que tous les conteneurs connectés à ce réseau peuvent potentiellement communiquer entre eux, qu'ils soient exposés publiquement ou localement.

Impact :

  • Un conteneur exposé via traefik-public peut techniquement accéder à un conteneur exposé via traefik-private
  • Le cloisonnement réseau n'est pas complet, il repose uniquement sur les IP bindings (192.168.1.2 vs 192.168.1.3)

Amélioration possible :

  • Créer deux réseaux Docker distincts : traefik_public_network et traefik_private_network
  • Connecter chaque instance Traefik uniquement à son réseau dédié
  • Garantir une isolation réseau complète au niveau Docker

Absence de segmentation VLAN

Problème : Les deux NICs de la VM partagent le même réseau physique (192.168.1.0/24) sans segmentation VLAN.

Impact :

  • Le NIC pour traefik-private (192.168.1.3) a techniquement accès à Internet via la passerelle réseau, alors qu'il n'en a pas besoin
  • Pas de cloisonnement réseau au niveau L2/L3 entre les interfaces publique et privée
  • En cas de compromission, un attaquant pourrait potentiellement pivoter entre les deux réseaux

Amélioration possible :

  • VLAN public : Placer le NIC de traefik-public (192.168.1.2) dans un VLAN avec accès Internet
  • VLAN privé : Placer le NIC de traefik-private (192.168.1.3) dans un VLAN isolé, sans accès Internet
  • Configurer des règles de firewall strictes entre les VLANs
  • Cette segmentation renforcerait considérablement le cloisonnement et limiterait la surface d'attaque

Accès au socket Docker

Problème : Les deux instances Traefik ont un accès direct et complet au socket Docker (/var/run/docker.sock). Le socket Docker est l'API d'administration de Docker, donnant un contrôle total sur l'hôte.

Impact sécurité :

  • Un conteneur Traefik compromis pourrait contrôler tous les conteneurs de l'hôte
  • Possibilité d'élévation de privilèges (lancer un conteneur en mode privileged, monter des volumes sensibles, etc.)
  • Accès en lecture seule (ro) limite les dégâts, mais permet toujours d'extraire des informations sensibles (variables d'environnement, secrets, etc.)

Amélioration possible :

  • Utiliser un proxy au socket Docker comme Tecnativa/docker-socket-proxy
  • Ce proxy permet de filtrer finement les opérations autorisées (ex: seulement lire les conteneurs et leurs labels)
  • Réduire la surface d'attaque en limitant l'accès API aux endpoints strictement nécessaires à Traefik

Exemple de configuration :

docker-socket-proxy:
image: tecnativa/docker-socket-proxy
environment:
CONTAINERS: 1 # Autoriser lecture des conteneurs
NETWORKS: 1 # Autoriser lecture des réseaux
SERVICES: 0 # Interdire accès aux services Swarm
TASKS: 0 # Interdire accès aux tâches Swarm
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro

Ces améliorations ne sont pas critiques pour un homelab, mais seraient fortement recommandées en environnement de production.

Ressources