Aller au contenu
Sécurité medium

Renouveler automatiquement des certificats TLS avec Vault PKI et Traefik

22 min de lecture

Ce guide montre comment automatiser le renouvellement de certificats TLS sans redémarrage de service. Vous allez déployer Traefik avec des certificats émis par Vault PKI, renouvelés automatiquement par Vault Agent, et rechargés dynamiquement par Traefik.

À la fin de ce guide, vous saurez :

  • Configurer Vault PKI pour émettre des certificats courts (5 min pour la démo)
  • Utiliser Vault Agent pour écrire cert.pem et key.pem sur disque
  • Configurer Traefik pour recharger les certificats sans restart
  • Observer le renouvellement automatique en action

Prérequis : Docker Compose, un terminal Linux/macOS.

Pourquoi combiner Vault PKI, Vault Agent et Traefik ?

Section intitulée « Pourquoi combiner Vault PKI, Vault Agent et Traefik ? »

La gestion classique des certificats TLS pose plusieurs problèmes :

ProblèmeConséquence
Certificats longue durée (1-2 ans)Fenêtre d’exploitation large si compromis
Renouvellement manuelOublis, expirations, interruptions de service
Clés privées dans les repos/configsRisque de fuite
Redémarrage requis pour rechargerDowntime lors du renouvellement

Avec Vault PKI + Vault Agent + Traefik :

AspectAvantAprès
Durée de vie1-2 ans24h-72h (ou moins)
RenouvellementManuelAutomatique
Impact sur le serviceRestart requisAucun (rechargement dynamique)
Stockage des clésStatiqueÉphémère (généré à la demande)

Voici comment les composants interagissent :

Architecture Vault PKI + Vault Agent + Traefik

Le flux complet :

  1. Vault PKI émet un certificat TLS avec un TTL court
  2. Vault Agent écrit cert.pem, key.pem et met à jour tls.yml
  3. Traefik détecte le changement de tls.yml et recharge les certificats
  4. Le service (whoami) reste accessible sans interruption
  5. Avant expiration, Vault Agent répète le cycle

Traefik distingue deux types de configuration :

TypeExemplesRechargeable à chaud ?
StatiqueEntrypoints, providers, logs❌ Non (restart requis)
DynamiqueRouters, services, middlewares, TLS✅ Oui

Les certificats TLS font partie de la configuration dynamique. Traefik peut les recharger sans redémarrage grâce au file provider avec l’option watch: true.

  1. Créer les répertoires :

    Fenêtre de terminal
    mkdir -p lab-vault-traefik/{vault-agent,traefik/{dynamic,certs},scripts}
    cd lab-vault-traefik
  2. Créer le fichier docker-compose.yml :

    docker-compose.yml
    services:
    # Vault en mode dev pour la démo
    vault:
    image: hashicorp/vault:1.18
    container_name: vault
    cap_add:
    - IPC_LOCK
    environment:
    VAULT_DEV_ROOT_TOKEN_ID: root
    VAULT_DEV_LISTEN_ADDRESS: 0.0.0.0:8200
    VAULT_ADDR: http://127.0.0.1:8200
    ports:
    - "8200:8200"
    networks:
    - vault-traefik
    healthcheck:
    test: ["CMD", "vault", "status"]
    interval: 5s
    timeout: 3s
    retries: 10
    # Service de démo
    whoami:
    image: traefik/whoami:latest
    container_name: whoami
    networks:
    - vault-traefik
    # Traefik avec file provider
    traefik:
    image: traefik:v3.3
    container_name: traefik
    command:
    - "--api.insecure=true"
    - "--providers.file.directory=/etc/traefik/dynamic"
    - "--providers.file.watch=true"
    - "--entrypoints.web.address=:80"
    - "--entrypoints.websecure.address=:443"
    - "--log.level=INFO"
    ports:
    - "9080:80"
    - "9443:443"
    - "9088:8080" # Dashboard
    volumes:
    - ./traefik/dynamic:/etc/traefik/dynamic:ro
    - ./traefik/certs:/etc/traefik/certs:ro
    networks:
    - vault-traefik
    depends_on:
    - whoami
    # Vault Agent pour le renouvellement des certificats
    vault-agent:
    image: hashicorp/vault:1.18
    container_name: vault-agent
    entrypoint: ["vault", "agent", "-config=/etc/vault-agent/config.hcl"]
    environment:
    VAULT_ADDR: http://vault:8200
    volumes:
    - ./vault-agent:/etc/vault-agent:ro
    - ./traefik/certs:/output/certs
    - ./traefik/dynamic:/output/dynamic
    - vault-agent-token:/run/vault
    networks:
    - vault-traefik
    depends_on:
    vault:
    condition: service_healthy
    networks:
    vault-traefik:
    driver: bridge
    volumes:
    vault-agent-token:
  3. Créer un certificat placeholder pour le démarrage initial :

    Fenêtre de terminal
    openssl req -x509 -nodes -days 1 -newkey rsa:2048 \
    -keyout traefik/certs/key.pem \
    -out traefik/certs/cert.pem \
    -subj "/CN=placeholder.local.test"

