🛡️ Traefik 2 - configuration TLS personnalisée

L'objectif de cet article est de configurer Traefik avec la meilleure configuration TLS personnalisée.

logo traefik
logo traefik
Version Date Commentaires
1 05/2022 Création de l'article
1.1 05/2023 Mise à jour de la syntaxe, des numéros de version, ajout du contexte d'exécution
1.2 07/2023 Mise à jour des fichiers de configuration, ajout d'informations complémentaires dans les blocs "labels" et "configuration dynamique", correction des numéros de version, mise à jour des fichiers de configuration et des chemins

Objectif : Configurer les options TLS de Traefik

Environnement : Debian 12, Docker 24.x, docker compose (plugin) 2.20.x, Traefik 2.10.

Contexte d'exécution :

jho@vmi866042:/opt/docker/dc$ tree
.
├── conf
│   ├── acme.json
│   ├── traefik.yml
│   ├── traefikdynamic
│   │   ├── dynamic.yml
├── docker-compose.yml
└── logs
    ├── traefikAccess.log
    ├── traefik.log
RĂ©sultat de la commande "tree" dans le dossier /opt/docker/dc
  • dossier oĂą se trouvent tous les fichiers et dossiers pour cet exemple : /opt/docker/dc
  • emplacement du fichier de configuration principal de Traefik : /opt/docker/dc/conf/traefik.yml
  • dossier oĂą se trouvent les configurations de Traefik (fichiers dynamiques) : /opt/docker/dc/conf/traefikdynamic
  • emplacement du fichier comportant tous les certificats gĂ©nĂ©rĂ©s par let's encrypt (ou autre) : /opt/docker/dc/conf/acme.json
  • dossier oĂą se trouvent les journaux d'Ă©vĂ©nements (logs) : /opt/docker/dc/logs/

Configuration du fichier docker compose

Avant-propos, vous ne pouvez pas utiliser les deux types de configurations (statique et dynamique) en même temps, c'est l'un ou l'autre. Ici, je privilégie la configuration dynamique. Au besoin, vous trouverez un rappel des différents types de configuration à cette adresse.

Préparons notre fichier docker-compose.yml :

---
services:
  traefik:
    image: traefik:saintmarcelin
    container_name: traefik
    restart: unless-stopped
    ports:
      - target : 80
        published : 80
        protocol: tcp
        mode : host
      ### BEGIN dashboad
      - target : 8080
        published : 8080
        protocol: tcp
        mode : host
      ### END dashboard
      - target : 443
        published : 443
        protocol: tcp
        mode : host
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./conf/traefikdynamic:/dynamic
      - ./conf/traefik.yml:/etc/traefik/traefik.yml:ro
      - ./conf/acme.json:/etc/traefik/acme.json
      - ./logs/traefik.log:/etc/traefik/applog.log
    environment:
      TZ: Europe/Paris

Explications :

  • J'utilise la version "saintmarcelin" de Traefik (v2.10.x).
  • Exposition des ports TCP 80 et 443
  • montage de plusieurs volumes : un premier fichier de configuration « traefik.yml » pour les options de base et le dossier entier pour les options dites « dynamiques ». Le dossier entier est montĂ©, les fichiers Ă  l'intĂ©rieur le seront donc aussi.
  • Le fichier « acme.json » est Ă  crĂ©er avant tout, via un touch conf/acme.json && chmod 0600 conf/acme.json.

Configuration de Traefik

Pour rappel, tous les fichiers de configuration que je vous propose sont créés dans un dossier « conf ». Libre à vous de placer les fichiers où vous le souhaitez, tant que vous modifiez correctement les chemins dans les configurations.

Contenu du fichier traefik.yml :

---
global:
  sendAnonymousUsage: false
  checkNewVersion: false

api:
  dashboard: true

log:
  filePath: "/etc/traefik/applog.log"
  format: json
  level: "ERROR"

providers:
  docker:
    endpoint: unix:///var/run/docker.sock
    exposedByDefault: false
    watch: true
    swarmMode: false
  file:
    directory: "/dynamic"
    watch: true

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

certificatesResolvers:
  letsencrypt:
    acme:
      email: [email protected]
      caServer: https://acme-staging-v02.api.letsencrypt.org/directory
#      caServer: https://acme-v02.api.letsencrypt.org/directory
      storage: "/etc/traefik/acme.json"
      keyType: EC256
      httpChallenge:
        entryPoint: web

Ce qui est important dans ce fichier, c'est le bloc "providers" avec la définition d'un dossier « dynamic ». Ce dossier est à créer en local auparavant sur votre machine pour y intégrer ensuite le fichier « dynamic.yml ».

La ligne watch: true permet à Traefik de regarder les changements fourni dans les fichiers de configuration et de les appliquer une fois l'enregistrement du fichier effectué.

