Introduction
Les Kubernetes Secrets contiennent des identifiants, tokens, clés d'API et autres configurations sensibles. Traitez-les comme des "joyaux de la couronne". Ce guide pratique montre comment appliquer Kubernetes Secrets hardening dans des clusters réels, à l'aide de YAML et de commandes kubectl. Vous apprendrez à réduire le rayon d'impact via le RBAC, à éviter d'exposer des Secrets dans les environnements des pods, à activer le chiffrement au repos, à restreindre l'exposition réseau, et à ajouter des contrôles simples et répétés.
Ce que vous allez faire :
- Inventorier et classifier les Secrets par sensibilité et propriétaire
- Activer le chiffrement au repos pour les Secrets dans le data store de l'API server
- Appliquer le moindre privilège via RBAC et comptes de service par workload
- Consommer les Secrets via des fichiers plutôt que des variables d'environnement
- Ajouter des NetworkPolicies par défaut en deny avec des autorisations explicites
- Exécuter des contrôles simples, peu risqués, pour valider le durcissement
Astuce SEO/termes usuels : ce guide illustre Kubernetes Secrets security, Kubernetes Secrets hardening, Kubernetes Secrets access control, Kubernetes Secrets permissions et, plus largement, la gestion de Kubernetes Secrets secrets dans des environnements de production.
Vue d'ensemble du flux
Adoptez un déroulé par étapes que vous pouvez appliquer namespace par namespace : 1) inventaire et classification, 2) chiffrement au repos, 3) verrouillage RBAC et comptes de service, 4) usage sûr des Secrets dans les pods, 5) confinement réseau, 6) contrôles et rotation.
1) Inventorier et classifier les Secrets
Commencez par lister les Secrets sur tous les namespaces et étiqueter les propriétaires.
Lister les Secrets avec leurs types :
kubectl get secrets -A -o wide
Ajouter des labels homogènes pour pouvoir requêter par propriétaire et usage :
kubectl label secret db-credentials owner=payments purpose=database -n team-apps --overwrite
Créer de nouveaux Secrets avec un nommage clair et un bon périmètre :
kubectl create secret generic db-credentials \
--from-literal=username=app \
--from-literal=password='S3cr3t!' \
-n team-apps
Bonnes pratiques : préférez des Secrets dédiés par application/composant plutôt que des Secrets partagés entre applications. Cela réduit le rayon d'impact et simplifie Kubernetes Secrets access control.
2) Chiffrer les Secrets au repos
Les Secrets sont stockés par l'API server. Activez l'encryption au repos pour qu'ils soient chiffrés sur disque.
Exemple d'EncryptionConfiguration (fournisseur aescbc) :
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aescbc:
keys:
- name: key1
secret: <base64-encoded-32-byte-key>
- identity: {}
Étapes clés :
- Placer le fichier EncryptionConfiguration sur les nœuds du plan de contrôle.
- Configurer l'API server pour l'utiliser (par exemple via --encryption-provider-config).
- Redémarrer l'API server et ré-encrypter les données existantes si nécessaire.
Sur des clusters managés, utilisez le paramètre de la plateforme qui active le chiffrement des Secrets.
3) Verrouiller le RBAC et les comptes de service
N'accordez la lecture de Secrets qu'aux workloads qui en ont besoin, et si possible seulement sur des noms précis.
ServiceAccount sans montage automatique du token :
apiVersion: v1
kind: ServiceAccount
metadata:
name: web-sa
namespace: team-apps
automountServiceAccountToken: false
Role autorisant la lecture d'un seul Secret par nom :
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: read-db-credentials
namespace: team-apps
rules:
- apiGroups: ['']
resources: ['secrets']
resourceNames: ['db-credentials']
verbs: ['get']
RoleBinding pour ce ServiceAccount :
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: web-sa-read-db-credentials
namespace: team-apps
subjects:
- kind: ServiceAccount
name: web-sa
namespace: team-apps
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: read-db-credentials
Vérification rapide :
kubectl auth can-i get secret db-credentials \
--as=system: serviceaccount: team-apps: web-sa \
-n team-apps
4) Utiliser les Secrets en toute sécurité dans les pods
Privilégiez le montage des Secrets en fichiers plutôt que leur exposition en variables d'environnement. Les variables peuvent fuiter dans les logs, rapports de crash et /proc.
Pod montant un Secret en fichiers en lecture seule :
apiVersion: v1
kind: Pod
metadata:
name: web
namespace: team-apps
spec:
serviceAccountName: web-sa
automountServiceAccountToken: false
containers:
- name: app
image: your-registry/web:1.0.0
volumeMounts:
- name: db-secret
mountPath: /var/run/secrets/app
readOnly: true
env:
- name: DB_USER_FILE
value: /var/run/secrets/app/username
- name: DB_PASS_FILE
value: /var/run/secrets/app/password
volumes:
- name: db-secret
secret:
secretName: db-credentials
Dans votre application, lisez le contenu des fichiers au démarrage au lieu d'utiliser des variables d'environnement. Si vous devez utiliser des variables, limitez-les aux seuls conteneurs qui en ont réellement besoin et évitez les projections globales de type envFrom.
Secrets immuables pour prévenir les modifications accidentelles :
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
namespace: team-apps
type: Opaque
immutable: true
data:
username: YXBw
password: UzNjcjN0IQ==
Pour la rotation, créez un nouveau Secret (par ex. db-credentials-v2), mettez à jour le Deployment pour le référencer, puis supprimez l'ancien après vérification.
5) Contenir l'exposition réseau
Les NetworkPolicies ne protègent pas directement les Secrets, mais elles limitent les mouvements latéraux et réduisent le risque d'exfiltration depuis un pod compromis.
Deny-all par défaut en ingress et egress :
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: team-apps
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
Autoriser la sortie uniquement vers le DNS (adaptez selon votre cluster) :
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns-egress
namespace: team-apps
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
Ajoutez ensuite des politiques d'autorisation explicites pour les dépendances nécessaires.
6) Ajouter des contrôles rapides et des audits
Exécutez régulièrement ces vérifications légères.
Qui peut lire quels Secrets :
# Lister les permissions effectives d'un ServiceAccount
kubectl auth can-i --list \
--as=system: serviceaccount: team-apps: web-sa \
-n team-apps | grep secrets || true
Trouver les pods qui référencent des Secrets via des variables d'environnement (jq) :
kubectl get pods -A -o json | jq -r '
.items[]
| select((.spec.containers // [])
| map(.env // [] | map(has("valueFrom") and .valueFrom.secretKeyRef != null) | any)
| any)
| [.metadata.namespace, .metadata.name] | @tsv'
Lister les Secrets non immuables :
kubectl get secrets -A -o json | jq -r '
.items[] | select(.immutable != true) | [.metadata.namespace, .metadata.name] | @tsv'
Détecter les Roles/ClusterRoles trop larges lisant de nombreux Secrets :
kubectl get role, clusterrole -A -o yaml | grep -n 'resources: \[secrets\]' -n || true
Confirmez la configuration d'encryption au repos en inspectant les flags de l'API server et le fichier du provider d'encryption sur les nœuds du plan de contrôle, ou l'option équivalente en cluster managé.
Plan pilote local
Démarrez petit pour mesurer les progrès et limiter les risques.
Périmètre
- Namespace : team-apps
- Workload : un Deployment unique nommé web qui a besoin de db-credentials
Critères de succès
- Seul le ServiceAccount web-sa peut lire le Secret db-credentials dans team-apps
- Aucun pod dans team-apps ne consomme des Secrets via des variables d'environnement
- Le namespace a une NetworkPolicy deny-all avec des autorisations explicites
Étapes
- Créer le namespace et le compte de service
kubectl create namespace team-apps
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: ServiceAccount
metadata:
name: web-sa
namespace: team-apps
automountServiceAccountToken: false
EOF
- Créer le Secret
kubectl create secret generic db-credentials \
--from-literal=username=app \
--from-literal=password='S3cr3t!' \
-n team-apps
- Appliquer le RBAC de moindre privilège
kubectl apply -f - <<'EOF'
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: read-db-credentials
namespace: team-apps
rules:
- apiGroups: ['']
resources: ['secrets']
resourceNames: ['db-credentials']
verbs: ['get']
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: web-sa-read-db-credentials
namespace: team-apps
subjects:
- kind: ServiceAccount
name: web-sa
namespace: team-apps
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: read-db-credentials
EOF
- Déployer l'application en montant les Secrets comme fichiers
kubectl apply -f - <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
namespace: team-apps
spec:
replicas: 1
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
serviceAccountName: web-sa
automountServiceAccountToken: false
containers:
- name: app
image: your-registry/web:1.0.0
volumeMounts:
- name: db-secret
mountPath: /var/run/secrets/app
readOnly: true
env:
- name: DB_USER_FILE
value: /var/run/secrets/app/username
- name: DB_PASS_FILE
value: /var/run/secrets/app/password
volumes:
- name: db-secret
secret:
secretName: db-credentials
EOF
- Appliquer deny-all et autoriser le DNS
kubectl apply -f - <<'EOF'
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: team-apps
spec:
podSelector: {}
policyTypes: [Ingress, Egress]
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns-egress
namespace: team-apps
spec:
podSelector: {}
policyTypes: [Egress]
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
EOF
- Valider
# Seul web-sa peut lire db-credentials
kubectl auth can-i get secret db-credentials \
--as=system: serviceaccount: team-apps: web-sa \
-n team-apps
# Le SA par défaut ne doit pas pouvoir
kubectl auth can-i get secret db-credentials \
--as=system: serviceaccount: team-apps: default \
-n team-apps
# Aucun pod ne consomme des Secrets via env vars
kubectl get pods -n team-apps -o json | jq -r '
.items[]
| select((.spec.containers // [])
| map(.env // [] | map(has("valueFrom") and .valueFrom.secretKeyRef != null) | any)
| any)
| .metadata.name' | wc -l
# Les objets NetworkPolicy existent
kubectl get networkpolicy -n team-apps
- Documenter la rotation de db-credentials
- Créer db-credentials-v2
- Mettre à jour le Deployment pour référencer db-credentials-v2
- Redéployer les pods et vérifier
- Supprimer db-credentials après validation
Plan de rollback
- Conserver le Secret précédent jusqu'à validation complète
- En cas d'échec, rétablir le Deployment sur l'ancien Secret et relancer les tests
Conclusion
Vous disposez désormais d'une démarche pratique pour durcir la sécurité des Kubernetes Secrets : inventaire, chiffrement au repos, RBAC de moindre privilège, limitation des variables d'environnement, confinement réseau et contrôles répétables. Démarrez par un namespace pilote, mesurez, puis étendez progressivement.
Métriques à suivre en continu :
- Nombre de Secrets par namespace et présence des labels propriétaire
- Nombre de Roles lisant des Secrets arbitraires vs noms spécifiques
- Pods consommant des Secrets via variables d'environnement vs montages fichiers
- Namespaces couverts par des NetworkPolicies deny-all
Cette approche par étapes rend les changements sûrs et observables, tout en améliorant votre posture de sécurité pas à pas.