Ceci est un copier/coller de mon blog

Je suis très longtemps resté sur mon bon vieux nginx pour faire mon reverse proxy, ça fonctionne parfaitement.
J'avais déjà testé traefik en version 1.5, et je l'avais trouvé plutôt lent et limité, mais récemment, je me suis dit que j'allais retesté tout ça, à l'heure ou j'écris ces lignes, nous sommes à la version 2.2, nommée chevrotin.



J'ai eu beaucoup de mal à bien comprendre le fonctionnement de traefik, car la quasi totalité des tutoriels sont identiques niveau configuration, ce qui changeait c'est l'application mis derrière. Mais aucun réel tutoriel qui en explique le fonctionnement. Nous allons ici tenter de réellement comprendre ce que nous faisons, avec une approche totalement différente.

Je ne reviendrais sur ce qu'est traefik, si vous êtes tombé sur ce tutoriel, c'est que vous savez ce que c'est.

Vocabulaire

Pour comprendre le fonctionnement de traefik, nous aurons besoin d'un peu de vocabulaire :

les entrypoints

Les entrypoints sont comme le nom l'indique les points d'entrées, ce sont les adresses et ports exposés par traefik, qui permettrons d'exposer nos applications. Bien souvent nous utilisons les ports 443 et 80.

les services

Les services sont nos applications, par exemple nextcloud. Dans la version 1.X de traefik, c'était nommé les backends.
Nous avons 3 types de services :

  • http
  • tcp
  • udp

Nous ne sommes donc pas limité au web, nous pouvons redirigé du ssh, du ftp , what else ?!!!

les routers

Les routers sont les règles de redirection du reverse, nous pointons un router vers un service. A ce router nous lui donnons des URLs, des entrypoints.

les providers

Les providers sont les sources de génération de la configuration. C'est ce qui fait la force de traefik, il en existe plusieurs, de plusieurs type :

  • Moteur de conteneur :
    • docker : La plus connu, on donne accès au socket de docker à traefik, et lui va gérer la création des services et routers en lisant celui-ci, en fonction des labels utilisés.
    • kubernetes : Fonctionne de manière similaire à docker.
    • rancher : Je n'ai pas encore regardé le fonctionnement, mais je suppose que le fonctionnement est identique à kubernetes
    • marathon : Pareil que pour rancher
  • Base de clé/valeur : Je n'ai pas regardé exactement le fonctionnement de ce type de provider
    • consul
    • consulcatalog
    • etcd
    • redis
    • zookeeper
  • file : Permets de lire un fichier ou un répertoire contenant des fichiers de configuration à la volée.

les middlewares

Les middlewares seront des étapes intermédiaires entre le router et le service, ça peux être un peu tout et n'importe quoi, comme de la compression, de la sécurisation, de la configuration, ou même de l'authentification (cela fera l'objet d'un autre article).

Configuration

Pour configurer traefik, nous avons deux méthodes, la méthode interactive, et la méthode déclarative.

Interactive

Cette méthode permets de passer la configuration directement en paramètre de l'exécutable, par exemple nous pourrions avoir ceci :

$ traefik --accesslog=true \
          --api=true \
          --api.insecure=true \
          --api.dashboard=true \
          --api.debug=true \
          --log.level=INFO \
          --providers.docker.endpoint=unix:///var/run/docker.sock \
          --providers.docker.exposedbydefault=false \
          --providers.docker.watch=true \
          --providers.docker.swarmmode=true \
          --providers.file.filename=/etc/traefik/traefik_dynamic.yml \
          --providers.file.watch=true \
          --entrypoints.web.address=:80 \
          --entrypoints.websecure.address=:443 \
          --entrypoints.web.http.redirections.entrypoint.scheme=https \
          --entrypoints.web.http.redirections.entrypoint.to=websecure \
          --certificatesresolvers.letsencrypt.acme.email=xataz@monmail.net \
          --certificatesresolvers.letsencrypt.acme.caserver=https://acme-v02.api.letsencrypt.org/directory \
          --certificatesresolvers.letsencrypt.acme.storage=/acme.json \
          --certificatesresolvers.letsencrypt.acme.keytype=EC384 \
          --certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web \
          --certificatesresolvers.letsencrypt.acme.tlschallenge=true

C'est personnellement la méthode que j'utilise.
Cette méthode devient déclarative si on utilise docker et docker-compose, ou même kubernetes.

Déclarative

Nous pouvons également passer un fichier de paramètre à traefik, au format toml ou yaml, ce qui donnerais pour la même configuration :

TOML

[accesslog]
[api]
  insecure=true
  dashboard=true
  debug=true

[log]
  level="INFO"

[providers]
  [providers.docker]
    endpoint="unix:///var/run/docker.sock"
    exposedbydefault=false
    watch=true
    swarmmode=true
    
  [providers.file]
    filename=/etc/traefik/traefik_dynamic.yml
    watch=true

[entryPoints]
  [entryPoints.web]
    address=":80"
    [entryPoints.web.http.redirections.entrypoint]
      scheme="https"
      to="websecure"
  [entryPoints.websecure]
    address=":443"