Ce certificat temporaire permet à Traefik de démarrer. Vault Agent le remplacera par un vrai certificat signé par Vault PKI.

Créez le fichier de routes pour le service whoami :

traefik/dynamic/routes.yml
http:
routers:
whoami:
rule: Host(`whoami.local.test`)
service: whoami
entryPoints:
- websecure
tls: {}
# Redirection HTTP vers HTTPS
whoami-redirect:
rule: Host(`whoami.local.test`)
service: whoami
entryPoints:
- web
middlewares:
- redirect-to-https
middlewares:
redirect-to-https:
redirectScheme:
scheme: https
permanent: true
services:
whoami:
loadBalancer:
servers:
- url: http://whoami:80

Vault Agent utilise AppRole pour s’authentifier auprès de Vault et des templates pour écrire les certificats sur disque.

vault-agent/config.hcl
vault {
address = "http://vault:8200"
}
auto_auth {
method "approle" {
mount_path = "auth/approle"
config = {
role_id_file_path = "/etc/vault-agent/role_id"
secret_id_file_path = "/etc/vault-agent/secret_id"
remove_secret_id_file_after_reading = false
}
}
sink "file" {
config = {
path = "/run/vault/token"
mode = 0644
}
}
}
# Template pour le certificat (cert.pem)
template {
source = "/etc/vault-agent/cert.tpl"
destination = "/output/certs/cert.pem"
perms = 0644
}
# Template pour la clé privée (key.pem)
template {
source = "/etc/vault-agent/key.tpl"
destination = "/output/certs/key.pem"
perms = 0600
}
# Template TLS pour Traefik - force le rechargement du file provider
template {
source = "/etc/vault-agent/traefik-tls.tpl"
destination = "/output/dynamic/tls.yml"
perms = 0644
}
template_config {
static_secret_render_interval = "5m"
exit_on_retry_failure = true
}

Ces templates demandent un nouveau certificat à Vault PKI et extraient les données nécessaires.

Template pour le certificat (cert.pem) :

vault-agent/cert.tpl
{{- with secret "pki_int/issue/traefik-web" "common_name=whoami.local.test" "alt_names=whoami.local.test" "ttl=5m" -}}
{{ .Data.certificate }}
{{ .Data.issuing_ca }}
{{- end -}}

Template pour la clé privée (key.pem) :

vault-agent/key.tpl
{{- with secret "pki_int/issue/traefik-web" "common_name=whoami.local.test" "alt_names=whoami.local.test" "ttl=5m" -}}
{{ .Data.private_key }}
{{- end -}}

Template pour la config TLS Traefik (tls.yml) :

