Cet article explicite la génération d'un certificat SSL de type 'wildcard' généré gratuitement par Let's Encrypt. Avoir un certificat par service ou sous-domaine c'est bien, mais allons plus loin et exploitons la fonctionnalité wildcard permettant de générer un unique certificat pour tous les sous-domaines.

Importante mise Ă  jour de l'article par ldez au 15/07/2020. Une nouvelle fois, un grand merci !!
Version Date Commentaires
1 07/2020 Création de l'article
1.1 05/2022 Reformatage de l'article
1.2 07/2022 Mise Ă  jour des informations

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

Environnement : Debian 11.2, Docker 20.10.x, docker-compose 2.4.x, Traefik 2.8

Cet article implique une modification dans votre zone DNS. Assurez-vous d'avoir vos identifiants quant à l'accÚs au service ou serveur DNS, y compris les droits nécessaires pour ajouter des enregistrements et générer des clés API. Ici, j'utiliserai le fournisseur "Gandi" avec la fonctionnalité "LiveDNS". La procédure est similaire avec d'autres gestionnaires de service DNS, mais ne sera pas vu dans cet article.

Préparation du 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 au travers de l'outil lego.

Vous devez crĂ©er les enregistrements de type "A" nĂ©cessaires pour vos services. Ensuite, gĂ©nĂ©rez 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 cette clĂ© prĂ©cieusement, si vous la perdez, vous devrez la rĂ©gĂ©nĂ©rer.

Configurations pour Docker et Traefik

J'utiliserai le dossier /opt/docker/dc pour y stocker les fichiers YAML docker-compose et les fichiers de configuration de Traefik. Créons le nécessaire :

mkdir -p /opt/docker/dc/{conf,logs}
mkdir -p /opt/docker/dc/conf/traefikdynamic

touch /opt/docker/dc/conf/acme.json
chmod 0600 /opt/docker/dc/conf/acme.json

touch /opt/docker/dc/logs/traefik.log /opt/docker/dc/conf/traefik.yml /opt/docker/dc/conf/traefikdynamic/dynamic.yml

L'exemple ci-dessous est dit "configuration statique", Ă  savoir l'utilisation de labels dans le fichier docker-compose. Ci-dessous le fichier docker-compose.yml :

---
services:
  traefik:
    image: traefik:2.8
    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
      - ./conf/traefik.yml:/etc/traefik/traefik.yml:ro
      - ./conf/traefikdynamic:/etc/traefik/dynamic:ro
      - ./conf/acme.json:/acme.json
    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.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-ce:2.14.0
    restart: unless-stopped
    depends_on:
      - traefik
    command: -H unix:///var/run/docker.sock
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock: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

api:
  dashboard: true

pilot:
  dashboard: false

log:
  level: ERROR

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
      alpnProtocols:
        - h2
        - http/1.1
    mintls13:
      minVersion: VersionTLS13

http:
  middlewares:
    compression:
      compress:
        excludedContentTypes:
          - text/event-stream
 
    security:
      headers:
        accessControlAllowMethods:
          - GET
          - OPTIONS
          - PUT
        accessControlMaxAge: 100
        addVaryHeader: true
        browserXssFilter: true
        contentTypeNosniff: true
        forceSTSHeader: true
        frameDeny: true
        stsPreload: true
        customFrameOptionsValue: SAMEORIGIN
        referrerPolicy: "origin-when-cross-origin"
        permissionsPolicy: "camera 'none'; microphone 'none'; geolocation 'none'; payment 'none';"
        stsSeconds: 315360000
        hostsProxyHeaders:
          - "X-Forwarded-Host"

J'attire votre attention à propos du fichier traefik.yml⁣ : il y a deux lignes caServer, pointant vers des serveurs Let's Encrypt. Pendant vos tests, pour éviter un bannissement auprÚs de Let's Encrypt (nombre maximal de demandes par semaine), 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". N'oubliez pas de redémarrer Traefik pour prendre en compte les modifications et régénérer les certificats "de production".

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 simples ! Tout se trouve dans la 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. Avec cette configuration, vos services seront à présent derriÚre Traefik, seront accessibles via SSL (et utiliseront le certificat SSL wildcard), et bénéficieront des options TLS.

Sources : Traefik et Let's Encrypt

Partager l'article