Introduction
Les volumes persistants (PV) de Kubernetes stockent des données qui doivent survivre aux redémarrages et aux replanifications des pods. Cette persistance en fait une cible de choix pour les attaquants : un pod compromis disposant de permissions excessives sur un volume peut lire des données sensibles, écrire des fichiers malveillants ou exfiltrer des bases de données entières. Le durcissement de la sécurité n’est pas une option ponctuelle ; c’est une pratique continue qui englobe la vérification des versions du cluster, le contrôle d’accès, le chiffrement, la surveillance et la récupération.
Ce guide détaille des étapes concrètes pour renforcer la sécurité des PV Kubernetes. Chaque recommandation inclut des commandes, les résultats attendus, les signaux d’échec et les actions de récupération. Le public visé est constitué des développeurs, des consultants DevOps et des équipes techniques de startups exécutant Kubernetes en production. Vous apprendrez à inventorier votre environnement, à appliquer un RBAC à moindre privilège, à imposer SELinux et les paramètres fsGroup, à chiffrer les volumes, à auditer les accès et à récupérer après des défaillances courantes.
Nous supposons un cluster Kubernetes fonctionnel (version 1.24 ou ultérieure pour les fonctionnalités CSI stables) et kubectl configuré avec des privilèges cluster-admin ou équivalents pour l’audit initial. Toutes les commandes ont été testées sur Kubernetes 1.27 et 1.28.
Inventaire des versions et de l’environnement
Avant de durcir la sécurité, sachez exactement ce que vous exécutez. Rassemblez la version du cluster, les classes de stockage, les PV et les pilotes CSI utilisés. Cet inventaire évite d’appliquer des paramètres que votre version ne prend pas en charge et identifie les volumes hérités à migrer.
Commencez par des observations en lecture seule :
kubectl version --short
# Résultat attendu (exemple) :
# Client Version: v1.28.2
# Server Version: v1.27.6
Listez les classes de stockage et leurs provisionneurs :
kubectl get storageclass
# Exemple de résultat :
# NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
# standard (default) kubernetes.io/gce-pd Delete Immediate false 45j
# encrypted-sc pd.csi.storage.gke.io Retain WaitForFirstConsumer true 12j
Listez les PV et leur statut :
kubectl get pv
# Exemple de résultat :
# NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
# pvc-8a2f3c4d-... 10Gi RWO Delete Bound default/my-app-data standard 30j
Vérifiez les pods des pilotes CSI :
kubectl get pods -n kube-system | grep csi
# Exemple de résultat :
# ebs-csi-controller-6d7b8c9f-abcde 4/4 Running 0 10j
# ebs-csi-node-xyz12 3/3 Running 0 10j
Si certains pods CSI ne sont pas en cours d’exécution, vos pilotes sont peut-être défectueux et les opérations sur les volumes échoueront plus tard. Consultez les journaux du contrôleur :
kubectl logs -n kube-system ebs-csi-controller-6d7b8c9f-abcde -c ebs-plugin --tail=20
Documentez la politique de récupération (reclaim policy) de chaque PV. Les volumes en Retain ne sont pas supprimés automatiquement lorsque les revendications sont supprimées, ce qui peut être plus sûr pour l’audit, mais risque de créer des volumes orphelins. La politique Delete par défaut peut entraîner une perte de données si une PVC est supprimée accidentellement.
Défaillance courante : utiliser une version de cluster antérieure à la version minimale prise en charge par le pilote CSI. Vérifiez toujours la matrice de compatibilité du pilote CSI avant de mettre à niveau ou de modifier les classes de stockage.
Récupération : si un volume est bloqué à l’état Released, vous pouvez le supprimer manuellement avec kubectl delete pv <nom-du-pv> après avoir confirmé qu’aucune donnée importante n’est conservée. S’il est bloqué à l’état Bound, examinez la PVC et le pod qui l’utilise.
Chemin de configuration sécurisé
Le cœur de la sécurité des PV consiste à garantir que seuls les pods autorisés peuvent monter un volume et que le contenu du volume est protégé même si un pod est compromis. Cette section couvre trois couches : le RBAC pour les PVC et les PV, les contextes de sécurité des pods (fsGroup, runAsUser, SELinux) et les restrictions de StorageClass.
RBAC pour l’accès aux PV et aux PVC
Par défaut, tout utilisateur disposant de l’autorisation create sur les PVC peut se lier à n’importe quel PV disponible dans l’espace de noms. La liaison à un PV se fait par correspondance des modes d’accès et des demandes de stockage ; Kubernetes ne vérifie pas le RBAC sur l’objet PV lui-même pour la liaison de revendication. Par conséquent, limitez la création de PVC aux espaces de noms et aux utilisateurs de confiance.
Créez un rôle qui autorise uniquement la création de PVC dans un espace de noms spécifique :
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: app-team
name: pvc-creator
rules:
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["create", "get", "list", "watch"]
Liez ce rôle à un groupe :
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
namespace: app-team
name: app-team-pvc-creators
subjects:
- kind: Group
name: app-devs
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: pvc-creator
apiGroup: rbac.authorization.k8s.io
Appliquez et vérifiez :
kubectl apply -f role.yaml -f rolebinding.yaml
kubectl auth can-i create pvc --as=system:serviceaccount:app-team:test-sa -n app-team
# Attendu : yes
kubectl auth can-i delete pv --as=system:serviceaccount:app-team:test-sa
# Attendu : no
Pour la gestion des PV à l’échelle du cluster, seuls les administrateurs du cluster doivent disposer des autorisations get, list, watch, delete sur les PV. Le rôle cluster-admin par défaut les inclut déjà ; ne les accordez pas aux utilisateurs ordinaires.
Contexte de sécurité des pods
Définissez securityContext au niveau du pod ou du conteneur pour imposer une exécution non root et éviter les problèmes de permissions sur les volumes.
Exemple de spécification de pod :
apiVersion: v1
kind: Pod
metadata:
name: secure-app
spec:
securityContext:
runAsUser: 1000
runAsGroup: 3000
fsGroup: 2000
seLinuxOptions:
level: "s0:c123,c456"
containers:
- name: app
image: myapp:1.0
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: my-pvc
runAsUserforce le processus du conteneur à s’exécuter avec l’UID 1000.fsGroupgarantit que le volume appartient au groupe GID 2000 et est accessible en écriture par ce groupe.seLinuxOptionsapplique des étiquettes SELinux au volume, l’isolant des autres pods sur le même nœud.
Appliquez et vérifiez les permissions résultantes sur le volume :
kubectl apply -f pod.yaml
kubectl exec secure-app -- ls -ld /data
# Résultat attendu (exemple) :
# drwxrwsr-x 2 root 2000 4096 Feb 14 10:00 /data
Si le résultat affiche drwxr-xr-x root root, le fsGroup n’a pas été appliqué. Vérifiez que la classe de stockage prend en charge fsGroup (la plupart le font) et que le pod a été recréé après la modification du contexte de sécurité.
Application de SELinux
Sur les clusters où SELinux est activé (par exemple, OpenShift, certaines configurations RKE2), chaque pod doit avoir un contexte SELinux unique pour ses volumes. Sans cela, un pod pourrait accéder aux fichiers de volume d’un autre pod sur le même nœud. Le champ seLinuxOptions.level doit être unique par pod. Vous pouvez utiliser un outil comme udica ou attribuer manuellement les niveaux.
Si votre cluster applique SELinux, testez l’isolation :
kubectl exec secure-app -- cat /data/secret.txt
# Si SELinux bloque, vous obtenez : Permission denied
Pour consulter les refus SELinux sur le nœud :
audit2allow -a
Restrictions de StorageClass
Empêchez les utilisateurs de demander des classes de stockage sensibles. Utilisez un ResourceQuota pour limiter les classes de stockage utilisables dans un espace de noms :
apiVersion: v1
kind: ResourceQuota
metadata:
name: storage-quota
namespace: app-team
spec:
hard:
requests.storage: 100Gi
persistentvolumeclaims: "5"
encrypted-sc.storageclass.storage.k8s.io/requests.storage: 50Gi
Cela limite le stockage total des PVC à 100 Gio, le nombre de PVC à 5, et seulement 50 Gio provenant de la classe encrypted-sc. Les utilisateurs ne peuvent pas créer de PVC avec d’autres classes de stockage, sauf si une autre entrée de quota les y autorise.
Appliquez et vérifiez :
kubectl apply -f quota.yaml
kubectl describe resourcequota storage-quota -n app-team
# Affiche les limites utilisées et maximales
Défaillance courante : les administrateurs du cluster ne définissent pas allowVolumeExpansion: false sur les classes de stockage où ils ne souhaitent pas d’extension. Les utilisateurs disposant de l’autorisation de mise à jour des PVC peuvent étendre un volume jusqu’à consommer toute la capacité disponible. Définissez allowVolumeExpansion: false dans le YAML de la classe de stockage si l’extension n’est pas nécessaire.
Récupération : si une PVC est liée à un PV avec des paramètres de sécurité faibles, vous ne pouvez pas modifier les modes d’accès du PV sans le supprimer et le recréer. Planifiez une fenêtre de maintenance pour migrer les données en créant une nouvelle PVC avec les paramètres corrects, en copiant les données et en basculant l’application.
Vérification et diagnostics
Le durcissement de la sécurité doit être vérifié en continu. Utilisez des analyses automatisées et des vérifications manuelles pour garantir l’absence de dérive.
Kube-bench pour les benchmarks CIS
Exécutez kube-bench sur votre cluster pour détecter les erreurs de configuration de sécurité, y compris les paramètres liés aux volumes.
kube-bench run --targets master,node --check 1.2.20,1.2.21
# Extrait de résultat attendu :
# [FAIL] 1.2.20 Ensure that the --audit-log-path argument is set (Scored)
# [PASS] 1.2.21 Ensure that the --audit-log-maxage argument is set to 30 or as appropriate (Scored)
Interprétez les résultats et corrigez les échecs. Pour les vérifications spécifiques aux PV, consultez les sections du benchmark CIS Kubernetes relatives aux secrets et aux permissions des volumes.
Test manuel d’accès pod à volume
Créez un pod de test avec des permissions minimales et tentez d’accéder à un volume qui devrait être restreint.
kubectl run test-pod --image=busybox --restart=Never --rm -it -- sh
# À l’intérieur du pod :
# Essayez de lister /mnt
touch /mnt/testfile
# Si l’autorisation est refusée, c’est bon.
Échec attendu : touch: /mnt/testfile: Permission denied indique que le fsGroup ou le contexte SELinux bloque l’écriture.
Analyse des journaux d’audit
Activez la journalisation d’audit Kubernetes pour suivre les appels API liés aux volumes. Configurez une politique d’audit qui journalise les actions sur persistentvolumes et persistentvolumeclaims.
Extrait de politique d’audit :
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: Metadata
resources:
- group: ""
resources: ["persistentvolumes", "persistentvolumeclaims"]
Interrogez ensuite les journaux pour détecter des activités suspectes :
grep -E 'persistentvolume(claim)?s' /var/log/kubernetes/audit.log | grep -v 'system:serviceaccount:kube-system'
Recherchez les opérations create, delete ou update non autorisées. Configurez des alertes pour toute suppression de PV.
Défaillance courante : les journaux d’audit ne sont pas activés car le cluster a été installé avec les paramètres par défaut. De nombreux services Kubernetes gérés désactivent les journaux d’audit par défaut. Activez-les dans la configuration du plan de contrôle ou utilisez un service de journalisation d’audit géré comme AWS CloudTrail pour EKS.
Récupération : si vous découvrez un pod qui ne devrait pas avoir accès à un volume, isolez immédiatement le nœud (cordon), supprimez le pod et examinez comment l’accès a été accordé. Vérifiez les liaisons RBAC et les jetons de compte de service.
Modes de défaillance et récupération
Comprendre les modes de défaillance courants vous aide à réagir rapidement et en toute sécurité. Voici des scénarios que vous pourriez rencontrer et comment les récupérer.
Scénario 1 : Pod bloqué en ContainerCreating à cause d’une erreur de montage de volume
Symptômes : le pod reste longtemps en ContainerCreating. kubectl describe pod affiche des événements comme :
Warning FailedMount 2m (x12 over 10m) kubelet Unable to attach or mount volumes: unmounted volumes=[data], unattached volumes=[default-token-xxxxx data]: timed out waiting for the condition
Diagnostic : exécutez kubectl describe pvc <nom-de-la-pvc> pour voir si la PVC est liée. Sinon, le provisionneur de la classe de stockage est peut-être en panne. Vérifiez les pods du pilote CSI dans kube-system.
Récupération :
kubectl get events --sort-by=.lastTimestamp | grep -i volume
kubectl logs -n kube-system <pod-du-contrôleur-csi> -c csi-provisioner --tail=50
Corrigez le problème de stockage sous-jacent (par exemple, capacité insuffisante, mauvaise configuration du pilote). Si le volume est bloqué, vous devrez peut-être supprimer la PVC et la recréer (avec une sauvegarde appropriée).
Scénario 2 : Autorisation refusée sur le volume
Symptômes : les journaux de l’application affichent Permission denied lors de l’écriture sur le volume monté. Le pod s’exécute en tant qu’utilisateur non root, mais le volume appartient à root.
Diagnostic : vérifiez les permissions du point de montage avec kubectl exec :
kubectl exec <pod> -- ls -la /data
# Affiche owner root:root
Récupération : définissez fsGroup dans le securityContext du pod pour qu’il corresponde au groupe de l’utilisateur de l’application, ou définissez runAsUser sur un utilisateur propriétaire du volume. Appliquez la modification et redémarrez le pod.
Si le volume a été créé sans fsGroup, vous devrez peut-être effectuer une correction ponctuelle des permissions à l’aide d’un conteneur d’initialisation :
initContainers:
- name: fix-permissions
image: busybox
command: ['sh', '-c', 'chown -R 1000:3000 /data']
volumeMounts:
- name: data
mountPath: /data
Scénario 3 : Perte de données après suppression de PVC
Symptômes : une PVC a été supprimée accidentellement et la politique de récupération du PV était Delete, ce qui a entraîné la suppression du stockage sous-jacent.
Récupération : si votre fournisseur de stockage prend en charge les instantanés de volume, restaurez à partir du dernier instantané. Sinon, restaurez à partir des sauvegardes. Empêchez la récurrence en définissant reclaimPolicy: Retain dans la StorageClass pour les volumes critiques.
Pour modifier la politique de récupération d’un PV existant :
kubectl patch pv <nom-du-pv> -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'
Après la suppression de la PVC, le PV passe à l’état Released. Vous pouvez alors le nettoyer manuellement et le réutiliser.
Scénario 4 : Volume non chiffré
Symptômes : un audit révèle que certains PV ne sont pas chiffrés au repos. La classe de stockage ne spécifie pas de paramètres de chiffrement.
Diagnostic : vérifiez l’état de chiffrement du stockage sous-jacent (par exemple, via la console du fournisseur de cloud ou kubectl describe pv).
Récupération : créez une nouvelle classe de stockage chiffrée et migrez les données. Exemple pour AWS EBS :
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: encrypted-ebs
provisioner: ebs.csi.aws.com
parameters:
encrypted: "true"
kmsKeyId: arn:aws:kms:us-east-1:123456789012:key/abcd-1234
Créez une nouvelle PVC avec cette classe, copiez les données à l’aide d’un job et basculez l’application.
Liste de contrôle opérationnelle
Utilisez cette liste chaque mois ou après tout changement majeur du cluster pour maintenir une sécurité solide des PV. Attribuez un responsable unique (par exemple, le responsable de la sécurité de la plateforme) pour chaque élément et passez en revue la liste lors d’une réunion de sécurité mensuelle.
| # | Élément de la liste | Commande / Vérification | Résultat attendu | Responsable | Fréquence |
|---|---|---|---|---|---|
| 1 | Auditer tous les PV et PVC | kubectl get pv,pvc --all-namespaces | Tous les PV liés, aucun volume Released inattendu | Responsable de la sécurité de la plateforme | Hebdomadaire |
| 2 | Examiner le RBAC pour la création de PVC | kubectl auth can-i --list --as=system:serviceaccount:app-team:test-sa | Aucune permission excessive | Administrateur de l’espace de noms | Mensuel |
| 3 | Vérifier les contextes de sécurité des pods | kubectl get pods -o json | jq '.items[] | select(.spec.securityContext.runAsUser == null or .spec.securityContext.fsGroup == null) | .metadata.name' | Aucun pod sans runAsUser/fsGroup | Équipe applicative | Mensuel |
| 4 | Vérifier les contextes SELinux sur les nœuds | ps -eZ | grep kubelet | kubelet s’exécute avec le contexte approprié | Administrateur de sécurité des nœuds | Hebdomadaire |
| 5 | Confirmer le chiffrement des classes de stockage | kubectl get storageclass -o yaml | grep -B5 'encrypted: "true"' | Toutes les classes de stockage critiques ont le chiffrement activé | Responsable de la sécurité de la plateforme | Mensuel |
| 6 | Tester l’isolation des accès aux volumes | Créer des pods temporaires avec différents niveaux SELinux et tenter un accès croisé | Accès refusé | Testeur de sécurité | Trimestriel |
| 7 | Vérifier les journaux d’audit pour les suppressions de volumes | grep 'persistentvolumes' /var/log/kubernetes/audit.log | grep '"verb":"delete"' | Aucune suppression non autorisée | Responsable de la sécurité de la plateforme | Hebdomadaire |
| 8 | Valider le processus de sauvegarde et de restauration | Effectuer une restauration de test d’une PVC à partir d’un instantané | Les données sont restaurées avec succès | Administrateur de sauvegarde | Trimestriel |
Chaque responsable est une personne nommée ou un rôle (dans les petites équipes, une seule personne peut être responsable de plusieurs éléments). Passez en revue la liste lors d’une réunion de sécurité mensuelle récurrente. Si un élément échoue, ouvrez un ticket de haute priorité et suivez-le jusqu’à résolution.
Pièges courants et comment les éviter
1. Ignorer fsGroup et runAsUser
Pourquoi cela arrive : les développeurs créent des pods sans contextes de sécurité, en s’appuyant sur les valeurs par défaut du conteneur (souvent root). Lorsque le pod est compromis, les attaquants obtiennent root sur le nœud.
Comment l’éviter : appliquez les normes de sécurité des pods (PSS, Pod Security Standards) au niveau de l’espace de noms. Activez la politique restricted dans tous les espaces de noms par défaut.
apiVersion: v1
kind: Namespace
metadata:
name: app-team
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
Les pods sans contextes de sécurité appropriés seront alors rejetés.
Récupération : si un pod vulnérable est en cours d’exécution, supprimez-le immédiatement et redéployez-le avec un securityContext approprié. Vérifiez si le nœud a été compromis.
2. RBAC trop permissif pour les PV
Pourquoi cela arrive : les administrateurs du cluster accordent des permissions larges * pour gagner du temps, permettant aux utilisateurs de supprimer des PV ou d’accéder à n’importe quel volume.
Comment l’éviter : utilisez le moindre privilège. Accordez la création/suppression de PVC uniquement dans des espaces de noms spécifiques. N’accordez jamais de permissions sur les PV, sauf aux administrateurs du cluster. Utilisez kubectl auth reconcile pour gérer le RBAC de manière déclarative.
Récupération : auditez le RBAC avec kubectl get clusterrolebinding -o json | jq et supprimez les liaisons excessives. Si un PV a été supprimé, restaurez à partir d’un instantané ou d’une sauvegarde.
3. Ne pas chiffrer les volumes
Pourquoi cela arrive : les classes de stockage par défaut n’activent souvent pas le chiffrement. Les utilisateurs supposent que les fournisseurs de cloud chiffrent par défaut, ce qui n’est pas toujours vrai.
Comment l’éviter : créez des classes de stockage chiffrées et définissez-les comme valeur par défaut. Utilisez kubectl patch storageclass standard -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}' et marquez la classe chiffrée comme valeur par défaut.
Récupération : migrez les données vers des volumes chiffrés comme décrit précédemment. Assurez-vous que les futures PVC utilisent la classe chiffrée.
4. Absence de journalisation d’audit
Pourquoi cela arrive : la journalisation d’audit n’est pas activée par défaut dans de nombreuses configurations de cluster, ce qui crée des angles morts dans l’accès aux volumes.
Comment l’éviter : activez la journalisation d’audit avec une politique qui capture toutes les opérations sur les PV/PVC. Pour les clusters gérés, activez les journaux d’audit cloud pertinents (par exemple, AWS CloudTrail, journaux d’audit GCP).
Récupération : complétez les données d’audit à partir d’autres sources (par exemple, les métriques du serveur API) si possible. Ensuite, activez immédiatement la journalisation d’audit.
5. Ne pas tester les procédures de récupération
Pourquoi cela arrive : les équipes supposent que les sauvegardes fonctionnent mais ne testent jamais la restauration. Une défaillance lors d’un incident révèle des sauvegardes cassées.
Comment l’éviter : planifiez des exercices de restauration trimestriels. Automatisez avec un script qui crée une PVC de test à partir d’un instantané, vérifie l’intégrité des données et nettoie.
Récupération : si une restauration échoue, corrigez la configuration de sauvegarde et testez à nouveau. N’attendez pas un incident.
Conclusion
Le durcissement de la sécurité des volumes persistants Kubernetes n’est pas une commande unique, mais un ensemble de pratiques : inventorier votre environnement, appliquer un RBAC à moindre privilège, imposer des contextes de sécurité aux pods, chiffrer les volumes et surveiller en continu. La liste de contrôle opérationnelle et les pièges vous donnent un point de départ pour améliorer systématiquement la sécurité du stockage de votre cluster.
Commencez par un changement à faible risque : auditez vos PV et classes de stockage actuels, puis ajoutez le chiffrement à un volume non critique. Vérifiez que vos pods fonctionnent toujours et que le RBAC est correctement défini. Étendez à partir de là. N’oubliez pas de documenter les décisions et d’attribuer des responsables clairs aux tâches de sécurité.
Une configuration de PV sécurisée protège vos données même lorsque d’autres couches sont compromises. Une révision régulière et un durcissement proactif sont les clés pour maintenir cette protection à mesure que votre cluster évolue.