vault-agent/traefik-tls.tpl
{{- with secret "pki_int/issue/traefik-web" "common_name=whoami.local.test" "alt_names=whoami.local.test" "ttl=5m" -}}
# Configuration TLS - regénéré automatiquement par Vault Agent
# Serial: {{ .Data.serial_number }}
# Expiration: {{ .Data.expiration }}
tls:
certificates:
- certFile: /etc/traefik/certs/cert.pem
keyFile: /etc/traefik/certs/key.pem
stores:
default:
defaultCertificate:
certFile: /etc/traefik/certs/cert.pem
keyFile: /etc/traefik/certs/key.pem
{{- end -}}

Ce script configure Vault PKI (Root CA + Intermediate CA), crée un rôle pour Traefik, et génère les credentials AppRole pour Vault Agent.

scripts/init-vault.sh
#!/bin/bash
set -e
echo "=== Configuration Vault PKI + AppRole pour Traefik ==="
# Fonction pour exécuter vault via docker
vault() {
docker exec -e VAULT_ADDR=http://127.0.0.1:8200 -e VAULT_TOKEN=root vault vault "$@"
}
# 1. Activer et configurer PKI Root CA
echo ">>> 1. Activation PKI Root CA..."
vault secrets enable -path=pki pki 2>/dev/null || echo " (pki déjà activé)"
vault secrets tune -max-lease-ttl=87600h pki
vault write -field=certificate pki/root/generate/internal \
common_name="Demo Root CA" \
issuer_name="root-ca" \
ttl=87600h > /dev/null
vault write pki/config/urls \
issuing_certificates="http://vault:8200/v1/pki/ca" \
crl_distribution_points="http://vault:8200/v1/pki/crl"
echo " ✓ PKI Root CA configuré"
# 2. Activer et configurer PKI Intermediate CA
echo ">>> 2. Activation PKI Intermediate CA..."
vault secrets enable -path=pki_int pki 2>/dev/null || echo " (pki_int déjà activé)"
vault secrets tune -max-lease-ttl=43800h pki_int
vault write -field=csr pki_int/intermediate/generate/internal \
common_name="Demo Intermediate CA" \
issuer_name="intermediate-ca" \
> /tmp/pki_int.csr
vault write -field=certificate pki/root/sign-intermediate \
csr=@/tmp/pki_int.csr \
format=pem_bundle \
ttl="43800h" \
> /tmp/int.cert.pem
vault write pki_int/intermediate/set-signed \
certificate=@/tmp/int.cert.pem
echo " ✓ PKI Intermediate CA configuré"
# 3. Créer le rôle PKI pour Traefik
echo ">>> 3. Création du rôle PKI 'traefik-web'..."
vault write pki_int/roles/traefik-web \
allowed_domains="local.test" \
allow_subdomains=true \
allow_bare_domains=false \
max_ttl="24h" \
ttl="5m" # TTL court pour la démo
echo " ✓ Rôle traefik-web créé (TTL: 5 min)"
# 4. Créer la policy pour Vault Agent
echo ">>> 4. Création de la policy 'traefik-pki'..."
vault policy write traefik-pki - <<POLICY
path "pki_int/issue/traefik-web" {
capabilities = ["create", "update"]
}
POLICY
echo " ✓ Policy traefik-pki créée"
# 5. Activer et configurer AppRole
echo ">>> 5. Configuration AppRole..."
vault auth enable approle 2>/dev/null || echo " (approle déjà activé)"
vault write auth/approle/role/traefik-agent \
token_policies="traefik-pki" \
token_ttl=1h \
token_max_ttl=4h \
secret_id_ttl=0
echo " ✓ Rôle AppRole traefik-agent créé"
# 6. Récupérer les credentials AppRole
echo ">>> 6. Génération des credentials AppRole..."
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROLE_ID=$(vault read -field=role_id auth/approle/role/traefik-agent/role-id)
SECRET_ID=$(vault write -f -field=secret_id auth/approle/role/traefik-agent/secret-id)
echo "$ROLE_ID" > "$SCRIPT_DIR/../vault-agent/role_id"
echo "$SECRET_ID" > "$SCRIPT_DIR/../vault-agent/secret_id"
echo " ✓ Credentials sauvegardés dans vault-agent/"
echo ""
echo "=== Configuration terminée ==="
echo ""
echo "Prochaines étapes :"
echo " 1. docker compose up -d vault-agent"
echo " 2. Ajouter '127.0.0.1 whoami.local.test' à /etc/hosts"
echo " 3. curl -k https://whoami.local.test:9443"

