Traefik – certificat wildcard avec Let’s Encrypt
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é)
Mise à jour au 28/09/2020 :
- montée en version de traefik vers « picodon » (v2.3)
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).
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.
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.6'
services:
traefik:
container_name: traefik
image: traefik:picodon
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/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: [email protected]
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: [email protected]
portainer:
container_name: portainer
image: portainer/portainer-ce:2.0.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: [email protected]
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: [email protected]
# 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
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: [email protected]
traefik.http.routers.SERVICENAME.tls: true
Le fichier acme.json
doit maintenant comporter quelques lignes pour votre certificat – fini le .json de 93248 lignes !
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..!