Les certificats sont générés à l'aide du service Let's Encrypt. En effet, Traefik, au travers de l'outil « Lego », ira lui-même générer les certificats nécessaires. Lors de vos tests, préférez utiliser le serveur "staging" qui a une limite plus haute que le serveur « acme-v02 ». Une fois les tests terminés, vous pouvez commenter la ligne "staging" et utiliser le serveur Let's Encrypt « acme-v02 ».

Le fichier de configuration secondaire (dynamique) est de même type que le « traefik.yml », la différence réside dans les paramètres. Pour rappel, le fichier « traefik.yml » contient la configuration pour le démarrage de Traefik, tandis que le fichier de configuration dynamique contient les options pour les middlewares, les routeurs et autres loadbalancers. Retrouvez ci-dessous mon fichier de configuration « dynamic.yml, dans le dossier « conf/traefikdynamic" :

---
tls:
  options:
    default:
      minVersion: VersionTLS12
      sniStrict: true
      cipherSuites:
        - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
        - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
        - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305
        - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
        - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
        - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
        - TLS_AES_128_GCM_SHA256
        - TLS_AES_256_GCM_SHA384
        - TLS_CHACHA20_POLY1305_SHA256
      curvePreferences:
        - CurveP521
        - CurveP384
      alpnProtocols:
        - h2
        - http/1.1
    mintls13:
      minVersion: VersionTLS13

http:
  middlewares:
    security:
      headers:
        accessControlAllowMethods:
          - GET
          - OPTIONS
          - PUT
        accessControlMaxAge: 100
        addVaryHeader: true
        browserXssFilter: true
        contentTypeNosniff: true
        customFrameOptionsValue: SAMEORIGIN
        customResponseHeaders:
          Access-Control-Allow-Origin: "*"
          Sec-Fetch-Site: cross-site
          X-Forwarded-Proto: https
        forceSTSHeader: true
        frameDeny: true
        hostsProxyHeaders:
          - "X-Forwarded-Host"
        referrerPolicy: "strict-origin-when-cross-origin"
        stsIncludeSubdomains: true
        stsPreload: true
        stsSeconds: 31536000

Quelques explications Ă  fournir :

  • Le bloc TLS instaure toutes les options nĂ©cessaires pour avoir une configuration optimale. Simplement, la version minimale est le TLS 1.2 avec des algorithmes de chiffrement utilisĂ©s par les navigateurs web modernes,
  • le bloc « HTTP comporte deux sous-blocs :
    - un premier middleware pour compresser certains flux sauf en cas de streaming
    - un second middleware pour toutes les sécurités relative au HTTPS (les "headers" en-têtes).

Utilisation des labels docker

Pour que vos services sous docker soient exploités au travers de Traefik, vous avez deux possibilités : utiliser les labels docker, ou saisir une configuration dans les fichiers de Traefik. Pour cette partie, utilisons les labels.

Pour chaque service que vous souhaitez avoir derrière Traefik, ajoutez ces labels (à modifier en fonction du nom de vos entrypoints, vos noms de définition de middleware) :

services:
  portainer:
...
    labels:
      traefik.enable: "true"
      traefik.http.routers.service-https.entrypoints: "websecure"
      traefik.http.routers.service-https.middlewares: "security@file"
      traefik.http.routers.service-https.tls: "true"
      traefik.http.routers.service-https.tls.certresolver: "letsencrypt"
docker-compose.yml

Les middlewares security@file et compression@file viennent piocher directement les paramètres dans le fichier dynamic.yml précédemment créé.

Lorsque vos labels sont mis en place, n'oubliez pas de relancer vos conteneurs Docker (docker-compose up -d) et le tour est joué.

Ne pas utiliser les labels Docker et laisser Traefik chercher seul

Si vous ne souhaitez pas ou n'utilisez pas les labels docker, vous pouvez ajouter dans le fichier de configuration auparavant modifié des informations de « routeurs » et « services ». À la suite des configurations TLS, toujours dans la partie http, deux nouveaux blocs doivent être ajoutés : (pour l'exemple, j'utiliserai le conteneur Portainer).

http:
...
  services:
    sc-portainer:
      loadBalancer:
        servers:
        - url: "http://portainer:9000"
 
   routers:
     rt-portainer:
      entryPoints:
      - websecure
      middlewares:
      - security
      service: sc-portainer
      rule: Host(`portainer.domain.local`)
      tls:
        certResolver: letsencrypt

Le bloc « services » défini les conteneurs à accéder, en interne de votre infrastructure, avec le port de communication. Chaque service doit être défini pour être exploité au travers de Traefik, comme dans l'exemple ci-dessus.

Le bloc « routers » défini la route et les middlewares pour les services. Dans ce cas, je veux que le service « sc-portainer » utilise les deux middlewares, soit accessible via « portainer.domain.local » (ligne rule:), et accessible en HTTPS depuis l'extérieur (certificat généré en utilisant le service letsencrypt défini dans le fichier traefik.yml).

Cette configuration est plus pratique, car elle ne nécessite pas de changer le fichier docker-compose.yml de votre service. Traefik prend à chaud les modifications, dans les fichiers de configuration dynamique.