Rendez le script exécutable :

Fenêtre de terminal
chmod +x scripts/init-vault.sh
  1. Démarrer Vault, Traefik et whoami :

    Fenêtre de terminal
    docker compose up -d vault whoami traefik

    Attendez que Vault soit healthy :

    Fenêtre de terminal
    docker compose ps
  2. Configurer Vault PKI :

    Fenêtre de terminal
    ./scripts/init-vault.sh

    Vous devriez voir :

    === Configuration terminée ===
    Prochaines étapes :
    1. docker compose up -d vault-agent
    2. Ajouter '127.0.0.1 whoami.local.test' à /etc/hosts
    3. curl -k https://whoami.local.test:9443
  3. Démarrer Vault Agent :

    Fenêtre de terminal
    docker compose up -d vault-agent
  4. Vérifier les certificats générés :

    Fenêtre de terminal
    ls -la traefik/certs/

    Vous devriez voir cert.pem et key.pem avec des timestamps récents.

  5. Ajouter l’entrée DNS locale :

    Fenêtre de terminal
    echo "127.0.0.1 whoami.local.test" | sudo tee -a /etc/hosts

Examinez le certificat émis par Vault PKI :

Fenêtre de terminal
openssl x509 -in traefik/certs/cert.pem -noout -subject -issuer -dates

Résultat attendu :

subject=CN = whoami.local.test
issuer=CN = Demo Intermediate CA
notBefore=Mar 16 16:56:40 2026 GMT
notAfter=Mar 16 17:01:40 2026 GMT

Le certificat est signé par “Demo Intermediate CA” et valide 5 minutes.

Fenêtre de terminal
curl -k https://whoami.local.test:9443

Résultat :

Hostname: 9b134b252f36
IP: 127.0.0.1
IP: 172.23.0.3
RemoteAddr: 172.23.0.4:44418
GET / HTTP/1.1
Host: whoami.local.test:9443
...

Exécutez ce script pour observer le renouvellement :

Fenêtre de terminal
echo "=== Observation du renouvellement ==="
LAST_SERIAL=""
while true; do
SERIAL=$(openssl s_client -connect whoami.local.test:9443 \
-servername whoami.local.test </dev/null 2>/dev/null \
| openssl x509 -noout -serial | cut -d= -f2)
DATES=$(openssl s_client -connect whoami.local.test:9443 \
-servername whoami.local.test </dev/null 2>/dev/null \
| openssl x509 -noout -enddate | cut -d= -f2)
NOW=$(date "+%H:%M:%S")
if [[ "$SERIAL" != "$LAST_SERIAL" ]] && [[ -n "$LAST_SERIAL" ]]; then
echo ""
echo ">>> CERTIFICAT RENOUVELÉ ! <<<"
echo ""
fi
LAST_SERIAL="$SERIAL"
echo "[$NOW] Serial: ${SERIAL:0:20}... | Expire: $DATES"
sleep 30
done

Après environ 3-4 minutes, vous verrez le certificat changer :

[17:57:07] Serial: 2F92FE700EA7F45AFAA8... | Expire: Mar 16 17:01:39 2026 GMT
[17:57:37] Serial: 2F92FE700EA7F45AFAA8... | Expire: Mar 16 17:01:39 2026 GMT
[17:58:07] Serial: 2F92FE700EA7F45AFAA8... | Expire: Mar 16 17:01:39 2026 GMT
>>> CERTIFICAT RENOUVELÉ ! <<<
[17:58:37] Serial: 5193A369CE35F6173E6F... | Expire: Mar 16 17:06:11 2026 GMT
Fenêtre de terminal
docker compose logs vault-agent --tail=20 | grep rendered

