i

Nouvelle config Traefik

Nouvelle config Traefik

Déjà 10 mois que mon infra Docker / Traefik ronronne tranquillement…il est temps de tout casser !

Photo by Denys Nevozhai on Unsplash

J’ai commencé mon infra auto-hébergée en août dernier pour me faire un peu la main sur Docker et Traefik. Depuis beaucoup d’eau a coulé sous les ponts (oui je suis normand…) et il est temps de revoir un peu la configuration.

Pourquoi changer un truc qui marche ?

  • simplifier la configuration: avec une configuration globale incluse dans le docker-compose.yml au lieu du traditionnel “traefik.toml”
  • ajouter un peu de sécurité: avec des passwords gérés par des Docker secrets
  • gérer les redirections HTTPS au niveau global au lieu de par service
  • passer la config du dashboard Traefik dans le docker-compose.yml

Configuration

Je pars du principe que vous avez un serveur clean avec Docker, docker-compose installés (sinon voir la doc officielle) et une petite idée de comment ça marche…

Arborescence

Comme dans la précédente config, l’ensemble de la configuration des conteneurs est stocké dans /opt

1/opt
2├── traefik
3│   ├── config
4│   │   ├── acme.json
5│   │   ├── dynamic-conf
6│   │   │   ├── tls.toml
7│   └── docker-compose.yml

J’ai conservé le fonctionnement dynamique de Traefik avec le dossier dynamic-conf pour prendre en charge des modifications sans avoir à relancer le conteneur.

Voici les commandes pour créer l’arborescence, le fichier docker-compose.yml et le fichier acme.json qui va contenir les certificats TLS et les clés privées obtenus grâce à Let’s Encrypt:

1sudo mkdir -p /opt/traefik/config/dynamic-conf
2sudo touch /opt/traefik/config/acme.json /opt/traefik/docker-compose.yml
3sudo chmod 600 /opt/traefik/config/acme.json

Docker-Compose

J’utilise un bridge Docker dédié pour l’interconnexion entre les conteneurs et Traefik.

1 sudo docker network create --driver=bridge --subnet=172.19.0.0/24 traefik_lan

Sans cette astuce et si vous laissez docker-compose créer le reseau pour vous, vous ne pourrez pas stopper le conteneur traefik sans couper tous les autres conteneurs qui utiliseraient ce même réseau.

Il ne reste plus qu’à éditer le fichier docker-compose.yml et modifier les lignes:

  • email@exemple.tld: mettre votre adresse email
  • traefik.exemple.tld: indiquer l’url choisie pour l’accès au dashboard Traefik
  • admin:$$xxxxxxxxx$$xxxxxxx/: basic auth pour protéger l’accès au dashboard

Pour générer le hash du password, vous pouvez utiliser la commande suivante pour doubler tous les caractères $:

1echo $(htpasswd -nb user password) | sed -e s/\\$/\\$\\$/g

Le contenu du fichier /opt/traefik/docker-compose.yml:

 1version: '3'
 2services:
 3  traefik:
 4    image: traefik:v2.4.8
 5    container_name: traefik
 6    restart: unless-stopped
 7    command:
 8      - "--providers.docker=true"
 9      - "--providers.docker.exposedbydefault=false"
10      - "--providers.file.directory=/etc/traefik/dynamic-conf"
11      - "--providers.file.watch=true"
12      - "--api.dashboard=true"
13      - "--entrypoints.web.address=:80"
14      - "--entrypoints.websecure.address=:443"
15      - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
16      - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
17      - "--certificatesResolvers.letsencrypt.acme.email=email@exemple.tld"
18      - "--certificatesResolvers.letsencrypt.acme.storage=acme.json"
19      - "--certificatesResolvers.letsencrypt.acme.tlsChallenge=true"
20    labels:
21      - "traefik.enable=true"
22      - "traefik.http.routers.dashboard.rule=Host(`traefik.exemple.tld`)"
23      - "traefik.http.routers.dashboard.service=api@internal"
24      - "traefik.http.routers.dashboard.entrypoints=websecure"
25      - "traefik.http.routers.dashboard.middlewares=auth-dashboard"
26      - "traefik.http.middlewares.auth-dashboard.basicauth.users=admin:$$xxxxxxxxx$$xxxxxxx/"
27      - "traefik.http.routers.dashboard.tls=true"
28      - "traefik.http.routers.dashboard.tls.certresolver=letsencrypt"
29    networks:
30      - traefik_lan
31    ports:
32      - 80:80
33      - 443:443
34    volumes:
35      - /var/run/docker.sock:/var/run/docker.sock:ro
36      - $PWD/config/acme.json:/acme.json
37      - $PWD/config:/etc/traefik:ro
38networks:
39  traefik_lan:
40    external: true

On peut maintenant lancer le conteneur Traefik:

1docker-compose -f /opt/traefik/docker-compose.yml up -d

et vérifier que tout fonctionne correctement: https://traefik.exemple.tld

screenshot Traefik Dashboard

Utiliser Traefik

Pour exposer un service frontalisé en HTTPS par Traefik, il suffit de déclarer quelques labels. Voici un exemple avec un simple serveur NGINX:

 1version: '3'
 2services:
 3  app:
 4    container_name: mysite
 5    image: nginx:latest
 6    restart: unless-stopped
 7    networks:
 8      - traefik_lan
 9    volumes:
10      - app:/usr/share/nginx/html:ro
11    expose:
12      - "80"
13    environment:
14      - NGINX_HOST=mysite.exemple.tld
15      - NGINX_PORT=80
16    labels:
17      - "traefik.enable=true"
18      - "traefik.http.routers.blog.rule=Host(`mysite.exemple.tld`)"
19      - "traefik.http.routers.blog.entrypoints=websecure"
20      - "traefik.http.routers.blog.tls=true"
21      - "traefik.http.routers.blog.tls.certresolver=letsencrypt"
22networks:
23  traefik_lan:
24    external: true
25volumes:
26  app:

Améliorer la configuration TLS:

Pour finir cet article sur Traefik, nous allons profiter la fonctionnalité de configuration dynamique pour sécurisé la partie TLS. Pour cela, il suffit d’ajouter un fichier /opt/traefik/config/dynamic-conf/tls.toml.

Le dossier /opt/traefik/config/dynamic-conf est en bind mount dans le conteneur Traefik et la directivement providers.file.watch=true" permet de prendre compte tous les événements dans ce dossier.

 1[tls]
 2  [tls.options]
 3    [tls.options.default]
 4      minVersion = "VersionTLS12"
 5      sniStrict = true
 6      cipherSuites = [
 7        "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
 8        "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
 9        "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
10        "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
11        "TLS_AES_256_GCM_SHA384",
12        "TLS_CHACHA20_POLY1305_SHA256"
13      ]
14      curvePreferences = ["CurveP521","CurveP384"]