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 :

alt text

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"

alt text

- "traefik.http.routers.traefik.middlewares=HTTPS-only@file"

alt text

Pour simplifier l’administration, il est possible de définir des chaines de middleware.

 middlewares:
    SecuredChain:
      chain:
       middlewares:
        - HTTPS-only
        - Security-Headers
        - RateLimit

alt text

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

Related