
Un Secret Kubernetes permet de stocker des données sensibles comme des mots de passe, des clés API, des tokens ou des certificats. Il fournit un conteneur dédié pour ce type d'information, mais sa sécurité dépend surtout du RBAC, du chiffrement au repos et de la manière dont vous l'injectez dans vos Pods.
Ce guide montre comment créer un Secret, choisir son type, l'injecter dans un Pod et appliquer les bonnes pratiques de sécurité.
Prérequis : un cluster Kubernetes avec kubectl configuré.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Comprendre ce qu'un Secret protège (et ce qu'il ne protège pas)
- Créer des Secrets de différents types (Opaque, TLS, docker-registry)
- Injecter les Secrets dans vos Pods (env ou volume)
- Appliquer les bonnes pratiques de sécurité essentielles
- Décider quand passer à un gestionnaire de secrets externe
Ce qui différencie vraiment un Secret d'un ConfigMap
Section intitulée « Ce qui différencie vraiment un Secret d'un ConfigMap »Un Secret est pensé pour des données sensibles, mais il ne devient réellement sûr que si vous montez les protections autour : RBAC strict, chiffrement au repos dans etcd, accès à l'API limités, et montage en volume plutôt qu'en variables d'environnement. Aucune de ces quatre protections n'est automatique. Par défaut, un Secret reste lisible par toute identité autorisée à le récupérer via l'API.
Cela posé, le type Secret n'est pas qu'une étiquette : Kubernetes lui réserve
trois traitements qu'un ConfigMap n'a pas, tous observables en une commande.
kubectl describe masque les valeurs d'un Secret et affiche seulement leur
taille, là où il imprime en clair celles d'un ConfigMap. C'est la protection la
plus concrète au quotidien, celle qui évite d'étaler un mot de passe dans un
terminal partagé ou une capture d'écran.
kubectl describe secret db-credentialsType: Opaque
Data====password: 11 bytesusername: 5 bytesCette protection ne tient qu'à la commande employée, et elle s'annule
trivialement. kubectl get -o yaml ne masque rien : il rend le champ data
tel quel, et le base64 se décode en une seconde.
kubectl get secret db-credentials -o yamldata: password: UzNjcjN0IVBAc3M= username: YWRtaW4=kind: SecretRetenez donc ce que describe protège réellement : le coup d'oeil par-dessus
l'épaule, la capture d'écran, le copier-coller dans un ticket. Il ne protège
de personne ayant le droit de lire le Secret, puisqu'une seule autre commande
suffit.
Un Secret monté est écrit en tmpfs, donc en mémoire, et n'atteint jamais
le disque du nœud. Un ConfigMap monté, lui, est un fichier ordinaire du système
de fichiers du nœud :
kubectl exec mon-pod -- stat -f -c '%n : %T' /etc/secrets /etc/conf/etc/secrets : tmpfs/etc/conf : ext2/ext3Le chiffrement au repos d'etcd se configure pour la ressource secrets, et
c'est le seul de ces trois mécanismes qui ne soit pas actif par défaut :
tant que vous ne l'avez pas activé, vos Secrets sont écrits en clair sur le
disque du control plane.
Ce que le type Secret n'apporte pas, en revanche : aucune
confidentialité vis-à-vis de qui possède le droit de lecture sur l'API. C'est
le RBAC qui décide, pas le type de la ressource.
Pourquoi base64 n'est pas du chiffrement
Section intitulée « Pourquoi base64 n'est pas du chiffrement »C'est le piège le plus courant. Beaucoup pensent que l'encodage base64 protège leurs données. C'est faux. N'importe qui le décode en une seconde, par exemple avec CyberChef.
# Créer un Secretkubectl create secret generic demo-secret --from-literal=password='S3cr3t!P@ss'
# Voir le Secret "protégé"kubectl get secret demo-secret -o jsonpath='{.data.password}'UzNjcjN0IVBAc3M=# Décoder en une commandeecho 'UzNjcjN0IVBAc3M=' | base64 -dS3cr3t!P@ssLe décodage est instantané. Base64 sert uniquement à transporter des données binaires dans du YAML/JSON, pas à les protéger.
La conséquence est directe : quiconque peut exécuter kubectl get secret
dans votre namespace lit tous vos mots de passe, sans outil ni privilège
particulier. Le RBAC est votre première ligne de défense, et la seule qui vaille
tant que le chiffrement au repos n'est pas activé.
Types de Secrets Kubernetes
Section intitulée « Types de Secrets Kubernetes »Kubernetes propose plusieurs types de Secrets, chacun avec une structure adaptée :
| Type | Usage | Clés créées |
|---|---|---|
Opaque | Usage générique (mots de passe, tokens) | Libres |
kubernetes.io/tls | Certificats TLS | tls.crt, tls.key |
kubernetes.io/dockerconfigjson | Authentification registry | .dockerconfigjson |
kubernetes.io/basic-auth | Authentification HTTP basique | username, password |
kubernetes.io/ssh-auth | Clé SSH | ssh-privatekey |
La colonne de droite n'est pas indicative : ces clés sont obligatoires, et l'API refuse le Secret si elles manquent. Un type kubernetes.io/tls déclaré avec des clés nommées autrement est rejeté avant d'exister.
The Secret "tls-faux" is invalid:* data[tls.crt]: Required value* data[tls.key]: Required valueLa validation s'arrête cependant à la structure, jamais au contenu. Un Secret TLS dont tls.crt contient du texte quelconque est accepté, avec un simple avertissement côté client, Warning: tls: failed to find any PEM data in certificate input. Ne comptez donc pas sur Kubernetes pour vérifier vos certificats.
Dernier point à connaître avant de choisir : le type est immuable. Un Secret créé en Opaque ne deviendra jamais un Secret TLS, il faudra le supprimer et le recréer.
* type: Invalid value: "kubernetes.io/tls": field is immutableCréer un Secret
Section intitulée « Créer un Secret »Le choix de la méthode a une conséquence de sécurité immédiate. La ligne de commande est la plus rapide, mais elle inscrit le mot de passe dans l'historique de votre shell et le rend visible dans la liste des processus le temps de son exécution. Le manifeste YAML évite cela, au prix d'un fichier qui ne doit jamais atteindre Git.
Secret Opaque (usage courant)
Section intitulée « Secret Opaque (usage courant) »La méthode la plus simple pour des identifiants :
kubectl create secret generic db-credentials \ --from-literal=username=admin \ --from-literal=password='S3cr3t!P@ss'Vérifiez la création :
kubectl get secret db-credentialsNAME TYPE DATA AGEdb-credentials Opaque 2 5sSecret TLS
Section intitulée « Secret TLS »Pour stocker un certificat et sa clé privée :
kubectl create secret tls my-tls-secret \ --cert=server.crt \ --key=server.keykubectl get secret my-tls-secretNAME TYPE DATA AGEmy-tls-secret kubernetes.io/tls 2 5sSecret docker-registry
Section intitulée « Secret docker-registry »Ce type répond à un besoin précis : c'est le kubelet, pas votre application, qui doit s'authentifier pour tirer l'image. Le Secret n'est donc pas monté dans le conteneur mais déclaré au niveau du Pod, via imagePullSecrets.
kubectl create secret docker-registry my-registry \ --docker-server=registry.example.com \ --docker-username=myuser \ --docker-password=mypasswordUtilisez-le dans un Pod avec imagePullSecrets :
spec: imagePullSecrets: - name: my-registry containers: - name: app image: registry.example.com/myapp:1.5.0Secret depuis un fichier YAML
Section intitulée « Secret depuis un fichier YAML »Pour un contrôle total et la gestion en GitOps :
apiVersion: v1kind: Secretmetadata: name: db-credentialstype: Opaquedata: username: YWRtaW4= # echo -n 'admin' | base64 password: UzNjcjN0IVBAc3M= # echo -n 'S3cr3t!P@ss' | base64Vous pouvez aussi utiliser stringData pour éviter l'encodage manuel :
apiVersion: v1kind: Secretmetadata: name: db-credentialstype: OpaquestringData: username: admin password: S3cr3t!P@ssKubernetes convertit automatiquement stringData en data encodé.
Attention à ne pas y voir une amélioration de sécurité : stringData simplifie
l'écriture du manifeste, il ne rend pas le fichier plus sûr. Le secret y figure
en clair avant application, ce qui est même plus exposé qu'un base64 dans un
historique de shell ou une pièce jointe.
Injecter un Secret dans un Pod
Section intitulée « Injecter un Secret dans un Pod »Deux méthodes principales, chacune avec ses cas d'usage :
| Méthode | Cas d'usage | Mise à jour auto |
|---|---|---|
| Variable d'environnement | Identifiants simples | Jamais |
| Volume | Certificats, fichiers de config | Oui, jusqu'à 2 min |
Méthode 1 : Variables d'environnement
Section intitulée « Méthode 1 : Variables d'environnement »apiVersion: apps/v1kind: Deploymentmetadata: name: myappspec: replicas: 1 selector: matchLabels: app: myapp template: metadata: labels: app: myapp spec: containers: - name: app image: nginx:1.29-alpine@sha256:5616878291a2eed594aee8db4dade5878cf7edcb475e59193904b198d9b830de env: - name: DB_USERNAME valueFrom: secretKeyRef: name: db-credentials key: username - name: DB_PASSWORD valueFrom: secretKeyRef: name: db-credentials key: passwordVérifiez l'injection :
kubectl exec deploy/myapp -- env | grep DB_DB_USERNAME=adminDB_PASSWORD=S3cr3t!P@ssLes valeurs sont décodées automatiquement dans le conteneur : vous ne manipulez jamais de base64 côté application.
Pour charger toutes les clés d'un coup, envFrom accepte un secretRef,
exactement comme il accepte un configMapRef. Chaque clé du Secret devient une
variable portant son nom, ce qui suppose des clés déjà nommées comme des
variables.
envFrom:- secretRef: name: db-credentialsPratique, mais à peser : vous perdez la liste explicite de ce que le Pod reçoit. Une clé ajoutée au Secret arrive dans tous les conteneurs qui le chargent ainsi, sans que leur manifeste change.
Méthode 2 : Monter comme volume
Section intitulée « Méthode 2 : Monter comme volume »Recommandé pour les certificats et fichiers de configuration :
apiVersion: apps/v1kind: Deploymentmetadata: name: myappspec: selector: matchLabels: app: myapp template: metadata: labels: app: myapp spec: containers: - name: app image: nginx:1.29-alpine@sha256:5616878291a2eed594aee8db4dade5878cf7edcb475e59193904b198d9b830de volumeMounts: - name: secret-volume mountPath: /etc/secrets readOnly: true volumes: - name: secret-volume secret: secretName: db-credentials defaultMode: 0400Vérifiez les fichiers montés :
kubectl exec deploy/myapp -- ls -la /etc/secretstotal 8drwxrwxrwt 3 root root 120 Sep 13 16:56 .drwxr-xr-x 1 root root 4096 Sep 13 16:56 ..drwxr-xr-x 2 root root 80 Sep 13 16:56 ..2026_09_13_16_56_55.1418541054lrwxrwxrwx 1 root root 32 Sep 13 16:56 ..data -> ..2026_09_13_16_56_55.1418541054lrwxrwxrwx 1 root root 15 Sep 13 16:56 password -> ..data/passwordlrwxrwxrwx 1 root root 15 Sep 13 16:56 username -> ..data/usernameCette arborescence n'est pas du bruit, elle est le mécanisme de mise à jour.
Les vraies données vivent dans le répertoire horodaté. ..data est un lien
vers lui, et chaque clé est un lien vers ..data. Pour publier une nouvelle
version, le kubelet écrit un répertoire horodaté neuf, puis fait basculer le
seul lien ..data : l'opération est atomique, votre application ne lit
jamais un jeu de fichiers à moitié mis à jour. Comptez jusqu'à deux minutes
avant la bascule, comme pour un ConfigMap, le kubelet additionnant sa période de
synchronisation et la durée de vie de son cache. Mesuré ici : 68 secondes.
Le piège du mode : un zéro qui change tout
Section intitulée « Le piège du mode : un zéro qui change tout »defaultMode attend un entier, et le zéro de tête est ce qui le rend octal.
Les trois écritures ci-dessous n'ont rien d'équivalent, et deux passent sans
la moindre erreur :
| Écriture | Verdict de l'API | Permissions réelles du fichier |
|---|---|---|
defaultMode: 0400 | acceptée | -r--------, ce que vous vouliez |
defaultMode: 400 | acceptée aussi | -rw--w----, groupe en écriture |
defaultMode: "0400" | refusée | cannot unmarshal string ... of type int32 |
Le cas du milieu est le dangereux : un mot de passe monté en lecture seule devient inscriptible par le groupe, sans le moindre avertissement. Notez que la règle est ici l'inverse de celle des modules de fichiers Ansible, où le mode se quote toujours : ce champ-ci refuse la chaîne de caractères.
Encore faut-il pouvoir le vérifier, et c'est là que le montage piège une
seconde fois. La commande réflexe, ls -l, montre le lien symbolique, dont
les permissions sont toujours lrwxrwxrwx et ne veulent rien dire :
kubectl exec mon-pod -- ls -l /etc/secrets/password # le lienkubectl exec mon-pod -- ls -lL /etc/secrets/password # le fichier derrièrelrwxrwxrwx 1 root root 15 /etc/secrets/password -> ..data/password-rw--w---- 1 root root 11 /etc/secrets/passwordC'est ls -lL, qui suit le lien, ou stat sur le fichier réel, qui donnent
la vérité. Plus simple encore, contrôlez avant même de déployer : l'API convertit
le mode octal en décimal, et la valeur stockée trahit immédiatement l'erreur.
kubectl get pod mon-pod -o jsonpath='{.spec.volumes[0].secret.defaultMode}'| Ce que vous avez écrit | Valeur stockée | Permissions obtenues |
|---|---|---|
0400 | 256 | -r-------- |
400 | 400 | -rw--w---- |
0644 | 420 | -rw-r--r-- |
Une valeur stockée identique à ce que vous avez tapé est le signe qu'il manque le zéro de tête.
Monter une seule clé avec un nom personnalisé
Section intitulée « Monter une seule clé avec un nom personnalisé »volumes:- name: secret-volume secret: secretName: db-credentials items: - key: password path: db-passwordLe fichier /etc/secrets/db-password contient uniquement le mot de passe.
Sécurité : les pratiques essentielles
Section intitulée « Sécurité : les pratiques essentielles »Ces cinq pratiques se renforcent mutuellement et aucune ne suffit seule. Le RBAC décide qui peut lire, le chiffrement au repos protège le disque du control plane, le montage en volume limite l'exposition dans le Pod, la rotation borne la durée de vie d'une fuite, et l'immutabilité empêche une modification accidentelle.
1. Restreindre l'accès avec RBAC
Section intitulée « 1. Restreindre l'accès avec RBAC »Par défaut, tout utilisateur avec get sur les Secrets peut les lire. Créez des Roles restrictifs :
apiVersion: rbac.authorization.k8s.io/v1kind: Rolemetadata: name: secret-reader namespace: productionrules:- apiGroups: [""] resources: ["secrets"] resourceNames: ["db-credentials"] # Limiter aux Secrets nécessaires verbs: ["get"]Le verbe list mérite une attention particulière : accordé largement, il permet
d'énumérer tous les noms de Secrets du namespace. Un nom comme
stripe-prod-api-key renseigne déjà un attaquant sur ce qui existe et sur ce
qu'il vaut la peine d'attaquer. Préférez get avec resourceNames, comme
ci-dessus.
2. Activer le chiffrement dans etcd
Section intitulée « 2. Activer le chiffrement dans etcd »Par défaut, Kubernetes stocke les Secrets en clair dans etcd. Activez le chiffrement au repos :
apiVersion: apiserver.config.k8s.io/v1kind: EncryptionConfigurationresources:- resources: - secrets providers: - aescbc: keys: - name: key1 secret: <base64-encoded-32-byte-key> - identity: {}Sur un cluster managé, vous n'avez pas la main sur ce fichier : le chiffrement au repos est un réglage du fournisseur. Ne le supposez pas actif, allez lire sa documentation et vérifiez comment il est configuré, la réponse varie d'un fournisseur à l'autre et parfois d'une option de facturation à l'autre.
3. Préférer le montage volume aux variables d'environnement
Section intitulée « 3. Préférer le montage volume aux variables d'environnement »| Aspect | Variable env | Volume |
|---|---|---|
Visible dans docker inspect | Oui | Non |
Visible dans /proc/<pid>/environ | Oui | Non |
| Mise à jour automatique | Non | Oui |
Le montage en volume est préférable : il limite les expositions accidentelles et bénéficie des mises à jour automatiques. Mais il ne remplace ni un cloisonnement des accès ni un durcissement du runtime : si le conteneur est compromis, les fichiers montés restent lisibles par le processus.
4. Faire la rotation des Secrets
Section intitulée « 4. Faire la rotation des Secrets »Kubernetes n'offre aucune rotation automatique : c'est à vous de remplacer la valeur, puis de forcer les Pods à la reprendre. Le --dry-run=client suivi d'un apply sert à écraser un Secret existant, là où un create seul échouerait sur un objet déjà présent.
# Mettre à jour le Secretkubectl create secret generic db-credentials \ --from-literal=username=admin \ --from-literal=password='N0uv3@uP@ss!' \ --dry-run=client -o yaml | kubectl apply -f -
# Redémarrer les Pods pour appliquerkubectl rollout restart deployment myapp5. Figer un Secret qui ne doit plus bouger
Section intitulée « 5. Figer un Secret qui ne doit plus bouger »Comme un ConfigMap, un Secret accepte le champ immutable: true. L'API
refuse alors toute modification de son contenu, avec le même message :
The Secret "fige" is invalid: data: Forbidden: field is immutable when `immutable` is setLe bénéfice n'est pas que défensif. Un Secret immutable n'est plus surveillé
par les kubelets : sur un gros cluster, cela retire un flux d'observation
permanent par Secret et par nœud. Le coût est le versionnement, qui devient
obligatoire, db-credentials-v2 remplaçant db-credentials.
La contrepartie mérite d'être pesée pour un Secret précisément : une rotation d'urgence, après une fuite, demande alors de créer un nouvel objet et de modifier tous les Pods qui le référencent. Réservez l'immutabilité aux secrets dont la rotation est planifiée, pas à ceux que vous pourriez devoir révoquer dans la minute.
Quand passer à un gestionnaire externe
Section intitulée « Quand passer à un gestionnaire externe »Les Secrets Kubernetes natifs ont des limites :
| Limite | Impact |
|---|---|
| Pas de rotation automatique | Vous devez mettre à jour manuellement |
| Pas d'audit détaillé | Qui a accédé à quel Secret ? |
| Chiffrement etcd à configurer | Pas activé par défaut |
| Secrets visibles en YAML | Risque de commit accidentel |
Utilisez un gestionnaire externe si vous avez besoin de :
- Rotation automatique des secrets
- Audit détaillé des accès
- Intégration avec des secrets existants (AWS, Azure, GCP)
- Secrets dynamiques (credentials de BDD à durée limitée)
Solutions recommandées :
- HashiCorp Vault avec External Secrets ou Vault Sidecar
- AWS Secrets Manager / Azure Key Vault / GCP Secret Manager
- Sealed Secrets pour chiffrer les Secrets dans Git
Debug : problèmes courants
Section intitulée « Debug : problèmes courants »-
Le Secret existe-t-il ?
Fenêtre de terminal kubectl get secret db-credentialsSi "NotFound", créez-le d'abord.
-
La clé existe-t-elle dans le Secret ?
Fenêtre de terminal kubectl get secret db-credentials -o jsonpath='{.data}'Vérifiez l'orthographe exacte de la clé.
-
Le Pod référence-t-il le bon Secret ?
Fenêtre de terminal kubectl describe pod myapp-xxx | grep -A5 "Environment\|Mounts" -
Erreur CreateContainerConfigError ?
Secret ou clé introuvable. Vérifiez que le Secret existe dans le même namespace que le Pod.
Erreurs fréquentes
Section intitulée « Erreurs fréquentes »| Erreur | Cause | Solution |
|---|---|---|
CreateContainerConfigError | Secret ou clé introuvable | Vérifier nom et namespace |
| Variable vide | Clé mal orthographiée | kubectl get secret -o yaml |
unauthorized | RBAC insuffisant | Vérifier les permissions |
La première ne se devine pas, Kubernetes nomme la clé qu'il a cherchée et le Secret où il ne l'a pas trouvée :
kubectl get pod mon-pod \ -o jsonpath='{.status.containerStatuses[0].state.waiting.message}'couldn't find key CLE-ABSENTE in Secret production/db-credentialsÀ retenir
Section intitulée « À retenir »- base64 n'est pas du chiffrement, c'est un encodage de transport qui se décode en une commande
- Ce que le type
Secretapporte réellement :describemasque les valeurs, le volume est monté en tmpfs, et le chiffrement au repos d'etcd ne vise que lui - Le masquage de
describene tient qu'à la commande :get -o yamlrend ledataen clair - Les types de Secrets (Opaque, TLS, docker-registry) structurent vos données
- Variables d'environnement : simples mais jamais rafraîchies ; volumes : mise à jour atomique via
..data, en jusqu'à 2 minutes defaultMode: 0400etdefaultMode: 400sont tous deux acceptés et n'ont pas le même sens ;ls -lmontre le lien,ls -lLmontre le fichier- RBAC est votre première ligne de défense, et le droit de créer un Pod vaut droit de lecture
- Chiffrement etcd : activez-le pour protéger les Secrets au repos
immutable: truefige un Secret et le retire de la surveillance des kubelets, au prix d'une rotation d'urgence plus lourde- Pour la rotation automatique ou l'audit, passez à un gestionnaire externe
Testez vos connaissances
Section intitulée « Testez vos connaissances »Sept questions sur les points qui coûtent cher : ce que base64 ne fait pas, ce qui se rafraîchit, et ce qu'un droit de création de Pod permet vraiment.
Contrôle de connaissances
Validez vos connaissances avec ce quiz interactif
Informations
- Le chronomètre démarre au clic sur Démarrer
- Questions à choix multiples, vrai/faux et réponses courtes
- Vous pouvez naviguer entre les questions
- Les résultats détaillés sont affichés à la fin
Lance le quiz et démarre le chronomètre
Vérification
(0/0)Profil de compétences
Quoi faire maintenant
Ressources pour progresser
Des indices pour retenter votre chance ?
Nouveau quiz complet avec des questions aléatoires
Retravailler uniquement les questions ratées
Retour à la liste des certifications
Pour aller plus loin
Section intitulée « Pour aller plus loin »- ServiceAccounts pour développeurs : L'identité d'un Pod et les jetons projetés qui remplacent les Secrets de type token.
- Security Context : Les permissions du conteneur qui lit un Secret monté en volume.
- RBAC Kubernetes : Les règles qui limitent la lecture des Secrets aux seuls comptes qui en ont besoin.