Traefik - certificat wildcard avec Let's Encrypt

Tut's Linux juil. 14, 2020

Avoir un certificat par service/sous-domaine, c'est bien, mais pourquoi pas exploiter la fonctionnalité "wildcard" permettant de générer un seul et unique certificat pour tout vos sous-domaines ? C'est ce que vous allez découvrir ici, la mise en place du certificat SSL wildcard fourni par Let's Encrypt avec Traefik.

Importante mise à jour de l'article par ldez au 15/07/2020 - une nouvelle fois, un grand merci !!

Mise à jour au 15/07/2020 :

  • suppression du middleware "to-https" (redirection configurée dans le fichier traefik.toml au niveau des entryPoints)
  • correction des infos vis-à-vis du SSL = Let's Encrypt
  • file provider changé en répertoire (contrôle en fichier simple déprécié)

Objectif : Configurer Traefik pour obtenir un certificat SSL wildcard via Let's Encrypt pour tous vous services.

Contexte et environnement : Debian 10.4 (buster), Docker 19.03.12, docker-compose 1.26.2, Traefik 2.2.5
Cet article implique une modification côté DNS - assurez-vous d'avoir vos identifiants quant à l'hébergement de votre domaine, y compris les droits nécessaires pour ajouter des enregistrements et générer des clés API. Pour ma part, j'utilise le fournisseur "Gandi" avec la fonctionnalité "LiveDNS". La procédure sera similaire chez d'autres fournisseurs mais pas en revu dans cet article.

Les documentations officielles de Let's Encrypt et Traefik sont les sources de cet article.


Préparation DNS

