Le rapport d’incident commence toujours de la même façon : « Il se peut que des identifiants aient été exposés. » Ce n’est jamais « ont été » au départ.
On tourne autour, on essaie d’acheter de la certitude avec du temps. Puis quelqu’un trouve la preuve : un .env intégré dans une couche d’image,
affiché par une instruction de débogage, ou téléchargé avec un ticket de support parce que « c’était plus simple ».
Les conteneurs n’ont pas créé l’écume des secrets. Ils l’ont juste accélérée. Si vous utilisez encore les variables d’environnement (ou pire, .env)
comme principal mécanisme de distribution des secrets, vous ne « gardez pas ça simple ». Vous rédigez votre propre post-mortem à l’avance.
Ce qui tourne mal avec .env et les variables d’environnement
Soyons précis. Les variables d’environnement ne sont pas « intrinsèquement peu sûres ». Elles sont intrinsèquement volages.
Elles se propagent. Elles sont copiées, consignées et affichées à des endroits où vous ne vouliez pas les montrer. La différence importe.
1) Le runtime du conteneur traite l’env comme des métadonnées, et les métadonnées aiment voyager
L’environnement d’un conteneur finit dans plusieurs endroits : dans la définition de l’orchestrateur, dans la sortie d’inspection, dans la liste des processus,
dans les dump de crash et sur des pages « utiles » de débogage. Certains de ces endroits ont leurs propres contrôles d’accès. Beaucoup n’en ont pas.
2) Les variables env sont faciles à exfiltrer une fois que vous avez n’importe quelle prise
Si un attaquant obtient l’exécution de code à l’intérieur d’un conteneur, lire des fichiers sous un montage de secret dédié peut encore demander un effort,
mais lire l’env est trivial : c’est déjà là, hérité par le processus, habituellement lisible par ce processus, et souvent affiché par erreur par les outils standards.
3) Les fichiers .env sont un piège de workflow
Un fichier .env rend le développement local agréable. Il rend aussi « il suffit de le commit pour la démo » à un message Slack près.
Même si votre dépôt est privé, les secrets ne restent pas privés. Ils sont forkés, miroirs, mis en cache et clonés sur des portables
qui voyagent dans des aéroports et des cafés.
L’un des modes de défaillance les plus courants n’est pas un attaquant. C’est un ingénieur surchargé ajoutant printenv pour déboguer,
puis oubliant de l’enlever. C’est ainsi que votre mot de passe de base de données « privé » finit dans des logs centralisés, consultables par quiconque
a accès en lecture au système de logs.
Petite blague n°1 : Si votre secret est dans .env, ce n’est pas un secret ; c’est une humeur.
4) Les couches d’image et les caches de build vous trahiront
Si vous passez des secrets en tant qu’arguments de build ou copiez .env dans le contexte de build, vous pouvez vous retrouver avec des identifiants
à l’intérieur des couches d’image. Même si vous les supprimez ensuite, l’historique des couches peut encore les contenir. Vous ne vous « rm -f »z pas hors d’un
registre adressé par contenu.
5) Les variables env estompent la frontière entre configuration et matériel secret
Un numéro de port est de la configuration. Un mot de passe de base de données est du matériel secret. Traitez-les différemment. Le problème opérationnel est que
ENV= ressemble à la même chose pour les deux, donc les équipes finissent par donner aux secrets le même cycle de vie que la config : commit dans le repo, templating
dans le CI, copie entre environnements. C’est comme ça que les secrets dev deviennent des secrets prod, et que les secrets prod finissent sur des portables dev.
Faits et contexte historique (les choses qu’on réapprend sans cesse)
Voici des faits concrets et des points de contexte qui comptent quand vous décidez comment gérer les secrets, et pourquoi « mais tout le monde utilise les vars d’env »
n’est pas un argument sérieux.
-
Le « 12-factor app » a popularisé les vars d’env pour la config afin de garder les builds immuables et les déploiements portables. Ce n’était pas
écrit comme une approbation générale pour des secrets de production de longue durée. -
Docker Swarm a introduit les secrets intégrés (comme montages de fichiers) pour répondre au problème réel de fuite d’env via
docker inspectet les logs. -
Les Kubernetes Secrets étaient historiquement juste encodés en base64 par défaut ; le chiffrement au repos nécessitait une configuration explicite.
L’industrie a réappris que « encodé » n’est pas « chiffré ». -
Les systèmes de build se sont améliorés parce qu’ils ont dû le faire : BuildKit a ajouté des montages de secrets spécifiquement parce que passer des secrets
en tant qu’args de build les fuyait dans les couches d’image. -
Les registres sont pour toujours : une fois qu’un secret atteint une couche de registre ou un cache, supprimer le tag n’enlève pas de façon fiable les données
de tous les miroirs et caches. -
Les environnements de processus sont observables sur de nombreux systèmes. Sur Linux, l’environnement d’un processus peut être lu via
/proc/<pid>/environpar des utilisateurs suffisamment privilégiés. Vous n’avez pas besoin d’être « root » au sens abstrait ; vous avez besoin
de la bonne combinaison de capacités et d’accès aux namespaces. -
Les systèmes de journalisation ont changé le rayon d’impact : un mot de passe divulgué dans les logs d’un conteneur devient maintenant un artefact
consultable répliqué entre clusters et niveaux de rétention. -
La rotation des secrets est une fonctionnalité de fiabilité, pas seulement du théâtre de sécurité. Les équipes qui ne font jamais de rotation l’apprennent
pendant un incident, sous la pire pression temporelle possible.
Un modèle de menace pratique : comment les secrets fuient dans les systèmes conteneurisés
La modélisation de menace n’a pas besoin d’un tableau blanc et d’un atelier de trois jours. Il faut lister les façons dont les secrets s’échappent, et décider lesquelles
vous pouvez prévenir, lesquelles vous pouvez détecter, et lesquelles vous ne pouvez que réduire.
Chemin de fuite A : contrôle de source et prolifération d’artefacts
- Où ça arrive :
.envcommité, fichier d’exemple copié avec des valeurs réelles, branche « temporaire », paste interne sur un wiki. - Pourquoi ça arrive : commodité, confusion entre config et secret, absence de barrières de scan.
- Type de correction : prévenir (gitignore + scanning + politique) et détecter (scanners de secrets, surveillance de dépôts).
Chemin de fuite B : logs CI et métadonnées de build
- Où ça arrive : étape de pipeline qui echo l’env, tests qui impriment des chaînes de connexion, args de build stockés dans les logs.
- Pourquoi ça arrive : « débogage », verbosité mal configurée, scripts naïfs.
- Type de correction : prévenir (masquage, pas d’echo, montages fichiers) et détecter (nettoyage des logs et alerting).
Chemin de fuite C : inspection de conteneur et APIs d’orchestration
- Où ça arrive :
docker inspect, fichiers Compose, specs de service Swarm, specs de pod Kubernetes. - Pourquoi ça arrive : l’accès aux métadonnées est plus large que l’accès que devraient avoir les secrets.
- Type de correction : prévenir (utiliser des objets secret) et réduire (resserrer le RBAC pour inspect/describe).
Chemin de fuite D : compromission en runtime et mouvement latéral
- Où ça arrive : RCE dans l’app, SSRF vers des endpoints metadata, endpoints de debug exposés.
- Pourquoi ça arrive : les applications cassent.
- Type de correction : réduire (moindre privilège, identifiants courts, identité séparée par service) et détecter (détection d’anomalies).
Chemin de fuite E : sauvegardes et snapshots
- Où ça arrive : volumes avec secrets intégrés, dumps de base de données contenant des identifiants, snapshots de système de fichiers.
- Pourquoi ça arrive : secrets stockés comme fichiers ordinaires sans contrôles de cycle de vie.
- Type de correction : prévenir (ne pas stocker les secrets dans les chemins de données applicatifs), réduire (chiffrer les sauvegardes), détecter (audit).
Voici la vérité opérationnelle : vous ne protégez pas seulement les secrets des attaquants. Vous les protégez aussi de la tendance de vos propres systèmes
à tout copier, cacher et indexer.
Une citation, parce qu’elle s’applique aux secrets autant qu’aux pannes : « L’espoir n’est pas une stratégie. »
— Vince Lombardi
Que faire à la place : secrets fichiers, secrets Docker et bons paramètres par défaut
L’objectif n’est pas la pureté. L’objectif est de réduire la probabilité et le rayon d’impact des fuites. Le meilleur défaut sur les plateformes conteneurs est :
monter les secrets comme fichiers, les tenir hors des couches d’image, hors des logs, et faire de la rotation une étape normale du déploiement.
Option 1 : secrets Docker Swarm (toujours utile même si vous n’utilisez pas « Swarm »)
Les secrets Docker dans Swarm sont un mécanisme de première classe : chiffrés au repos dans le journal raft du Swarm, livrés aux tâches via mutual TLS,
et montés dans les conteneurs comme fichiers en mémoire (typiquement sous /run/secrets). Ils ne sont pas visibles dans
docker inspect comme le sont les vars d’env.
Le grand avantage : les secrets sont des données avec un cycle de vie, pas des chaînes saupoudrées dans du YAML.
Option 2 : secrets Docker Compose (avec réserves)
Compose supporte les secrets dans la spec, mais le comportement dépend du backend. Avec le moteur Docker local (non-Swarm),
les secrets en Compose se traduisent souvent en bind mounts, ce qui est mieux que des vars d’env mais signifie tout de même que le secret existe quelque part sur disque.
Cela peut être acceptable pour le développement et les petits déploiements si vous le faites consciemment.
Option 3 : gestionnaires de secrets externes (meilleur pour la production sérieuse)
Si vous avez plus d’un cluster, plus d’une équipe, ou des exigences de conformité, vous voulez un gestionnaire de secrets dédié
(magasin de secrets du fournisseur cloud, système de type Vault, ou service HSM-backed). Le runtime récupère alors des identifiants courts
en utilisant une identité, pas un mot de passe statique partagé.
Cet article est centré sur Docker, mais le principe est universel : l’accès basé sur l’identité bat les secrets statiques partagés.
Option 4 : si vous devez utiliser des vars d’env, limitez les dégâts
- Utilisez les vars d’env uniquement pour la configuration non secrète.
- Si vous devez passer un secret via l’env (applications legacy existent), rendez-le de courte durée et faites une rotation agressive.
- N’imprimez jamais l’environnement dans les logs.
- Restreignez qui peut inspecter les conteneurs et lire les logs. « Lecture seule » n’est souvent pas sans danger.
Petite blague n°2 : Mettre des mots de passe dans .env revient à étiqueter votre clé de maison « CLEF MAISON » et la cacher sous le paillasson.
Comment les secrets fichiers changent la conception de votre application (dans le bon sens)
Lire un secret depuis un fichier vous force à affronter le cycle de vie. Vous pouvez remplacer le fichier. Vous pouvez le faire pivoter. Vous pouvez contrôler les permissions.
Et votre application peut le recharger sans reconstruction complète.
Un pattern pratique :
- Monter le secret comme
/run/secrets/db_password - L’application le lit au démarrage, éventuellement relit sur SIGHUP ou à intervalle régulier
- La rotation devient : mettre à jour l’objet secret → redémarrer les tâches ou déclencher un reload
Tâches pratiques : commandes, sorties et décisions (12+)
C’est la partie que les gens sautent. Ne le faites pas. La différence entre « nous utilisons des secrets » et « nous ne les fuyons réellement pas » est la vérification.
Chaque tâche ci‑dessous inclut une commande, une sortie d’exemple, ce que la sortie signifie, et la décision que vous prenez.
Tâche 1 : Trouver les fichiers .env qui ne devraient pas exister
cr0x@server:~$ find . -maxdepth 4 -type f -name ".env" -o -name "*.env"
./.env
./services/api/.env
Signification : Vous avez des fichiers d’environnement dans l’arbre ; au moins un est à la racine du repo, là où les accidents arrivent.
Décision : Conservez uniquement des fichiers d’exemples non-secrets (.env.example) dans le repo ; retirez les vrais du contrôle de version
et faites pivoter tout ce qui y a déjà vécu.
Tâche 2 : Vérifier si des secrets sont traqués dans l’historique git
cr0x@server:~$ git log --name-only --pretty=format: | grep -E '(^|/)\.env$' | head
.env
services/api/.env
Signification : Ces fichiers ont été commités à un moment donné. Même s’ils sont supprimés maintenant, ils peuvent être présents dans l’historique et les clones.
Décision : Considérez tous les identifiants qui y sont apparus comme compromis ; faites la rotation. Puis nettoyez l’historique seulement si vous comprenez
l’impact opérationnel d’une réécriture de l’historique git.
Tâche 3 : Chercher des motifs communs de secrets dans le dépôt
cr0x@server:~$ grep -RIn --exclude-dir=.git -E "(PASSWORD=|API_KEY=|SECRET=|TOKEN=|BEGIN PRIVATE KEY)" .
./services/api/.env:3:DB_PASSWORD=summer2023
Signification : Vous avez au moins un secret littéral en clair. S’il est dans un working tree, il est probablement ailleurs aussi.
Décision : Supprimez-le, faites-le pivoter, ajoutez des gates de scan, et arrêtez de traiter grep comme votre programme de sécurité.
Tâche 4 : Inspecter un conteneur en cours d’exécution pour fuite de secrets via l’env
cr0x@server:~$ docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}"
NAMES IMAGE STATUS
api-1 myorg/api:1.8.2 Up 3 hours
db-1 postgres:16 Up 3 hours
cr0x@server:~$ docker inspect api-1 --format '{{json .Config.Env}}'
["NODE_ENV=production","DB_USER=app","DB_PASSWORD=summer2023","DB_HOST=db"]
Signification : Le mot de passe est visible par quiconque a la permission d’inspecter les conteneurs. Cette permission est souvent plus large que vous ne le pensez.
Décision : Retirez les vars d’env secrètes ; remplacez-les par des montages de fichiers secrets ; resserrez l’accès à l’API Docker.
Tâche 5 : Vérifier si le secret est présent à l’intérieur du conteneur en tant que fichier (meilleur pattern)
cr0x@server:~$ docker exec api-1 ls -l /run/secrets
total 4
-r--r----- 1 root root 16 Jan 3 10:12 db_password
Signification : Vous avez un fichier secret monté avec des permissions restrictives. Bon début.
Décision : Assurez-vous que votre appli s’exécute en tant qu’utilisateur pouvant le lire (membres d’un groupe), pas en root « parce que ça marche ».
Tâche 6 : Vérifier que l’application n’enregistre pas les variables d’environnement dans les logs
cr0x@server:~$ docker logs --tail 200 api-1 | grep -E "(DB_PASSWORD|API_KEY|SECRET|TOKEN)" || echo "no obvious secrets in tail"
no obvious secrets in tail
Signification : Aucune chaîne secrète évidente n’apparaît dans les 200 dernières lignes. Ce n’est pas une preuve, mais c’est un contrôle de base.
Décision : Poursuivez : vérifiez les logs structurés, les chemins d’erreur et les bannières de démarrage. Puis ajoutez des contrôles automatisés dans le CI.
Tâche 7 : Détecter l’exposition de secrets lors du rendu de la config Compose
cr0x@server:~$ docker compose config | sed -n '1,120p'
services:
api:
environment:
DB_HOST: db
DB_PASSWORD: ${DB_PASSWORD}
Signification : Compose câble toujours les secrets via environment. Même s’ils proviennent de votre shell, ils finissent dans la config.
Décision : Déplacez les secrets hors de environment et dans secrets: avec des montages fichiers.
Tâche 8 : Créer et utiliser un secret Swarm (cycle de vie réel du secret)
cr0x@server:~$ docker swarm init
Swarm initialized: current node (r8k3t2...) is now a manager.
cr0x@server:~$ printf "correct-horse-battery-staple\n" | docker secret create db_password -
z1p8kq3m9gq9u5a0l0xw2v3p1
Signification : Le secret existe maintenant dans le plan de contrôle Swarm. Vous ne l’avez pas écrit sur disque.
Décision : Utilisez ceci pour la distribution des secrets en production si Swarm correspond à votre modèle d’exploitation ; sinon utilisez un gestionnaire externe.
Tâche 9 : Confirmer que les secrets sont montés où vous l’attendez (et pas dans l’env)
cr0x@server:~$ docker service create --name api --secret db_password --env DB_USER=app alpine:3.20 sh -c "env | grep -E 'DB_PASSWORD' || echo 'no DB_PASSWORD in env'; ls -l /run/secrets"
no DB_PASSWORD in env
total 4
-r--r----- 1 root root 29 Jan 3 10:20 db_password
Signification : Le secret est disponible en tant que fichier, pas comme variable d’environnement. C’est ce que vous voulez.
Décision : Mettez à jour l’application pour lire le fichier et cessez d’attendre une var d’env.
Tâche 10 : Faire pivoter un secret Swarm (la partie pénible mais correcte)
cr0x@server:~$ docker secret ls
ID NAME CREATED UPDATED
z1p8kq3m9gq9u5a0l0xw2v3p1 db_password 5 minutes ago 5 minutes ago
cr0x@server:~$ printf "new-password-value\n" | docker secret create db_password_v2 -
m2c1v0b8n7x6a5s4d3f2g1h0j9
cr0x@server:~$ docker service update --secret-rm db_password --secret-add source=db_password_v2,target=db_password api
api
overall progress: 1 out of 1 tasks
1/1: running [==================================================>]
verify: Service converged
Signification : Vous avez créé un nouveau secret et mis à jour le service pour l’utiliser. Les secrets Swarm sont immuables ; la rotation est remplacer-et-redéployer.
Décision : Intégrez la rotation dans les runbooks de déploiement. Si votre système ne peut pas tolérer un redémarrage de tâche, corrigez d’abord cela.
Tâche 11 : Vérifier si un secret s’est accidentellement retrouvé dans une couche d’image
cr0x@server:~$ docker history --no-trunc myorg/api:1.8.2 | head -n 8
IMAGE CREATED CREATED BY SIZE COMMENT
sha256:3b1c... 2 days ago /bin/sh -c #(nop) COPY . /app 14.2MB
sha256:a9f0... 2 days ago /bin/sh -c #(nop) RUN npm ci 48.1MB
Signification : Si votre .env est dans le contexte de build et copié dans l’image, il pourrait être intégré dans cette couche COPY.
Décision : Assurez-vous que .dockerignore exclut les fichiers secrets ; rebuild et redeploy ; faites pivoter tout ce qui pourrait avoir été copié.
Tâche 12 : Valider que .dockerignore bloque les fichiers secrets évidents
cr0x@server:~$ cat .dockerignore
.env
*.env
**/.env
**/*.env
id_rsa
*.pem
Signification : Vous excluez explicitement les transporteurs de secrets courants du contexte de build.
Décision : Conservez-le. Validez aussi que votre CI n’injecte pas de secrets dans le contexte de build malgré tout.
Tâche 13 : Vérifier qui peut parler au socket Docker (aka « qui peut devenir root »)
cr0x@server:~$ ls -l /var/run/docker.sock
srw-rw---- 1 root docker 0 Jan 3 08:01 /var/run/docker.sock
cr0x@server:~$ getent group docker
docker:x:998:deploy,ci-runner
Signification : Les membres du groupe docker ont effectivement un contrôle équivalent à root sur l’hôte.
S’ils peuvent inspecter les conteneurs, ils peuvent lire l’env, monter des systèmes de fichiers et extraire des secrets.
Décision : Traitez l’accès au socket Docker comme privilégié ; réduisez l’appartenance, auditez-la et isolez les runners CI.
Tâche 14 : Confirmer qu’un secret n’est pas passé via --env-file de Compose
cr0x@server:~$ ps aux | grep -E "docker compose.*--env-file" | grep -v grep
deploy 21984 0.2 0.1 23844 9152 ? Ss 10:02 0:00 docker compose --env-file /srv/app/.env up -d
Signification : Quelqu’un injecte explicitement un env file au runtime. Ce fichier vit probablement sur le disque de l’hôte, et peut être sauvegardé.
Décision : Remplacez par des secrets montés depuis un emplacement protégé, et retirez les fichiers env en clair des chemins de sauvegarde de l’hôte.
Tâche 15 : Scanner les logs pour motifs à haut risque sans tout dumper
cr0x@server:~$ docker logs api-1 2>&1 | grep -E "password=|Authorization: Bearer|BEGIN PRIVATE KEY" | head
Authorization: Bearer eyJhbGciOi...
Signification : Vous avez des tokens bearer dans les logs. C’est un identifiant actif dans beaucoup de systèmes, et maintenant c’est un artefact consultable.
Décision : Traitez comme un incident : faites pivoter les tokens, purgez/limitez les logs, ajoutez des filtres de logs, et corrigez le chemin de code qui journalise les headers.
Playbook de diagnostic rapide
Quand vous suspectez une fuite de secrets, vous n’avez pas de temps pour la philosophie. Vous avez besoin d’une boucle serrée : confirmer, circonscrire, contenir, faire pivoter et prévenir la récidive.
Voici l’ordre qui gagne le plus souvent.
Première étape : confirmer les voies d’exposition (minutes)
-
Vérifier les métadonnées des conteneurs :
docker inspectpour les secrets dans l’env. S’ils sont présents, supposez une exposition à quiconque a accès à l’API Docker. - Vérifier les logs pour du texte en clair : chercher des patterns de tokens/mots de passe. S’ils sont trouvés, considérez les logs comme des magasins de données compromis.
- Vérifier la sortie CI : le dernier pipeline réussi pour des étapes de débogage « utiles » ou des échecs de masquage.
Deuxième étape : circonscrire le rayon d’impact (dizaines de minutes)
- Où d’autre le secret est allé ? couches d’image, stores d’artefacts, bundles de support, pages wiki, logs de chat.
- Qui a accès ? utilisateurs du socket Docker, lecteurs d’API d’orchestrateur, lecteurs de logs, lecteurs de registre.
- Est-ce réutilisable ? les mots de passe statiques sont pires que les tokens courts. Mais des tokens courts dans des logs restent problématiques.
Troisième étape : contenir et faire pivoter (heures)
- Faire pivoter les identifiants en commençant par les plus puissants (clefs cloud, credentials admin base de données, clefs de signature).
- Redéployer pour supprimer l’usage de l’env et basculer vers des secrets fichiers ou la récupération externe de secrets.
-
Réduire les surfaces d’observabilité : resserrer le RBAC, réduire la verbosité des logs, bloquer les patterns
printenvvia du linting.
Réalité du goulot : la plupart des équipes passent la première heure à discuter de si c’est « une vraie fuite ». Arrêtez ça. Faites la rotation d’abord, débattez après.
Erreurs courantes : symptômes → cause racine → correction
Erreur 1 : « On utilise .env mais ce n’est pas commit »
Symptômes : le secret apparaît dans un diff de PR, ou la compromission d’un portable dev déclenche une panique de rotation des credentials.
Cause racine : des secrets stockés en clair dans des répertoires de travail se font copier, zipper, attacher et sauvegarder.
Correction : gardez .env pour la config locale non secrète ; déplacez les secrets vers le trousseau OS, un gestionnaire externe ou des fichiers secret Compose/Docker.
Erreur 2 : « Ça va, seuls les ops peuvent faire docker inspect »
Symptômes : les auditeurs demandent qui a accès à Docker et vous ne pouvez pas répondre rapidement ; des sous-traitants peuvent lire l’env des conteneurs.
Cause racine : l’accès au socket Docker est plus large que prévu (runners CI, accès « temporaire », jump boxes partagées).
Correction : traitez l’API Docker comme privilégiée ; minimisez la membership du groupe docker ; isolez le CI ; utilisez des secrets plutôt que l’env.
Erreur 3 : Passer des secrets au moment du build
Symptômes : le mot de passe apparaît dans docker history ou est récupérable depuis les couches du registre.
Cause racine : utilisation de ARG ou copie de fichiers secret dans le contexte de build ; les supprimer plus tard n’efface pas les couches.
Correction : utilisez les montages de secrets BuildKit pour les besoins spécifiques au build ; ne bunkerisez jamais de secrets runtime dans les images ; faites respecter .dockerignore.
Erreur 4 : « On a monté un fichier secret, donc c’est bon »
Symptômes : le secret finit toujours dans les logs, ou l’app plante et dump la config incluant le contenu du fichier secret.
Cause racine : l’app lit le secret et l’imprime (directement ou indirectement) lors du traitement d’erreur, ou des endpoints de debug exposent la config.
Correction : redigez les champs sensibles ; désactivez les endpoints de debug en prod ; ajoutez des tests qui échouent si des secrets apparaissent dans les logs.
Erreur 5 : Mots de passe partagés et longue durée entre environnements
Symptômes : une fuite en dev déclenche une rotation en prod ; les équipes craignent la rotation parce que ça casse tout.
Cause racine : un seul identifiant réutilisé dev/stage/prod et entre services ; pas de frontières d’identité.
Correction : identifiants uniques par environnement et idéalement par identité de service ; privilégiez des tokens courts ; faites de la rotation une routine de déploiement.
Erreur 6 : Stocker des secrets dans des volumes sauvegardés
Symptômes : des restaurations de sauvegarde révèlent des credentials anciens ; la sécurité demande pourquoi des secrets existent dans des snapshots.
Cause racine : secrets écrits dans des répertoires de données applicatives ; le système de sauvegarde capture tout.
Correction : montez les secrets dans des chemins runtime dédiés comme /run/secrets ; excluez ces chemins des sauvegardes ; chiffrez les sauvegardes de toute façon.
Trois mini-récits d’entreprise depuis le terrain
Mini-récit 1 : Un incident causé par une mauvaise hypothèse
Une entreprise SaaS de taille moyenne avait une séparation nette entre les équipes « plateforme » et « application ». L’équipe plateforme gérait les hôtes Docker et le CI.
L’équipe application gérait le code du service et les fichiers Compose. Chacun croyait que sa frontière était sûre.
L’équipe application définissait DB_PASSWORD via des variables d’environnement dans Compose, sourcées depuis un magasin de variables CI protégé. Ils partaient du principe :
« Le store CI est sécurisé, donc le secret est sécurisé. » Ils n’étaient pas imprudents. Ils étaient littéraux.
L’équipe plateforme a ajouté un job de dépannage pour l’astreinte : un script qui exécutait docker inspect et archivait la sortie pendant les incidents.
Il aidait à diagnostiquer les limites mémoire, les boucles de redémarrage et les tags d’image. Il archivait aussi chaque variable d’environnement pour chaque conteneur, y compris
les mots de passe de base de données et les tokens API. L’archive allait dans un objet store interne utilisé pour les artefacts d’incident.
Quelques semaines plus tard, un contractant s’est vu accorder un accès en lecture aux artefacts d’incident pour aider aux investigations de performance. Il n’a rien fait de mal.
Il avait juste accès à ce qui s’y trouvait. Une revue sécurité a trouvé des secrets dans ces artefacts, et maintenant l’entreprise avait un problème de divulgation plus un projet de rotation sous délai.
La mauvaise hypothèse n’était pas « les contractants sont malveillants ». C’était « les secrets dans l’env sont seulement visibles au runtime ». En pratique, les secrets d’env deviennent
des métadonnées, et les métadonnées deviennent des artefacts. La correction était ennuyeuse : basculer vers des secrets fichiers, rédiger les bundles de diagnostic et traiter
« la sortie d’inspection » comme sensible.
Mini-récit 2 : Une optimisation qui s’est retournée contre eux
Une autre entreprise faisait tourner une flotte de petits services. Les déploiements étaient fréquents, et ils voulaient des rollouts plus rapides. Quelqu’un a proposé une « optimisation » :
build once, deploy everywhere, et éviter les redémarrages en faisant recharger la config dynamiquement depuis les variables d’environnement. Ça semblait malin.
Ils ont ajouté un endpoint léger pour le support : /debug/config. Il renvoyait la configuration effective pour aider à diagnostiquer les erreurs de routage,
les feature flags et les endpoints upstream. Il était restreint au réseau interne, et « seulement les SRE » pouvaient y accéder. Vous voyez déjà où ça mène.
Un changement de routage a accidentellement exposé cet endpoint via un proxy interne partagé utilisé par plusieurs équipes. Pas l’internet public, mais une audience suffisamment large.
Quelqu’un en train de dépanner son propre service a frappé l’endpoint, vu un blob JSON avec des credentials, et l’a signalé.
L’optimisation s’est retournée contre eux parce que la « config dynamique » basée sur l’env a poussé les secrets dans le même canal que la config régulière. L’endpoint de debug n’était pas
censé exposer des secrets, mais il n’avait pas de séparation propre. Il vidait simplement la config.
La remédiation a été douloureuse mais directe : retirer les secrets de l’env, les monter en fichiers, redesigner la sortie de debug pour exclure explicitement le matériel secret, et introduire
des identités par service pour que l’exposition accidentelle ne devienne pas une compromission à l’échelle de la flotte.
Mini-récit 3 : Une pratique ennuyeuse mais correcte qui a sauvé la mise
Une entreprise « financière » (suffisamment réglementée pour s’en soucier, pas assez pour avoir un effectif infini) a adopté tôt une politique :
« Tous les secrets sont des fichiers, tous les fichiers vivent sous un seul répertoire, et ce répertoire n’est jamais inclus dans les bundles de diagnostic. »
C’était le genre de règle dont les gens se plaignent jusqu’à ce qu’elle les sauve.
Ils utilisaient les secrets Docker Swarm pour un sous-ensemble de charges et un gestionnaire de secrets externe pour le reste. Dans tous les cas, le chemin runtime était cohérent :
/run/secrets/<name>. Les applications étaient tenues de supporter la lecture depuis ce chemin. Ce n’était pas optionnel.
Pendant une panne chaotique, un ingénieur senior a demandé tout : sortie d’inspect container, logs et snapshots du système de fichiers d’un nœud fautif.
L’équipe les a collectés rapidement. La sécurité a revu le bundle avant de le partager avec un prestataire. La revue n’a trouvé aucun credential.
Pas parce que les gens étaient soigneux sur le moment, mais parce que le système était conçu pour rendre « soigneux » le comportement par défaut.
La pratique ennuyeuse a fait deux choses : elle a réduit le nombre d’endroits où les secrets pouvaient apparaître, et elle a rendu l’audit plus facile. Quand vous pouvez dire,
« les secrets vivent ici et seulement ici », vous pouvez scanner cette frontière, appliquer des permissions et l’exclure des sauvegardes et diagnostics.
Personne n’a écrit un post de blog interne sur cette politique. C’était trop ennuyeux. Elle a tout de même empêché un incident secondaire pendant un incident principal,
ce qui est le genre de victoire qui n’apparaît pas sur les tableaux de bord mais qui maintient les entreprises en vie.
Listes de contrôle / plan pas à pas
Plan pas à pas : migrer de .env vers des secrets fichiers (centré Docker)
-
Inventorier les secrets actuellement en env : lister quels containers/services ont des variables d’env de type secret
(mots de passe, tokens, clés privées). -
Définir un chemin standard de montage de secrets : choisissez
/run/secretset tenez-vous-y. -
Mettre à jour les applications : apprenez aux applis à lire
DB_PASSWORD_FILE=/run/secrets/db_passwordou à lire directement le fichier.
Préférez la convention « _FILE » si vous devez garder la config basée sur l’env sans y intégrer les valeurs secrètes. -
Implémenter les secrets dans votre plateforme :
- Swarm :
docker secret create+ montages de secret sur les services. - Compose (non-Swarm) : utilisez
secrets:si supporté, sinon bind-montez depuis un répertoire hôte protégé avec permissions strictes. - Gestionnaire externe : récupérez au runtime via identité ; écrivez en tmpfs ; montez dans le conteneur.
- Swarm :
- Faire la rotation pendant la migration : ne réutilisez pas les valeurs « juste pour la coupure ». Supposez que les anciens chemins env sont compromis.
- Verrouiller les surfaces d’observabilité : retirer les endpoints de dump de config ; rediger les logs ; restreindre qui peut inspecter les conteneurs.
-
Ajouter des garde-fous CI : échouer les builds si
.envou des motifs de secrets sont détectés dans le contexte de build ou le repo. - Pratiquer la rotation : exécutez un drill de rotation trimestriel. La première fois que vous faites une rotation ne doit pas être pendant un incident.
Checklist : à quoi ressemble le « bon » en production
- Les secrets n’apparaissent pas dans la sortie de
docker inspect. - Les secrets ne sont pas dans les images (vérifié via
docker historyet contrôles du contexte de build). - Les secrets ne sont pas dans les logs (vérification ponctuelle et scans automatiques).
- La rotation est une opération de déploiement routinière, avec runbooks et rollback testés.
- L’accès au socket Docker / aux métadonnées de l’orchestrateur est audité et minimisé.
- Les secrets sont uniques par environnement et idéalement par identité de service.
FAQ
1) Les variables d’environnement sont-elles parfois acceptables pour des secrets ?
Parfois, pour des applis legacy, des tokens de courte durée, ou une phase de transition. Mais traitez ça comme une dette technique avec une échéance.
Si c’est long-terme et à fort impact (mot de passe admin DB, clé de signature), ne le mettez pas dans l’env.
2) Un fichier secret monté est-il encore lisible par l’app, donc quelle est la différence ?
La différence est la surface d’exposition. L’env fuit dans les métadonnées, la sortie d’inspection et les patterns d’enregistrement accidentels plus souvent.
Les fichiers peuvent être permissionnés, exclus des diagnostics, et tournés par remplacement sans réécrire des configs.
3) Les secrets Docker fonctionnent-ils sans Swarm ?
Pas dans le sens complet géré par Swarm. Compose supporte un concept de secrets, mais selon le mode cela peut devenir un bind mount.
Cela peut quand même être mieux que des vars d’env, mais vous devez comprendre où le texte en clair vit sur le disque.
4) Qu’en est-il des patterns DB_PASSWORD_FILE — sont-ils sécurisés ?
C’est un compromis pragmatique : l’env contient seulement un chemin de fichier, pas la valeur secrète. Le secret a toujours besoin d’une source sécurisée et d’un montage.
Ce pattern garde aussi la config applicative cohérente entre plateformes.
5) Comment empêcher que des secrets soient cuits dans les images ?
Excluez les fichiers secrets avec .dockerignore, évitez de passer des secrets via ARG, et utilisez les montages de secrets BuildKit pour les besoins build-time.
Puis vérifiez avec docker history --no-trunc et le scanning de registre.
6) Comment devons-nous faire la rotation des secrets avec un downtime minimal ?
Utilisez une rotation en deux phases si possible : introduire un nouvel identifiant, déployer le support pour les deux, basculer le trafic, puis retirer l’ancien.
Pour les secrets Swarm, cela signifie typiquement créer _v2 et mettre à jour le service, puis retirer _v1.
7) Si quelqu’un a vu le secret dans les logs une fois, devons-nous vraiment faire une rotation ?
Oui. Les logs se répliquent, persistent et sont accessibles pour des raisons non liées à la sécurité. S’il est apparu une fois, vous ne pouvez pas garantir qu’il est complètement parti.
Faites la rotation et corrigez le chemin de logging.
8) Quelles permissions devraient avoir les fichiers secret à l’intérieur des conteneurs ?
Lecture seule pour le processus qui en a besoin, idéalement lisible par groupe avec un groupe dédié. Évitez la lecture par tous.
Ne lancez pas l’ensemble de l’app en root juste pour lire un fichier.
9) Comment convaincre une équipe qu’un « dépôt privé » n’est pas un magasin de secrets ?
Demandez qui a accès aujourd’hui, qui aura accès le trimestre prochain, et où les clones vivent. Les dépôts sont conçus pour se répliquer.
Les magasins de secrets sont conçus pour contrôler l’accès et l’auditer.
10) Les secrets sous forme de fichiers résolvent-ils tout ?
Non. Ils réduisent les chemins de fuite courants. Vous avez toujours besoin du moindre privilège, de la segmentation, d’identifiants courts quand possible, et d’une hygiène des logs.
Mais c’est un bon défaut qui empêche beaucoup d’erreurs auto-infligées.
Conclusion : étapes suivantes qui réduisent réellement le risque
Arrêtez de traiter .env comme une commodité inoffensive. En production, c’est un aimant à responsabilités : il fuit dans les dépôts, les logs, les artefacts
et la sortie d’inspection. Et le pire, c’est à quel point ça paraît normal — jusqu’à ce que ça ne le soit plus.
Étapes pratiques :
- Auditez les services en cours pour des vars d’env secrètes avec
docker inspect; retirez-les. - Basculez les secrets vers des montages fichiers (
/run/secrets) via les secrets Swarm, les secrets Compose ou un gestionnaire externe. - Faites pivoter tout ce qui a jamais vécu dans
.env, les logs CI ou les bundles de diagnostic. - Verrouillez qui peut accéder au socket Docker et qui peut lire les logs ; auditez les deux.
- Faites de la rotation une action de déploiement routinière, pas un rituel d’urgence.
Vous n’avez pas besoin d’une sécurité parfaite. Vous avez besoin de systèmes qui ne copient pas à la légère vos bijoux de couronne dans chaque outil qui se trouve à proximité.
C’est ce que signifie « secrets sans fuites » dans le monde réel.