Un Secret n’est pas chiffré, seulement encodé en base64. Où mettre un mot de passe, qui peut réellement le lire, et pourquoi Vault ne remplace pas le Secret mais l’alimente.
Un mot de passe n’a rien à faire dans le code, ni dans l’image Docker, ni dans un ConfigMap. Cette phrase est facile à retenir et facile à répéter. Le problème est qu’elle laisse croire que déplacer la valeur dans un Secret règle la question de la confidentialité — alors qu’elle ne fait que la déplacer.
Voyons d’abord la répartition, puis ce que le mot « Secret » promet de trop.
ConfigMap
├── APP_TITLE = "Mon application"
├── APP_VERSION = "1.0"
└── BG_COLOR = "#0f172a"
Secret
├── DB_USER = "..."
├── DB_PASSWORD = "..."
└── API_KEY = "..."Le critère est simple : est-ce que cette valeur, publiée par accident, poserait un problème ? Une couleur de fond, non. Un identifiant de base de données, oui. Le reste des différences en découle.
L’application, elle, ne voit aucune différence :
Image Docker + ConfigMap + Secret
↓
PodTrois sources, un seul environnement d’exécution. C’est tout l’intérêt : au moment du docker build, l’image ne connaît aucun mot de passe.
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: demo-secret
key: DB_PASSWORDEt côté application, rien de particulier :
import os
password = os.getenv('DB_PASSWORD')Comme pour un ConfigMap, envFrom permet de tout verser d’un coup :
envFrom:
- secretRef:
name: demo-secretPratique, et un peu trop : le conteneur reçoit alors toutes les clés du Secret, y compris celles dont il n’a pas besoin. Nommer explicitement ce qu’on injecte est un meilleur réflexe pour un Secret que pour un ConfigMap.
C’est ici que se produit le bug le plus fréquent, et il est vicieux parce que la valeur semble correcte.
echo "mon-mot-de-passe" | base64Cette commande ajoute un retour à la ligne avant l’encodage. Le Secret contient donc mon-mot-de-passe\n, l’authentification échoue, et le mot de passe affiché à l’écran paraît juste. On cherche alors du côté du réseau, des droits, du pilote de base de données — partout sauf au bon endroit.
printf '%s' 'mon-mot-de-passe' | base64 # correctLe plus sûr reste de ne jamais encoder à la main :
kubectl create secret generic demo-secret \
--from-literal=DB_USER=app \
--from-literal=DB_PASSWORD='mon-mot-de-passe'kubectl se charge de l’encodage, sans caractère parasite. Dans un fichier YAML, le champ stringData rend le même service :
apiVersion: v1
kind: Secret
metadata:
name: demo-secret
type: Opaque
stringData:
DB_USER: app
DB_PASSWORD: mon-mot-de-passeConfortable — et dangereux, parce que ce fichier contient un mot de passe en clair et que le premier réflexe est de le committer. Nous y revenons plus bas.
La démonstration prend cinq secondes :
kubectl get secret demo-secret -o jsonpath='{.data.DB_PASSWORD}' | base64 -dmon-mot-de-passebase64 est un encodage, pas un chiffrement : c’est une transformation réversible, publique, sans clé. Son rôle est de permettre de transporter n’importe quel octet dans du texte, y compris des données binaires comme un certificat. Il ne cache rien.
Ce qu’un Secret apporte réellement, et qui n’est pas rien :
kubectl describe montre le nom des clés et la taille des valeurs, pas leur contenu ;tmpfs) plutôt que sur le disque du nœud ;Ce qu’il n’apporte pas par défaut : la confidentialité au repos. Dans un cluster sans configuration supplémentaire, les Secrets sont écrits dans etcd tels quels. Une sauvegarde d’etcd contient donc tous vos mots de passe en clair.
Par une EncryptionConfiguration fournie au serveur d’API, qui chiffre les ressources choisies avant écriture dans etcd :
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources: ['secrets']
providers:
- aescbc:
keys:
- name: cle-1
secret: <clé en base64>
- identity: {}Deux points à connaître. L’ordre des fournisseurs compte : le premier sert au chiffrement, tous servent au déchiffrement, et identity en fin de liste permet de lire les Secrets écrits avant l’activation. Et la clé se retrouve alors dans un fichier sur les nœuds de contrôle, ce qui déplace le problème plutôt que de le supprimer — d’où l’intérêt d’un fournisseur KMS, où la clé reste chez le service de gestion de clés.
Sur un cluster géré, c’est souvent une case à cocher qui branche le KMS du fournisseur. À vérifier, car ce n’est pas toujours activé par défaut.
La question intéressante n’est pas comment ils sont stockés, mais qui y accède.
kubectl auth can-i get secrets --namespace demoEt le point que presque personne n’énonce : quiconque peut créer un Pod dans un namespace peut lire tous les Secrets de ce namespace. Il suffit de monter le Secret dans un conteneur et d’en afficher le contenu. Rien à contourner, c’est le fonctionnement normal.
Autrement dit, accorder le droit de créer des Pods équivaut à accorder la lecture des Secrets. C’est ce qui fait du namespace la véritable frontière de sécurité : séparer les environnements et les équipes par namespace vaut mieux que d’affiner les droits sur les Secrets à l’intérieur d’un namespace partagé.
Les deux fonctionnent, comme pour un ConfigMap — mais pour un Secret, la recommandation s’inverse.
volumeMounts:
- name: secrets
mountPath: /etc/secrets
readOnly: true
volumes:
- name: secrets
secret:
secretName: demo-secretLes variables d’environnement ont trois défauts propres aux données sensibles. Elles sont héritées par tous les processus enfants, y compris ceux qui n’ont rien à voir avec la base. Elles apparaissent dans les traces d’exception de nombreux frameworks, dans les rapports de plantage et dans les journaux de diagnostic qui vident l’environnement. Et elles sont figées au démarrage du processus, donc une rotation impose un redémarrage.
Les fichiers montés sont mis à jour par Kubernetes quand le Secret change, ce qui rend la rotation possible sans redémarrer — à condition que l’application relise ses fichiers. Le détail de cette mécanique est le même que pour la mise à jour d’un ConfigMap.
Pour un TP, les variables d’environnement suffisent et restent plus lisibles. Pour de la production, les fichiers sont le choix par défaut.
Le vrai risque, en pratique, n’est ni etcd ni le RBAC : c’est le dépôt Git. Un stringData avec un mot de passe en clair, poussé une fois, y reste dans l’historique même après suppression.
Trois approches, par ordre d’engagement :
kubectl create secret, et ne pas versionner de fichier. Simple, mais rien ne documente ce qui existe dans le cluster.La question est mal posée, de la même façon que « Docker ou Kubernetes » : les deux ne se remplacent pas, ils se succèdent.
Un Secret est un objet Kubernetes — une petite boîte dans un namespace, avec un nom, des clés et des valeurs. Il vit dans le cluster et n’existe que pour lui.
Vault est un service externe, spécialisé dans la conservation, la distribution et la rotation des secrets, pour plusieurs applications et plusieurs clusters à la fois. Il apporte ce que Kubernetes ne fait pas : un journal d’accès complet, des secrets à durée de vie limitée, une politique de rotation, des identifiants engendrés à la demande.
Et dans le montage habituel, Vault alimente des Secrets Kubernetes :
Le point remarquable est à droite du schéma : le code de l’application ne change pas. Elle lit toujours une variable ou un fichier. Ce qui change, c’est l’origine de la valeur — et c’est précisément pourquoi commencer par un simple Secret n’est pas une impasse. Le jour où Vault arrive, on remplace la source sans toucher à l’application.
D’où la progression raisonnable : Secret pour apprendre et pour un TP ; Secret chiffré dans Git dès qu’une équipe travaille sur le dépôt ; gestionnaire externe quand plusieurs applications, plusieurs clusters ou des exigences d’audit entrent en jeu.
Le champ type ne sert pas qu’à décorer. Deux valeurs sont d’usage courant.
kubernetes.io/dockerconfigjson contient les identifiants d’un registre privé :
kubectl create secret docker-registry regcred \
--docker-server=registre.example.com \
--docker-username=robot \
--docker-password='...'spec:
imagePullSecrets:
- name: regcredSans lui, une image hébergée dans un registre privé produit un ImagePullBackOff — la même erreur qu’une image absente, pour une raison entièrement différente. La distinction se lit dans kubectl describe pod, qui mentionne alors une authentification refusée.
kubernetes.io/tls contient un certificat et sa clé privée, et c’est ce que consomme un Ingress pour servir en HTTPS.
immutable: trueUn Secret immuable ne peut plus être modifié, seulement supprimé et recréé. Cela évite un changement accidentel sur une valeur critique, et allège la charge du serveur d’API, qui n’a plus à surveiller cet objet.
spec:
automountServiceAccountToken: falsePar défaut, chaque Pod reçoit le jeton de son compte de service, monté dans son système de fichiers. Une application qui ne parle pas à l’API Kubernetes n’en a aucun besoin, et ce jeton est un moyen d’accès de plus en cas de compromission du conteneur. Le désactiver quand il est inutile est gratuit.
Le raisonnement est exactement celui du rolling update avec migration, et pour la même raison : pendant le remplacement des Pods, deux générations coexistent.
Un mot de passe remplacé d’un coup casse tous les Pods qui tournent encore avec l’ancien. La séquence correcte est donc :
kubectl rollout restart.Ajouter avant de retirer, une fois de plus. C’est la même forme que la migration de schéma, appliquée aux identifiants.
ConfigMap = réglages NON sensibles
Secret = réglages SENSIBLESLa règle est juste, mais elle mérite sa suite : un Secret n’est pas chiffré parce qu’il s’appelle Secret. Il vous donne un emplacement conventionnel, des droits séparés et la discrétion à l’affichage. La confidentialité réelle vient du chiffrement au repos, du cloisonnement par namespace, et de la discipline qui empêche le mot de passe d’atterrir dans le dépôt Git.
Quant à Vault, ce n’est pas l’étape suivante d’un Secret : c’est ce qui le remplit. Le Pod, lui, continue de lire une variable d’environnement — et c’est ce qui rend la transition possible sans réécrire l’application.
Pour la suite : le ConfigMap et ses deux modes de consommation, le Deployment qui les injecte, et le projet complet où ces objets fonctionnent ensemble.
Développement et déploiement de solutions de données — les premiers modules sont en accès libre.