[certificatesResolvers]
  [certificatesResolvers.letsencrypt]
    [certificatesResolvers.letsencrypt.acme]
      email = "mail@nomdedomaine.org"
      caServer = "https://acme-v02.api.letsencrypt.org/directory"
      storage = "acme.json"
      keyType = "EC384"

      [certificatesResolvers.letsencrypt.acme.httpChallenge]
        entryPoint = "web"

Perso je n'aime pas du tout ce format, c'est ce qui m'avait fait fuire traefik à l'époque.

YAML

accesslog: {}
api:
  insecure: true
  dashboard: true
  debug: true

log:
  level: "INFO"

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedbydefault: false
    watch: true
    swarmmode: true
    
  file:
    filename: /etc/traefik/traefik_dynamic.yml
    watch: true

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

certificatesResolvers:
  letsencrypt:
    acme:
      email: "mail@nomdedomaine.org"
      caServer: "https://acme-v02.api.letsencrypt.org/directory"
      storage: "acme.json"
      keyType: "EC384"
      httpChallenge:
        entryPoint: "web"

Je trouve le yaml beaucoup plus clair et simple.

Cas pratique

Rien de mieux qu'un exemple pour comprendre, nous allons faire une petite stack avec traefik, nextcloud et postgres.
Dans cette exemple, nous utiliserons docker pour installer les services, mais pas pour la configuration automatique de traefik.

Pouquoi donc ?!

Déjà je n'aime pas le fait de mettre mon socket docker dans un conteneur, même en lecture seul, le socket à toutes les informations des conteneurs. Autrement, je n'aime pas trop alourdir mes docker-compose, j'aime quand c'est clair, net et précis.
Selon moi, l'utilisation des labels va être utile seulement si on installe régulièrement (plusieurs fois par jours) des conteneurs, qu'on en supprime, en gros quand on est un cloud publique. Dans une utilisation personnelle, la configuration d'un reverse ne change pas souvent.


De plus, on a souvent tendance dès lors qu'on parle de traefik, de le lier directement à docker ou kubernetes, hors traefik est avant tout un reverse proxy, il est utilisable sans docker, sans conteneur, en baremetal.

Notre stack de base (docker-compose)

Nous partirons donc de cette configuration de docker-compose :

version: "3.8"

networks:
  traefik:

services:
  traefik:
    image: traefik:chevrotin
    volumes:
      - /srv/docker/traefik/acme.json:/etc/traefik/acme.json
      - /srv/docker/traefik/certs:/etc/traefik/certs
      - /var/run/docker.sock:/var/run/docker.sock
      - /srv/docker/traefik/conf.d:/etc/traefik/conf.d
    ports:
      - 80:80
      - 443:443
      - 8080:8080 # le temps de tester
    networks:
      - traefik
    command:
      - "--global.sendanonymoususage=false" # désactivation de l'envoi de donnée
      - "--global.checknewversion=false" # puisque dockerisé, on désactive le check de mise à jour
      - "--accesslog=true" # Pour avoir les logs d'accès
      - "--api=true" # Pour activer l'api
      - "--api.insecure=true" # Activer pour exposer l'api sur 8080
      - "--api.dashboard=true" # Pour activer le dashboard
      - "--log.level=INFO"
      - "--providers.file.directory=/etc/traefik/conf.d/" # Permets de charger les configurations dans le répertoire (tout les yaml et toml)
      - "--providers.file.watch=true" # Permets de surveiller le répertoire précédent pour charger dynamiquement les configurations
      - "--entrypoints.web.address=:80" # Création de l'entrypoint nommé web sur le port 80
      - "--entrypoints.websecure.address=:443" # Création de l'entrypoint nommé websecure sur le port 443
      #- "--entrypoints.web.http.redirections.entrypoint.scheme=https" # Pour créer une redirection vers https
      #- "--entrypoints.web.http.redirections.entrypoint.to=websecure" # Pour rediriger vers l'entrypoint websecure (port 443)
       - "--certificatesresolvers.letsencrypt-ecdsa.acme.email=xataz@monmail.net"
       - "--certificatesresolvers.letsencrypt-ecdsa.acme.caserver=https://acme-v02.api.letsencrypt.org/directory"
       - "--certificatesresolvers.letsencrypt-ecdsa.acme.storage=/acme.json"
       - "--certificatesresolvers.letsencrypt-ecdsa.acme.keytype=EC384"
       - "--certificatesresolvers.letsencrypt-ecdsa.acme.httpchallenge.entrypoint=web"
       - "--certificatesresolvers.letsencrypt-ecdsa.acme.tlschallenge=true"
       - "--certificatesresolvers.letsencrypt-rsa2048.acme.email=xataz@monmail.net"
       - "--certificatesresolvers.letsencrypt-rsa2048.acme.caserver=https://acme-v02.api.letsencrypt.org/directory"
       - "--certificatesresolvers.letsencrypt-rsa2048.acme.storage=/acme.json"
       - "--certificatesresolvers.letsencrypt-rsa2048.acme.keytype=RSA2048"
       - "--certificatesresolvers.letsencrypt-rsa2048.acme.httpchallenge.entrypoint=web"
       - "--certificatesresolvers.letsencrypt-rsa2048.acme.tlschallenge=true"


  db_nextcloud:
    image: postgres:12
    networks:
      - traefik
    volumes:
      - /srv/docker/db_nextcloud/:/var/lib/postgresql/
    environment:
      - POSTGRES_PASSWORD=nextcloud
      - POSTGRES_DB=nextcloud
      - POSTGRES_USER=nextcloud

  nextcloud:
    image: nextcloud:19
    networks:
      - traefik
    environment:
      - POSTGRES_HOST=db_nextcloud
      - POSTGRES_DB=nextcloud
      - POSTGRES_USER=nextcloud
      - POSTGRES_PASSWORD=nextcloud
      - NEXTCLOUD_ADMIN_USER=admin
      - NEXTCLOUD_ADMIN_PASSWORD=admin
    volumes:
      - /srv/docker/nextcloud:/var/www/html

Traefik utilise lego pour la génération des certificats ssl via acme, il est donc compatible avec les challenges DNS, et avec les API de différent provider de DNS, pour ceci vous pouvez regarder ce tableau

Nous créons 2 resolvers, un pour générer du ecdsa 384, et l'autre du RSA2048, car par exemple certains client n'aime pas trop le ECDSA.
Malheureusement traefik ne supporte pas la configuration dynamic des resolvers, nous sommes donc obligé de l'ajouter manuellement.

Maintenant que nous avons notre docker-compose, nous pouvons nous attaquer à la configuration de traefik. Nous pouvons dès à présent lancer notre stack avec docker-compose.

Configuration de traefik

Dans la configuration de traefik, nous avons mis un répertoire en écoute --providers.file.directory=/etc/traefik/conf.d/ (qui est monté sur l'hote dans /srv/docker/traefik/conf.d), donc toute la configuration de ce répertoire sera chargé dynamiquement --providers.file.watch=true, dès qu'on ajoutera ou modifira un fichier, il sera pris à chaud, sans avoir à redémarrer sa stack.

Nous allons ici ajouter quelques configurations, notammant sur le TLS, et quelques middleware pour la configuration.

Configuration TLS

Nous créons donc un premiers fichiers tls.yml dans notre répertoire écouté par traefik (pour rappel /srv/docker/traefik/conf.d)

tls.yml

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:
        - X25519
        - CurveP521
        - CurveP384
        - CurveP256

    mintls13: # Arbitraire également, toto fonctionne aussi
      minVersion: "VersionTLS13"

Cette configuration est plutôt simple à comprendre, nous avons les ciphers autorisés dans la configuration par defaut, avec une version de TLS minimum en version 1.2, et nous créons une configuration qui forcera l'utilisation de TLSv1.3.

Quelques middlewares

Maintenant que nous avons notre configuration TLS, il faut configurer quelques middlewares, comme précédemment dis, nous pouvons créer des middlewares pour beaucoup de chose.

Ici je vais créer un fichier par middleware, pour une raison simple, c'est que si je me foire dans la configuration, il ne chargera pas que le middleware qui pose problème, et non tous.

Le format de fichier d'un middleware est :

http:
  middlewares:
    <nomdumiddleware>:
      <typedumiddleware>:
        <options>

    <nomdu2ememiddleware>:
      <typedumiddleware>:
        <options>

Middleware de compression

/srv/docker/traefik/conf.d/compression.yml :

http:
  middlewares:
    compression:
      compress:
        excludedContentTypes:
          - "text/event-stream"

Pas beaucoup d'options pour la compression, et la compression est systématiquement en GZIP, pas de brotli pour le moment.

Middleware hsts

/srv/docker/traefik/conf.d/hsts.yml

http:
  middlewares:
    hsts:
      headers:
        forceSTSHeader: true
        stsSeconds: 315360000
        stsIncludeSubdomains: true
        stsPreload: true

Ici nous activons donc le hsts, le but du tutoriel n'est pas d'expliquer son fonctionnement.

Middleware pour la redirection http vers https

/srv/docker/traefik/conf.d/redirect-to-https.yml

http:
  middlewares:
    redirect-to-https:
      redirectScheme:
        scheme: https
        permanent: true

Nous aurions pu également créer une règle global, en lançant traefik avec les options --entrypoints.web.http.redirections.entrypoint.scheme=https et --entrypoints.web.http.redirections.entrypoint.to=websecure, mais il peux être possible que certains services n'est pas besoin d'accéder en https.

Middleware pour quelques sécurités supplémentaire

/srv/docker/traefik/conf.d/security.yml

http:
  middlewares:
    security:
      headers:
        accessControlMaxAge: 100
        addVaryHeader: true
        browserXssFilter: true
        contentTypeNosniff: true
        frameDeny: true
        sslRedirect: true
        customFrameOptionsValue: "SAMEORIGIN"
        referrerPolicy: "same-origin"
        featurePolicy: "vibrate 'self'"

Middleware pour l'authentification

Pour ici, nous avons pleins de possibilités de gestion. Cette exemple permets justement de voir la puissance des middlewares.

Nous pouvons par exemple créer un middleware par groupe d'utilisateur :

http:
  middlewares:
    admin-users:
      basicAuth:
        users:
          - "xataz:$apr1$FprQWnRT$3FZXlQg0.qCkkytl4iMLc1"
          - "toto:$apr1$pbpEY6eD$DFz44UOcGC5KC5jasAhgQ/"

    dev-users:
      basicAuth:
        users:
          - "tata:$apr1$inMBbv02$C/oh3LLEfmmOyloAtqW/V/"

Et quand on en aura besoin, on les appelleras.

Nous pouvons également créer un middlewares par users :

http:
  middlewares:
    xataz-user:
      basicAuth:
        users:
          - "xataz:$apr1$FprQWnRT$3FZXlQg0.qCkkytl4iMLc1"
    toto-user:
      basicAuth:
        users:
          - "toto:$apr1$pbpEY6eD$DFz44UOcGC5KC5jasAhgQ/"
    tata-user:
      basicAuth:
        users:
          - "tata:$apr1$inMBbv02$C/oh3LLEfmmOyloAtqW/V/"

Et créer des groupes de middlewares :

http:
  middlewares:
    admin-group:
      chain:
        - "xataz-user@file"
        - "toto-user@file"
    devs-group:
      chain:
        - "toto-user@file"

Pour ce tuto, nous simplifierons avec un seul fichier avec la méthode d'un middleware par groupe :
/serv/docker/traefik/conf.d/auth.yml

http:
  middlewares:
    admin-users:
      basicAuth:
        users:
          - "xataz:$apr1$FprQWnRT$3FZXlQg0.qCkkytl4iMLc1"
          - "toto:$apr1$pbpEY6eD$DFz44UOcGC5KC5jasAhgQ/"

    dev-users:
      basicauth:
        users:
          - "tata:$apr1$inMBbv02$C/oh3LLEfmmOyloAtqW/V/"

Pleins d'autres middlewares

Traefik propose pas mal de middlewares, là nous en avons vu que très peu, mais nous avons aussi la possibilité de rediriger l'authentification, de gérer les erreurs http, de limiter les IPs, etc ...., nous en verrons d'autres par la suite.

La liste complète des middlewares est disponible ici.

Les services et les routes

Personnellement j'aime bien créer les routers et les services dans le même fichier, mais un fichier par application.

Dashboard traefik

Nous allons d'abord créer notre configuration pour accéder au dashboard de traefik :

/serv/docker/traefik/conf.d/traefik.yml

http:
  services:
    traefik:
      loadBalancer:
        servers:
          - url: "http://localhost:8080"

  routers:
    traefik:
      rule: "Host(`traefik.exemple.fr`)"
      entryPoints:
        - "web"
      middlewares:
        - "redirect-to-https@file"
      service: "noop@internal"
    traefik-secure:
      rule: "Host(`traefik.exemple.fr`)"
      entryPoints:
        - "websecure"
      middlewares:
        - "hsts@file"
        - "security@file"
        - "compression@file"
        - "admins-users@file"
      service: "traefik@file"
      tls:
        certResolver: letsencrypt-ecdsa
        options: mintls13

Alors qu'avons nous dans ce fichier ?

D'abord nous avons la déclaration du service, que j'ai nommé traefik ici (très original), de type loadBalancer, qui pointe vers localhost:8080 (adresse exposé par traefik).

Puis nous avons 2 routes, traefik et traefik-secure. traefik écoute sur le port 80 (entrypoint web), réponds à l'url http://traefik.exemple.fr et utilise le middleware redirect-to-https@file, pour ce rediriger vers https. Le @file permets de définir sur qu'elle provider on tape, si c'est sur le provider docker, on utiliserais @docker.
Ensuite nous avons le service, là nous tappons sur noop@internal, alors là c'est un service particulier qui ne pointe sur rien, et puisque nous avons la redirection, je préfère tapper sur rien, pour éviter de me retrouver connecté sur le dashboard sans tls.

Puis nous avons traefik-secure, nous répondons toujours au requête de https://traefik.exemple.fr, mais sur l'entrypoint websecure. Ici nous avons plusieurs middlewares, hsts@file pour les règles hsts, security@file pour quelques sécurités, compression@file pour compresser les requêtes et admin-users@file pour limité l'accès au admins.

Et pour finir nous avons la configuration TLS, avec le certResolver qui appel letsencrypt-ecdsa pour générer automatiquement le certificat SSL ecdsa de 384 bits, et on active l'option mintls13 (du fichier tls.yml) pour n'autoriser les connexions qu'en TLS 1.3.

Si tout est bon, et sans redémarrer traefik, vous devriez pouvoir accéder à votre dashboard en TLS.

nextcloud

Pour nextcloud, nous allons faire une configuration un peu différente, histoire de voir quelques possibilité de traefik.
De base nextcloud écoute sur http://nextcloud:80/, mais nous allons ajouter un prefix /cloud.


Voici la configuration à utiliser :

http:
  services:
    nextcloud:
      loadBalancer:
        servers:
          - url: "http://nextcloud"

  routers:
    nextcloud:
      rule: "Host(`cloud.exemple.fr`) && PathPrefix(`/cloud`)"
      entryPoints:
        - "web"
      middlewares:
        - "redirect-to-https@file"
      service: "noop@internal"
    nextcloud-secure:
      rule: "Host(`cloud.exemple.fr`) && PathPrefix(`/cloud`)"
      entryPoints:
        - "websecure"
      middlewares:
        - "hsts@file"
        - "security@file"
        - "compression@file"
        - "strip-cloud@file"
      service: "nextcloud@file"
      tls:
        certResolver: letsencrypt-rsa2048

  middlewares:
    strip-cloud:
      stripPrefix:
        prefixes:
          - "/cloud"

Là nous avons ajouté deux choses, dans rule, nous avons ajouté le Prefix /cloud, pour que seulement cloud.exemple.fr/cloud soit redirigé, mais ensuite nous créons un middlewares nommé strip-cloud de type stripPrefix.
Pour bien comprendre, le PathPrefix se passe au niveau du front, mais de base, le reverse proxy va rediriger https://cloud.exemple.fr/cloud vers http://nextcloud:80/cloud, sauf que nextcloud ne connais pas se chemin.
Le stripPrefix va donc enlever ce prefix (/cloud), entre traefik et nextcloud, afin que nextcloud puisse retrouver son chemin.

Conclusion

Nous n'avons ici effleuré que de très prêt les possibilités de traefik. Mais nous avons pu voir que traefik est un reverse proxy à part entière, et que ce n'est pas seulement un outil pour les conteneurs.
Nous avons vu ici une méthode de configuration très différente de ce que l'on vois partout. Le but n'est pas de dénigrer la configuration par label ou autre, au contraire cette configuration à beaucoup d'avantages, mais de permettre un découpage plus propres de la configuration et surtout de montrer que traefik ­­!= docker.

Bon tuto, ça saura aiguiller les débutants ou même ceux qui utilisent déjà Traefik v2 sans trop comprendre ce qu'il font !

Je modifierais d'urgence une seule chose :

      curvePreferences:
        - X25519
        - CurveP521
        - CurveP384
        - CurveP256

X25519 est supporté par tous les navigateurs/OS modernes et c'est une crypto a priori plus sûre que les courbes NIST. De même, pourquoi drop le support de P-256 quand sa sécurité est largement équivalente encore à P-384.

Précise aussi que toutes les modifications de ciphers/curves n'affectent pas TLS 1.3 à cause d'une limitation de Go (qui fait confiance aveuglément à la spécification TLS 1.3).

Pour l'authentication tu aurais pu faire usage de ceci : https://docs.traefik.io/v2.0/middlewares/basicauth/#usersfile

@xataz Je reviens vers toi pour une suggestion que j'ai appliquée à mon traefik. Par défaut, l'image traefik utilise root. C'est pas idéal pour quelque chose d'exposé comme ça. Dans le docker-compose on peut ajouter la directive :

user: 1234:1234

Pouf, plus de root !

Problème n°1 : traefik ne peut pas utiliser les ports 80/443 dans le conteneur.
Solution : utiliser 8080/4430 et le port remapping (+ modifier les entrypoints) pour que ce soit 80/443 sur l'host.

Problème n°2 : pour ceux qui le montaient, on ne peut plus utiliser le socket.
Solution : déployer un proxy pour le socket sur un réseau propre, ce qui est déjà plus sûr.

  socket-proxy:
    image: tecnativa/docker-socket-proxy
    container_name: socket-proxy
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    environment:
      - CONTAINERS=1
    networks:
      - socket_network

traefik doit accéder à ce socket_network, et dans la conf on remplace le socket par tcp://socket-proxy:2375. Cette méthode est sympa car en plus l'image tecnativa/docker-socket-proxy verrouille des accès inutiles à l'API.

Voilà voilà, chez moi ça fonctionne bien comme hardening, pourquoi pas répandre ça dans le tuto. 😄

    Wonderfall J'ai une autre solution chez moi, docker tourne avec un usernamespace, comme ça, le root de traefik correspond en faite UID 231072.

    Ensuite pour le socket, y'avais effectivement la solution du proxy, ou alors utiliser les ACLs directement sur le socket.

    J'ai essayer de faire quelques choses de générique, et je pensais justement faire un tuto sur la sécurité de docker, avec justement ces problématiques.

    J'aime bien l'idée du namespace, malheureusement je ne sais pas si c'est adaptable à tous les cas. Jamais essayé pour cette raison, je préfère vérifier conteneur/conteneur ce qu'il s'y passe. Mais c'est une solution aussi !

    Merci pour ces explications c'est vraiment très clair !
    J'ai deux petites remarques :
    Dans le docker-compose tu map /etc/traefik/conf.d mais dans le provider file tu as mis /etc/traefik/conf/
    Dans les exemples de middlewares basicAuth il faut bien mettre le A en majuscule sinon le middleware est invalide

    Je rencontre un problème mais je fouille encore, traefik me dit que mes routeurs utilises un resolver qui n'existe pas... mais une fois réglé tout sera bon !

      Banip Merci pour tes remontés, c'est corrigé

      ça se fait de signaler un sujet parce qu'il est beaucoup trop stylé ? 🤣

      Merci en tout cas ! J'ai trouvé dans les quelques lignes que j'ai lu la motivation nécessaire pour tenter un passage de mon "bon vieux" nginx vers cette v2 fort prometteuse.

      Je teste ça au plus vite et je tenterais un petit retour bien propre ici pour y poser mon avis 😉

      5 jours plus tard

      Hello, avant tout super article ! Je rejoins totalement le fait que tout se ressemble concernant les explications de Traefik sauf cette page ! J'avais déjà appris "à la dure" ( voir très dure ... :p ) mais j'ai pu approfondir voir découvrir des nouvelles choses donc merci !
      J'ai une grosse question concernant les redirections HTTPS ( scheme et redirect to websecure) : Actuellement, nous avons lancé une stack nextcloud sous docker avec la DB sur le host ( non docker donc). Il y a des "lenteurs" concernant les images qui sont balisée en http par ex. Soit c'est lent ou cela n'apparaît tout simplement pas.
      Ces lenteurs n'étaient pas du tout présentes avant traefik ( ca réagissait à la secondes sans "lag"). Faut-il désactiver cette redirection ? Il y a des choses qui se font dans nextcloud vis à vis des proxys mais la c'est du docker .. je pense que cette règle ne s'applique pas. Avez-vous ressentit ce "soucis" ? Bonne soirée

        anoskar salut,

        Si tu ressens des lenteurs, c'est peux être que tu as plusieurs réseaux sous docker, j'ai eu le soucis et en spécifiant le réseaux à utiliser (--traefik.docker.network=traefik-net) ça a corrigé le bug.
        Je pense que dans le cas de plusieurs réseau, il doit tester les réseaux 1 à 1 dans un ordre inconnu.

        Ensuite effectivement tu peux supprimer la redirection, ou créer une règle global (dans les paramètres de lancement de traefik), je n'ai cependant jamais senti de ralentissement du a ceci.

        Autrement si tu étais sous nginx avant, il est possible que le cache soit mieux géré avec qu'avec traefik

        Bonsoir, merci pour cette réponse, je n'ai qu'un seul network de définit. Mais tôt ce matin le problème a été résolu par nos dev. En résumé, les url de certaines images étaient en HTTP et non en HTTPS. Tout est parfaitement réactif. Encore merci ! Bonne soirée

        Merci, un de mes projets était bloqué en traefik 1.7 car j'avais de la diffHHHH flemme pour lire les nouvelles docs, et une fois résumé comme ça, je vais pouvoir m'y remettre 🙂

        12 jours plus tard

        Bonjour,
        Tout d'abord merci pour ce super tuto qui m'a redonné envie de tester "Traefik" que j'avais abandonné !

        J'ai bien suivi à la lettre votre exemple et explications, et lorsque je lance le docker-compose tout se passe bien, les trois services docker sont actifs :

        CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                                              NAMES
        7274811ae581        nextcloud:19        "/entrypoint.sh apac…"   45 minutes ago      Up 45 minutes       80/tcp                                                             nextcloud
        844bd0c0cd0a        postgres:12         "docker-entrypoint.s…"   45 minutes ago      Up 45 minutes       5432/tcp                                                           db_nextcloud
        da02512af7ce        traefik             "/entrypoint.sh --gl…"   45 minutes ago      Up 45 minutes       0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp, 0.0.0.0:8080->8080/tcp   traefik

        Toutefois, je rencontre un problème de non génération de certificat SSL, à cause de l'erreur suivante suivante (issue de la log de traefik) :

        time="2020-06-30T18:27:27Z" level=error msg="the router nextcloud-secure@file uses a non-existent resolver: letsencrypt-rsa2048"
        time="2020-06-30T18:27:27Z" level=error msg="the router traefik-secure@file uses a non-existent resolver: letsencrypt-ecdsa"

        Malgré des heures de recherches sur Internet, je n'ai pas résolu le problème.
        Est-ce que vous pourriez m'aider ?
        Par avance, merci.
        Ricardo

          rican Salut,

          Pourrais-tu fournir tes fichiers de configuration pour voir d'où viens le problème, ainsi que la log complète de traefik

          Bonjour,
          Par avance, merci pour votre aide.
          Voici les éléments demandés.

          docker logs traefik
          time="2020-07-02T10:06:19Z" level=info msg="Configuration loaded from flags."
          time="2020-07-02T10:06:19Z" level=info msg="Traefik version 2.2.1 built on 2020-04-29T18:02:09Z"
          time="2020-07-02T10:06:19Z" level=info msg="\nStats collection is disabled.\nHelp us improve Traefik by turning this feature on :)\nMore details on: https://docs.traefik.io/contributing/data-collection/\n"
          time="2020-07-02T10:06:19Z" level=info msg="Starting provider aggregator.ProviderAggregator {}"
          time="2020-07-02T10:06:19Z" level=info msg="Starting provider *file.Provider {\"directory\":\"/etc/traefik/conf.d/\",\"watch\":true}"
          time="2020-07-02T10:06:19Z" level=info msg="Starting provider *traefik.Provider {}"
          time="2020-07-02T10:06:19Z" level=error msg="the router nextcloud-secure@file uses a non-existent resolver: letsencrypt-rsa2048"
          time="2020-07-02T10:06:19Z" level=error msg="the router traefik-secure@file uses a non-existent resolver: letsencrypt-ecdsa"
          time="2020-07-02T10:06:20Z" level=error msg="the router traefik-secure@file uses a non-existent resolver: letsencrypt-ecdsa"
          time="2020-07-02T10:06:20Z" level=error msg="the router nextcloud-secure@file uses a non-existent resolver: letsencrypt-rsa2048"
          #/srv/docker/traefik/docker-compose.yml
          version: "3.8"
          networks:
            traefik:
          services:
            traefik:
              image: traefik:chevrotin
              container_name: traefik
              volumes:
                - /srv/docker/traefik/acme.json:/etc/traefik/acme.json
                - /srv/docker/traefik/certs:/etc/traefik/certs
                - /var/run/docker.sock:/var/run/docker.sock
                - /srv/docker/traefik/conf.d:/etc/traefik/conf.d
              ports:
                - 80:80
                - 443:443
                - 8080:8080 # le temps de tester
              networks:
                - traefik
              command:
                - "--global.sendanonymoususage=false" # désactivation de l'envoi de donnée
                - "--global.checknewversion=false" # puisque dockerisé, on désactive le check de mise à jour
                - "--accesslog=true" # Pour avoir les logs d'accès
                - "--api=true" # Pour activer l'api
                - "--api.insecure=true" # Activer pour exposer l'api sur 8080
                - "--api.dashboard=true" # Pour activer le dashboard
                - "--log.level=INFO"
                - "--providers.file.directory=/etc/traefik/conf.d/" # Permets de charger les configurations dans le répertoire (tout les yaml et toml)
                - "--providers.file.watch=true" # Permets de surveiller le répertoire précédent pour charger dynamiquement les configurations
                - "--entrypoints.web.address=:80" # Création de l'entrypoint nommé web sur le port 80
                - "--entrypoints.websecure.address=:443" # Création de l'entrypoint nommé websecure sur le port 443
                #- "--entrypoints.web.http.redirections.entrypoint.scheme=https" # Pour créer une redirection vers https
                #- "--entrypoints.web.http.redirections.entrypoint.to=websecure" # Pour rediriger vers l'entrypoint websecure (port 443)
            db_nextcloud:
              image: postgres:12
              container_name: db_nextcloud
              networks:
                - traefik
              volumes:
                - /srv/docker/db_nextcloud/:/var/lib/postgresql/
              environment:
                - POSTGRES_PASSWORD=nextcloud
                - POSTGRES_DB=nextcloud
                - POSTGRES_USER=nextcloud
            nextcloud:
              image: nextcloud:19
              container_name: nextcloud
              networks:
                - traefik
              environment:
                - POSTGRES_HOST=db_nextcloud
                - POSTGRES_DB=nextcloud
                - POSTGRES_USER=nextcloud
                - POSTGRES_PASSWORD=nextcloud
                - NEXTCLOUD_ADMIN_USER=admin
                - NEXTCLOUD_ADMIN_PASSWORD=admin
              volumes:
                - /srv/docker/nextcloud:/var/www/html
          #/serv/docker/traefik/conf.d/auth.yml
          http:
            middlewares:
              admin-users:
                basicAuth:
                  users:
                    - "ricardo:xxxxx" # mot de passe masqué
              dev-users:
                basicAuth:
                  users:
                    - "tata:$apr1$inMBbv02$C/oh3LLEfmmOyloAtqW/V/"
          #/srv/docker/traefik/conf.d/compression.yml
          http:
            middlewares:
              compression:
                compress:
                  excludedContentTypes:
                    - "text/event-stream"
          #/srv/docker/traefik/conf.d/hsts.yml
          http:
            middlewares:
              hsts:
                headers:
                  forceSTSHeader: true
                  stsSeconds: 315360000
                  stsIncludeSubdomains: true
                  stsPreload: true
          #/srv/docker/traefik/conf.d/nextcloud.yml
          http:
            services:
              nextcloud:
                loadBalancer:
                  servers:
                    - url: "http://192.168.1.104:80/cloud"
            routers:
              nextcloud:
                rule: "Host(`xxxxx.ovh`) && PathPrefix(`/cloud`)" # domaine masqué
                entryPoints:
                  - "web"
                middlewares:
                  - "redirect-to-https@file"
                service: "noop@internal"
              nextcloud-secure:
                rule: "Host(`xxxxx.ovh`) && PathPrefix(`/cloud`)" # domaine masqué
                entryPoints:
                  - "websecure"
                middlewares:
                  - "hsts@file"
                  - "security@file"
                  - "compression@file"
                  - "strip-cloud@file"
                service: "nextcloud@file"
                tls:
                  certResolver: letsencrypt-rsa2048
            middlewares:
              strip-cloud:
                stripPrefix:
                  prefixes:
                    - "/cloud"
          #/srv/docker/traefik/conf.d/redirect-to-https.yml
          http:
            middlewares:
              redirect-to-https:
                redirectScheme:
                  scheme: https
                  permanent: true
          #/srv/docker/traefik/conf.d/security.yml
          http:
            middlewares:
              security:
                headers:
                  accessControlMaxAge: 100
                  addVaryHeader: true
                  browserXssFilter: true
                  contentTypeNosniff: true
                  frameDeny: true
                  sslRedirect: true
                  customFrameOptionsValue: "SAMEORIGIN"
                  referrerPolicy: "same-origin"
                  featurePolicy: "vibrate 'self'"
          #/srv/docker/traefik/conf.d/tls.yml
          certificatesResolvers: 
            letsencrypt-ecdsa: # Nom arbitraire, je pourrais mettre toto, ça fonctionne correctement
              acme:
                email: "xxx@xxxxx" # email masqué
                caserver: "https://acme-v02.api.letsencrypt.org/directory"
                storage: "/etc/traefik/acme.json"
                keytype: "EC384"
                httpChallenge:
                  entryPoint: "web"
            letsencrypt-rsa2048: # Nom arbitraire, je pourrais mettre toto, ça fonctionne correctement
              acme:
                email: "xxx@xxxxx" # email masqué
                caserver: "https://acme-v02.api.letsencrypt.org/directory"
                storage: "/etc/traefik/acme.json"
                keytype: "RSA2048"
                httpChallenge:
                  entryPoint: "web"
          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:
                  - X25519
                  - CurveP521
                  - CurveP384
                  - CurveP256
              mintls13: # Arbitraire également, toto fonctionne aussi
                minVersion: "VersionTLS13"
          #/serv/docker/traefik/conf.d/traefik.yml
          http:
            services:
              traefik:
                loadBalancer:
                  servers:
                    - url: "http://192.168.1.104:8080"
            routers:
              traefik:
                rule: "Host(`xxxxx.ovh`)" # domaine masqué
                entryPoints:
                  - "web"
                middlewares:
                  - "redirect-to-https@file"
                service: "noop@internal"
              traefik-secure:
                rule: "Host(`xxxxx.ovh`)" # domaine masqué
                entryPoints:
                  - "websecure"
                middlewares:
                  - "hsts@file"
                  - "security@file"
                  - "compression@file"
                  - "admin-users@file"
                service: "traefik@file"
                tls:
                  certResolver: letsencrypt-ecdsa
                  options: mintls13

          Enfin, je précise que la procédure a généré deux dossiers vides

          /serv/docker/traefik/acme.json
          /serv/docker/traefik/certs

            rican Ah le problème viens peux être du acme.json, c'est normalement un fichier, et bizarrement traefik est incapable de le créer.

            Essaie en supprimant le dossier acme.json et en créant un fichier vide (touch acme.json)

            Dans un test précédent, à partir d'un autre tuto, j'avais déjà rencontré le problème du "acme.json" et je l'avais résolu avec les commandes suivantes (trouvées sur Internet)

            sudo touch /srv/docker/traefik/acme.json
            sudo chmod 600 /srv/docker/traefik/acme.json

            Désolé, mais avec ton tuto ça ne fonctionne pas, j'obtiens la même anomalie 😃 !

            Ah merde, je viens de trouver le problème, c'est effectivement un soucis avec mon tuto.

            J'avais la configuration des certResolvers en double, dans le cli, et dans le tls.yml. Mais les certResolver ne fonctionne que dans la configuration static (yml, toml ou cli), et pas dans le providers file.

            Puisque je l'avais en double, forcément dans la rédaction du tuto, ça marchait. Je corrige ça dès que possible

              xataz J'ai vu à l'initialisation de traefik qu'il cherchait un traefik.toml ou yml dans /etc/traefik/, j'ai donc essayé de passer uniquement la config des certResolver dedans mais avoir un fichier lu au démarrage supplante les option passées en CLI donc impossible de faire cohabiter les deux.

              J'ai donc décidé de passer la conf présente dans le docker-compose vers le fichier yml dans /etc/traefik/traefik.yml (penser à ajouter /opt/docker/traefikV2/traefik.yml:/etc/traefik/traefik.yml pour monter le fichier) :

              ## Fichier traefik.yml lu au démarrage
              global:
                checkNewVersion: false
                sendAnonymousUsage: false
              
              providers:
                file:
                  directory: /etc/traefik/conf/
                  watch: true
              
              api:
                insecure: false
                dashboard: true
              
              log:
                level: INFO
                filePath: /etc/traefik/traefik.log
              
              accessLog: true
              
              entryPoints:
                web:
                  address: ":80"
                websecure:
                  address: ":443"
              
              certificatesResolvers:
                letsencrypt-ecdsa:
                  acme:
                    email: test@domain.tld
                    caserver: https://acme-v02.api.letsencrypt.org/directory
                    storage: /etc/traefik/acme.json
                    keytype: EC384
                    httpChallenge:
                      entryPoint: web
                letsencrypt-rsa2048:
                  acme:
                    email: test@domain.tld
                    caserver: https://acme-v02.api.letsencrypt.org/directory
                    storage: /etc/traefik/acme.json
                    keytype: RSA2048
                    httpChallenge:
                      entryPoint: web

              Tout fonctionne, même si j'aurai préféré que traefik puisse gérer les certificatesresolver dynamiquement.