Vous verrez les moments de renouvellement :

vault-agent | 2026-03-16T16:56:40.091Z [INFO] agent: (runner) rendered "/etc/vault-agent/cert.tpl" => "/output/certs/cert.pem"
vault-agent | 2026-03-16T16:56:40.094Z [INFO] agent: (runner) rendered "/etc/vault-agent/key.tpl" => "/output/certs/key.pem"
vault-agent | 2026-03-16T16:56:40.096Z [INFO] agent: (runner) rendered "/etc/vault-agent/traefik-tls.tpl" => "/output/dynamic/tls.yml"
vault-agent | 2026-03-16T17:01:11.620Z [INFO] agent: (runner) rendered "/etc/vault-agent/cert.tpl" => "/output/certs/cert.pem"
vault-agent | 2026-03-16T17:01:11.623Z [INFO] agent: (runner) rendered "/etc/vault-agent/key.tpl" => "/output/certs/key.pem"
vault-agent | 2026-03-16T17:01:11.625Z [INFO] agent: (runner) rendered "/etc/vault-agent/traefik-tls.tpl" => "/output/dynamic/tls.yml"
ComposantResponsabilité
Vault PKIÉmet des certificats courts, signés par une CA interne
Vault AgentDemande et renouvelle les certificats, écrit sur disque
TraefikSert le trafic HTTPS, recharge les certificats dynamiquement
whoamiService de démo, ne connaît pas l’existence de Vault

Points clés :

  • Le service (whoami) n’a aucune dépendance directe sur Vault
  • Traefik recharge les certificats sans redémarrage
  • Vault Agent gère tout le cycle de vie des certificats
  • Si Vault est temporairement indisponible, les certificats existants restent valides jusqu’à expiration
AspectRecommandation
Clé privéeProtéger key.pem avec les permissions 0600
AppRole secretsNe pas committer role_id/secret_id dans Git
Vault en mode devUtiliser un cluster Vault en production avec TLS
RisqueMitigation
Vault down au démarrageLe certificat existant reste valide jusqu’à expiration
Vault down au renouvellementVault Agent réessaie. Prévoir un TTL assez long
Perte du stockage VaultBackups réguliers, cluster HA
ParamètreRecommandation production
TTL certificat24h-72h (pas trop court pour éviter les paniques)
max_ttl7-30 jours selon les besoins
allowed_domainsRestreindre aux domaines réellement utilisés

Modifiez le rôle PKI pour un TTL de 24h :

Fenêtre de terminal
vault write pki_int/roles/traefik-web \
allowed_domains="votre-domaine.com" \
allow_subdomains=true \
max_ttl="168h" \ # 7 jours max
ttl="24h" # 24h par défaut

Mettez à jour les templates avec votre domaine :

vault-agent/cert.tpl
{{- with secret "pki_int/issue/traefik-web" "common_name=app.votre-domaine.com" "alt_names=app.votre-domaine.com,api.votre-domaine.com" "ttl=24h" -}}
{{ .Data.certificate }}
{{ .Data.issuing_ca }}
{{- end -}}

En environnement Kubernetes, remplacez AppRole par l’auth Kubernetes :

vault-agent/config.hcl (Kubernetes)
auto_auth {
method "kubernetes" {
mount_path = "auth/kubernetes"
config = {
role = "traefik-pki"
}
}
# ...
}
  1. Vault PKI génère des certificats TLS courts et signés
  2. Vault Agent renouvelle automatiquement avant expiration
  3. Traefik recharge les certificats sans redémarrage grâce au file provider
  4. Le template tls.yml force Traefik à détecter le changement
  5. Le service backend n’a aucune dépendance sur Vault
  6. En production, utilisez des TTL de 24-72h et un Vault sécurisé

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn