Traefik 2.0

Avec la publication de la version majeure 2.0, Traefik a modifié en profondeur ses règles de paramètrage. L’objectif est ici de configurer Traefik dans une architecture type de reverse proxy Web avec la prise en charge dynamique des certificats letsEncrypt.
Table des matières
L’architecture cible est la suivante :
Les entrypoints
On déclare dans le fichier de configuration de Traefik les entrypoint (les ports d’écoutes) et ensuite nous les référençons dans le docker-compose:
“Définition des entrypoints”
entryPoints:
http:
address: ":80"
https:
address: ":443"
Référence des entrypoints dans les labels du container:
- "traefik.http.routers.traefik.entrypoints=http"
- "traefik.http.routers.traefik-secure.entrypoints=https"
Dans le cas présent les points d’entrées sont de type HTTP (avec l’arrivée de la version 2.0, il est maintenant possible de définir des points d’entrées TCP):
Les règles de routage
Sur ces points d’entrées on applique des règles de routage :
- "traefik.http.routers.traefik.rule=Host(`traefik.redteams.fr`)"
- "traefik.http.routers.traefik-secure.rule=Host(`traefik.redteams.fr`)"
On pourra avoir saisi plusieurs entrées DNS en utilisant le séparateur virgule (les certificats associés seront automatiquement générés).
Les services
Dans le cas particulier du dashboard on déclare le point d’entrée sur l’API interne :
- "traefik.http.routers.traefik-secure.service=api@internal"
Qui sera accessible sur un port spécifique :
- "traefik.http.services.traefik.loadbalancer.server.port=8080"
Les traitements
Avant d’envoyer le flux vers le service souhaité différents traitement peuvent être appliqués.
Chiffrement TLS
- "traefik.http.routers.traefik-secure.tls=true"
- "traefik.http.routers.traefik-secure.tls.certresolver=http"
En référence à la configuration de letsEncrypt:
certificatesResolvers:
http:
acme:
email: certif@ndd.fr
storage: /acme.json
httpChallenge:
entryPoint: http
Attention à bien créer le fichier et lui donner les droits 600.
En bonus pour obtenir un A sur letsEncrypt il faut modifier les headers:
http:
middlewares:
Security-Headers:
headers:
contentTypeNosniff: true
browserXssFilter: true
forceSTSHeader: true
STSSeconds: 315360000
STSIncludeSubdomains: true
STSPreload: true
AccessControlMaxAge: 100
Et les options TLS pour restreindre les chiffrements acceptés :
tls:
options:
hardening:
minVersion: "VersionTLS12"
sniStrict: true
cipherSuites:
- "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
- "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
- "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
- "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305"
- "TLS_CHACHA20_POLY1305_SHA256"
Les middleware
Les middleware permettent d’effectuer différents traitements (ajout de headers par exemple) mais aussi de gèrer une politique de sécurité en limitant l’accès à certaines IP.
Les middlewares sont déclarés dans le fichier de configuration de traefik :
http:
middlewares:
HTTPS-only:
redirectScheme:
scheme: https
permanent: true
WhitelistAdmin:
ipWhiteList:
sourceRange:
- "x.x.x.x/32"
- "127.0.0.1/32"
RateLimit:
rateLimit:
average: 50
burst: 25
AuthUsers:
basicAuth:
users:
#Génération du mot de passe via echo $(htpasswd -nbB admin "MotDePasse") | sed -e s/\$/\$\$/g
- "superadmin:$2y$05$8vhIbg5uOwjvdz8888.VF.3pB2YJzqjITwiYERBDBzRmHme8Zk5Ne$$"
Et référencés via les labels dans le docker-compose :
- "traefik.http.routers.traefik-secure.middlewares=AuthUsers@file,WhitelistAdmin@file"
- "traefik.http.routers.traefik.middlewares=HTTPS-only@file"
Pour simplifier l’administration, il est possible de définir des chaines de middleware.
middlewares:
SecuredChain:
chain:
middlewares:
- HTTPS-only
- Security-Headers
- RateLimit
Attention :
Afin que que la source de définition du middleware soit le fichier de configuration de traefik il faut déclarer le provider :
providers:
file:
directory: "/"
Il est aussi possible de déclarer cette configuration spécifique dans un fichier externe
providers:
file:
directory: "/conf"
filename: "dynamic_conf.yml"
Journalisation des accès
accessLog:
filePath: "/log/access.log"
bufferingSize: 2
x.x.x.x - - [29/Sep/2019:09:56:21 +0000] "GET /js/featherlight.min.js?1569749397 HTTP/2.0" 200 9295 "-" "-" 1296 "redteams-secure@docker" "http://172.20.0.4:80" 674ms
x.x.x.x - - [29/Sep/2019:09:56:21 +0000] "GET /js/html5shiv-printshiv.min.js?1569749397 HTTP/2.0" 200 4367 "-" "-" 1297 "redteams-secure@docker" "http://172.20.0.4:80" 672ms
x.x.x.x - - [29/Sep/2019:09:56:21 +0000] "GET /js/highlight.pack.js?1569749397 HTTP/2.0" 200 94021 "-" "-" 1298 "redteams-secure@docker" "http://172.20.0.4:80" 971ms
x.x.x.x - - [29/Sep/2019:09:56:21 +0000] "GET /js/modernizr.custom-3.6.0.js?1569749397 HTTP/2.0" 200 7546 "-" "-" 1299 "redteams-secure@docker" "http://172.20.0.4:80" 969ms
Traefik ne gère pas la rotation du fichier access.log il faut donc s’appuyer sur un logrotate externe.
Configuration complète
docker-compose.yml
traefik:
image: traefik:v2.0.1
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock # So that Traefik can listen to the Docker events
- ./mount/traefik/conf/traefik2.yml:/traefik.yml:ro
- ./mount/traefik/log:/log
- ./mount/traefik/acme/acme.json:/acme.json
- /etc/localtime:/etc/localtime:ro
labels:
- "traefik.enable=true"
- "traefik.http.routers.traefik.entrypoints=http"
- "traefik.http.routers.traefik.rule=Host(`traefik.redteams.fr`)"
- "traefik.http.routers.traefik.middlewares=HTTPS-only@file"
- "traefik.http.routers.traefik-secure.entrypoints=https"
- "traefik.http.routers.traefik-secure.rule=Host(`traefik.redteams.fr`)"
- "traefik.http.routers.traefik-secure.middlewares=AuthUsers@file,WhitelistAdmin@file"
- "traefik.http.routers.traefik-secure.tls=true"
- "traefik.http.routers.traefik-secure.tls.certresolver=http"
- "traefik.http.routers.traefik-secure.service=api@internal"
- "traefik.http.services.traefik.loadbalancer.server.port=8080"
networks:
- traefik_network
depends_on:
- blog
blog:
environment:
- BASEURL=http://go.redteams.fr
- BIND_PORT=80
- DISABLE_LIVE_RELOAD=true
image: daki/hugo:latest
container_name: blog
restart: unless-stopped
hostname: blog
volumes:
- ./mount/blog:/var/www
labels:
- "traefik.enable=true"
- "traefik.http.routers.blog-redteams.entrypoints=http"
- "traefik.http.routers.blog-redteams.rule=Host(`go.redteams.fr`,`blog.redteams.fr`)"
- "traefik.http.routers.blog-redteams.middlewares=SecuredChain@file"
- "traefik.http.routers.blog-redteams-secure.rule=Host(`go.redteams.fr`,`blog.redteams.fr`)"
- "traefik.http.routers.blog-redteams-secure.entrypoints=https"
- "traefik.http.routers.blog-redteams-secure.middlewares=SecuredChain@file"
- "traefik.http.routers.blog-redteams-secure.tls=true"
- "traefik.http.routers.blog-redteams-secure.tls.options=hardening@file"
- "traefik.http.routers.blog-redteams-secure.tls.certresolver=http"
- "traefik.http.services.blog-redteams.loadbalancer.server.port=80"
networks:
- traefik_network
traefik.yml
api:
dashboard: true
entryPoints:
http:
address: ":80"
https:
address: ":443"
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
file:
directory: "/"
accessLog:
filePath: "/log/access.log"
bufferingSize: 2
log:
filePath : "/log/traefik.log"
level: DEBUG
format : "json"
tls:
options:
hardening:
minVersion: "VersionTLS12"
sniStrict: true
cipherSuites:
- "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
- "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
- "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
- "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305"
- "TLS_CHACHA20_POLY1305_SHA256"
http:
middlewares:
SecuredChain:
chain:
middlewares:
- HTTPS-only
- Security-Headers
- RateLimit
HTTPS-only:
redirectScheme:
scheme: https
permanent: true
Security-Headers:
headers:
contentTypeNosniff: true
browserXssFilter: true
forceSTSHeader: true
STSSeconds: 315360000
STSIncludeSubdomains: true
STSPreload: true
AccessControlMaxAge: 100
WhitelistAdmin:
ipWhiteList:
sourceRange:
- "127.0.0.1/32"
RateLimit:
rateLimit:
average: 50
burst: 25
AuthUsers:
basicAuth:
users:
# Generate with echo $(htpasswd -nbB admin "motDePasse") | sed -e s/\$/\$\$/g
- "admin:$2y$05$8vhIbzqjITaaaaERBDBzRmHmeaaaaaa$$"
certificatesResolvers:
http:
acme:
email: certi@ndd.fr
storage: /acme.json
httpChallenge:
entryPoint: http