Plusieurs moyens de vérification sont possibles quant à la génération d'un certificat wildcard - TLS, HTTP ou encore DNS. Des API sont fournies par les hébergeurs, qui seront notamment utilisées par Traefik et l'outil lego (Implémentation de Let's Encrypt pour les outils en GO).

Types de défis
Dernière mise à jour : Feb 24, 2020 | Voir toute la documentation Lorsque vous recevez un certificat de Let’s Encrypt, nos serveurs valident que vous contrôlez les noms de domaine dans ce certificat à l’aide de «défis» tel que défini par le standard ACME. La plupart du temps, cette valid…

Vous devez créer les enregistrements nécessaires de type "A" pour vos services - lorsque c'est terminé, il est temps de générer une clé d'authentification API auprès de votre hébergeur. Si comme moi vous êtes chez Gandi, connectez-vous sur l'adresse "account.gandi.net", dirigez-vous dans l'onglet "Security" et générez une "Production API key". Gardez bien cette clé, si vous la perdez, vous devrez la regénérer.


Configuration Traefik & Docker

J'utiliserai le dossier /opt/docker/dc pour y stocker mes fichiers docker-compose et autres fichiers de configuration. Créons le nécessaire en terme de dossier et de fichiers :

mkdir -p /opt/docker/dc/{conf,logs}
mkdir -p /opt/docker/dc/conf/traefik/dynamic
touch /opt/docker/dc/conf/acme.json
touch /opt/docker/dc/logs/traefik.log
chmod 0600 /opt/docker/dc/conf/acme.json
touch /opt/docker/dc/conf/traefik.yml
touch /opt/docker/dc/conf/traefik/dynamic/dynamic.yml

Vous trouverez les fichiers dans le repos GitHub à cette adresse - GitHub.

Mettmett/docker-compose
Some examples used on ComputerZ Solutions... Contribute to Mettmett/docker-compose development by creating an account on GitHub.

Vous pouvez utiliser SOIT les fichiers .yml ou les fichiers .toml pour Traefik, mais pas les deux types en même temps. Le dépôt est fait de sorte à ce que vous puissiez récupérer directement les fichiers, prêt à l'emploi.

fichier docker-compose.yml (j'utilise les fichier .yml pour Traefik)

---
version: '3.8'
services:
  traefik:
    container_name: traefik
    image: traefik:v2.2.5
    restart: unless-stopped
    environment:
      - "GANDIV5_API_KEY=<INSERT-WITHOUT-HOOK>"
    ports:
      - 80:80
      - 443:443
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - /etc/localtime:/etc/localtime:ro
      - /etc/timezone:/etc/timezone:ro
      - ./conf/traefik.yml:/etc/traefik/traefik.yml:ro
      - ./conf/traefik/dynamic:/etc/traefik/dynamic:ro
      - ./conf/acme.json:/acme.json
      - ./logs/traefik.log:/etc/traefik/applog.log
    labels:
      traefik.enable: true
      traefik.http.routers.traefik-secure.entrypoints: websecure
      traefik.http.routers.traefik-secure.rule: Host(`traefik.domain.local`)
      traefik.http.routers.traefik-secure.middlewares: authentification@file
      traefik.http.routers.traefik-secure.tls: true
      traefik.http.routers.traefik-secure.tls.certresolver: letsencrypt
      traefik.http.routers.traefik-secure.tls.domains[0].main: domain.local
      traefik.http.routers.traefik-secure.tls.domains[0].sans: "*.domain.local"
      traefik.http.routers.traefik-secure.service: api@internal

  portainer:
    container_name: portainer
    image: portainer/portainer:1.24.0
    restart: unless-stopped
    depends_on:
      - traefik
    command: -H unix:///var/run/docker.sock
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - /etc/localtime:/etc/localtime:ro
      - /etc/timezone:/etc/timezone:ro
      - dataportainer:/data
    labels:
      traefik.enable: true
      traefik.http.routers.portainer.entrypoints: websecure
      traefik.http.routers.portainer.rule: Host(`portainer.domain.local`)
      traefik.http.routers.portainer.middlewares: security@file
      traefik.http.routers.portainer.tls: true

volumes:
  dataportainer:

fichier conf/traefik.yml

---
global:
  sendAnonymousUsage: false
  checkNewVersion: false
  #insecureSNI: false

api:
  #insecure: true
  dashboard: true
  #debug: true

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

providers:
  docker:
    endpoint: unix:///var/run/docker.sock
    exposedByDefault: false
    watch: true
    swarmMode: false

  file:
    directory: /etc/traefik/dynamic
    watch: true

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

certificatesResolvers:
  letsencrypt:
    acme:
      email: contact@domain.local
#      caServer: https://acme-staging-v02.api.letsencrypt.org/directory
      caServer: https://acme-v02.api.letsencrypt.org/directory
      storage: acme.json
      keyType: EC256
      dnsChallenge:
        provider: gandiv5
        resolvers:
          - "1.1.1.1:53"
          - "8.8.8.8:53"

fichier conf/traefik_dynamic.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:
        - CurveP521
        - CurveP384

http:
  middlewares:
    security:
      headers:
        accessControlAllowMethods:
          - GET
          - OPTIONS
          - PUT
        #accessControlAllowOriginList = "*"
        accessControlMaxAge: 100
        addVaryHeader: true
        browserXssFilter: true
        contentTypeNosniff: true
        forceSTSHeader: true
        frameDeny: true
        sslRedirect: true
        sslForceHost: true
        stsIncludeSubdomain: true
        stsPreload: true
        #ContentSecurityPolicy = "default-src 'self' 'unsafe-inline'"
        customFrameOptionsValue: SAMEORIGIN
        referrerPolicy: same-origin
        featurePolicy: vibrate 'self'
        stsSeconds: 315360000

J'attire votre attention dans le fichier traefik.yml/toml : il y a deux lignes caServer, pointant vers des serveurs Let's Encrypt - pendant vos tests, pour éviter un blocage et un banissement du service, utilisez uniquement la ligne "caServer ... acme-staging". Lorsque vos tests seront validés (certificats correctement récupérés et générés), vous pourrez commenter la ligne "caServer ... acme-staging" et reprendre "caServer ... acme-v02".

En bref :

  • Le fichier docker-compose comporte la clé API précédemment générée, dans une variable d'environnement pour Traefik.
  • Le fichier acme.json est toujours présent avec les droits requis
  • Les labels TLS pour les services sont beaucoup plus courts et plus simple !! Tout se trouve dans les fichiers de configuration de Traefik.

Chaque service que vous ajouterez dans votre docker-compose.yml devra comporter ces labels :

    labels:
      traefik.enable: true
      traefik.http.routers.SERVICENAME.entrypoints: websecure
      traefik.http.routers.SERVICENAME.rule: Host(`SERVICENAME.domain.local`)
      traefik.http.routers.SERVICENAME.middlewares: security@file
      traefik.http.routers.SERVICENAME.tls: true

Le fichier acme.json doit maintenant comporter quelques lignes pour votre certificat - fini le .json de 93248 lignes !
Dans votre navigateur, vous pouvez contrôler l'authenticité du certificat généré, qui doit avoir ce genre d'informations :

Vers le milieu de l'image, le plus important est présent : *.computerz.solutions, vous confirmant qu'il s'agit d'un certificat wildcard.
A vous de jouer..!

Mots clés

Julien HOMMET

Bercé par l'informatique depuis mon plus jeune âge, je transforme ma passion en expertise.

Super ! Vous vous êtes inscrit avec succès.
Super ! Effectuez le paiement pour obtenir l'accès complet.
Bon retour parmi nous ! Vous vous êtes connecté avec succès.
Parfait ! Votre compte est entièrement activé, vous avez désormais accès à tout le contenu.