E-NO
Kubernetes 7 min de lecture

Kubernetes Security Hardening : Guide pratique d'implémentation

calendar_today Publié : 2026-08-13
update Dernière mise à jour : 2026-08-13
analytics Efficacité SEO : 100%
Illustration du guide technique pour « Kubernetes Security Hardening : Guide pratique d'implémentation ».

Le durcissement de la sécurité Kubernetes nécessite d'aller au-delà des configurations par défaut pour implémenter des contrôles de défense en profondeur tout au long du cycle de vie du cluster. Ce guide fournit des procédures observables et adaptées aux versions pour sécuriser les clusters Kubernetes, couvrant la conception RBAC au moindre privilège, les normes de sécurité des pods, la segmentation réseau, la gestion des secrets et la surveillance à l'exécution. Chaque section inclut des commandes de vérification, les sorties attendues et les procédures de retour arrière pour appliquer les changements en toute sécurité dans les environnements de production.

Inventaire de base et des versions du cluster

Avant d'appliquer toute mesure de durcissement, établissez un inventaire complet de vos composants de cluster et de leurs versions. Les correctifs de sécurité et la disponibilité des fonctionnalités varient considérablement entre les versions de Kubernetes, et les étapes de durcissement qui fonctionnent sur v1.28 peuvent ne pas s'appliquer à v1.26 ou se comporter différemment sur v1.29.

Commencez par capturer les versions du control plane et des nœuds :

kubectl version --output=json | jq -r '.serverVersion | "\(.major).\(.minor)"'
kubectl get nodes -o jsonpath='{range .items[*]}{.status.nodeInfo.kubeletVersion}{"\n"}{end}' | sort -u

Enregistrez la version du runtime de conteneurs sur chaque nœud :

kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.nodeInfo.containerRuntimeVersion}{"\n"}{end}'

Documentez le plugin CNI et sa version, car l'application des politiques réseau dépend des capacités du CNI :

kubectl get pods -n kube-system -l k8s-app=cilium -o jsonpath='{range .items[*]}{.spec.containers[0].image}{"\n"}{end}' 2>/dev/null || \
kubectl get pods -n kube-system -l k8s-app=calico-node -o jsonpath='{range .items[*]}{.spec.containers[0].image}{"\n"}{end}' 2>/dev/null || \
echo "CNI non détecté via les labels standards"

Vérifiez les API dépréciées qui seront supprimées dans les versions à venir :

kubectl get --raw=/metrics | grep apiserver_requested_deprecated_apis | head -20

Sauvegardez cet inventaire avec un horodatage. Tout changement de durcissement doit référencer cette base de référence pour vérifier la compatibilité et suivre la dérive.

Implémentation RBAC au moindre privilège

Les installations Kubernetes par défaut accordent souvent des permissions excessives via des liaisons cluster-admin ou des RoleBindings trop larges. Implémentez l'accès au moindre privilège en auditant les liaisons existantes, en créant des rôles à portée limitée et en validant l'accès avant de supprimer les permissions plus larges.

Auditer les liaisons actuelles

Listez tous les ClusterRoleBindings et RoleBindings avec leurs sujets :

kubectl get clusterrolebindings -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.roleRef.name}{"\t"}{.subjects[*].kind}{"\t"}{.subjects[*].name}{"\n"}{end}'
kubectl get rolebindings --all-namespaces -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{.roleRef.name}{"\t"}{.subjects[*].kind}{"\t"}{.subjects[*].name}{"\n"}{end}'

Identifiez les liaisons aux ClusterRoles cluster-admin, admin ou edit assignées à des utilisateurs ou comptes de service qui n'ont pas besoin d'un accès complet au cluster.

Créer des rôles à portée limitée

Pour un pipeline CI/CD qui a seulement besoin de déployer dans l'espace de noms production, créez un Role au lieu d'utiliser le rôle admin à portée d'espace de noms :

# cicd-deploy-role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: production
  name: cicd-deployer
rules:
- apiGroups: ["apps", ""]
  resources: ["deployments", "replicasets", "pods", "services", "configmaps", "secrets"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["networking.k8s.io"]
  resources: ["ingresses"]
  verbs: ["get", "list", "watch", "create", "update", "patch"]

Lie-le au compte de service du pipeline :

# cicd-deploy-binding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  namespace: production
  name: cicd-deployer-binding
subjects:
- kind: ServiceAccount
  name: gitlab-runner
  namespace: cicd
roleRef:
  kind: Role
  name: cicd-deployer
  apiGroup: rbac.authorization.k8s.io

Vérifier et tester

Appliquez le nouveau rôle et la liaison, puis testez l'accès avec kubectl auth can-i :

kubectl apply -f cicd-deploy-role.yaml -f cicd-deploy-binding.yaml
kubectl auth can-i create deployments --as=system:serviceaccount:cicd:gitlab-runner -n production
# Attendu : yes
kubectl auth can-i delete nodes --as=system:serviceaccount:cicd:gitlab-runner -n production
# Attendu : no

Ce n'est qu'après vérification que vous devriez supprimer la liaison plus large. Conservez le YAML de l'ancienne liaison pour un retour arrière immédiat si le rôle à portée limitée manque une permission requise.

Application des normes de sécurité des pods

Les normes de sécurité des pods (Pod Security Standards - PSS) remplacent le PodSecurityPolicy déprécié avec trois profils prédéfinis : privileged, baseline et restricted. Appliquez-les au niveau de l'espace de noms à l'aide de labels, et validez les charges de travail avant d'activer le mode d'application.

Évaluer la conformité actuelle des charges de travail

Exécutez le contrôle d'admission intégré en mode dry-run pour voir quels pods violeraient le profil restricted :

kubectl apply --dry-run=server -f <workload-manifest.yaml> 2>&1 | grep -i "pod security" || echo "Aucune violation PSS détectée"

Pour un audit à l'échelle du cluster, utilisez le plugin kubectl-pss ou le script suivant pour vérifier tous les espaces de noms :

for ns in $(kubectl get ns -o jsonpath='{.items[*].metadata.name}'); do
  echo "=== Espace de noms : $ns ==="
  kubectl get pods -n $ns -o json | jq -r '.items[] | select(.spec.securityContext.runAsNonRoot != true or .spec.containers[].securityContext.allowPrivilegeEscalation != false or .spec.containers[].securityContext.capabilities.add != null) | "\(.metadata.name): viole le profil restricted"'
done

Appliquer les labels d'espace de noms progressivement

Commencez par les modes warn et audit avant enforce. Labellisez un espace de noms de test :

kubectl label namespace staging \
  pod-security.kubernetes.io/enforce=baseline \
  pod-security.kubernetes.io/audit=restricted \
  pod-security.kubernetes.io/warn=restricted

Déployez une charge de travail de test et observez les avertissements :

kubectl run test-pod --image=nginx:alpine -n staging --restart=Never --dry-run=client -o yaml | kubectl apply -f -
# Surveiller : Warning: would violate PodSecurity "restricted:v1.28"

Après avoir confirmé la conformité des charges de travail, passez à enforce=restricted pour les espaces de noms de production. Exemptez les espaces de noms système (kube-system, monitoring, ingress-nginx) avec le profil privileged lorsque les composants requis ont besoin de privilèges élevés.

Vérifier l'application

Tentez de créer un pod privilégié dans un espace de noms restricted :

kubectl run bad-pod --image=nginx --privileged -n production --restart=Never
# Attendu : Error from server (Forbidden): pods "bad-pod" is forbidden: violates PodSecurity "restricted:v1.28"

Segmentation par politiques réseau

La mise en réseau Kubernetes par défaut permet à tous les pods de communiquer entre eux. Les politiques réseau restreignent le trafic aux chemins explicitement autorisés, réduisant le risque de mouvement latéral.

Refus par défaut en entrée

Créez une politique de refus par défaut dans chaque espace de noms d'application :

# default-deny-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
  namespace: production
spec:
  podSelector: {}
  policyTypes:
  - Ingress

Appliquez-la et vérifiez que le trafic existant fonctionne toujours pour les chemins autorisés. Ajoutez ensuite des politiques d'autorisation explicites pour les communications requises.

Autoriser le trafic spécifique

Pour un service frontend qui doit atteindre une API backend :

# frontend-to-backend.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: frontend-to-backend
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: backend
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: frontend
    ports:
    - protocol: TCP
      port: 8080

Contrôle de sortie pour l'accès externe

Restreignez le trafic sortant vers des points de terminaison connus. Autorisez le DNS et des API externes spécifiques :

# backend-egress.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: backend-egress
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: backend
  policyTypes:
  - Egress
  egress:
  - to:
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: kube-system
    ports:
    - protocol: UDP
      port: 53
  - to:
    - ipBlock:
        cidr: 192.0.2.0/24  # CIDR de l'API externe
    ports:
    - protocol: TCP
      port: 443

Vérifier la connectivité

Testez les chemins autorisés et refusés à l'aide de pods temporaires :

# Devrait réussir
kubectl run test-frontend --image=curlimages/curl -n production --restart=Never --rm -it -- curl -s http://backend:8080/health

# Devrait échouer (timeout ou connexion refusée)
kubectl run test-external --image=curlimages/curl -n production --restart=Never --rm -it -- curl -s http://unauthorized-service:8080

Surveillez les rejets de politiques réseau via les métriques spécifiques au CNI (Cilium : cilium_drop_count_total, Calico : calico_network_policy_egress_deny).

Gestion des secrets et chiffrement

Les secrets Kubernetes sont stockés non chiffrés dans etcd par défaut. Activez le chiffrement au repos, utilisez des magasins de secrets externes pour les valeurs sensibles et faites pivoter les identifiants régulièrement.

Activer le chiffrement au repos

Créez un fichier de configuration de chiffrement sur chaque nœud du control plane :

# /etc/kubernetes/encryption-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
  - secrets
  providers:
  - aescbc:
      keys:
      - name: key1
        secret: <clé-32-octets-encodée-base64>
  - identity: {}

Générez une clé forte :

head -c 32 /dev/urandom | base64

Mettez à jour le manifeste kube-apiserver pour référencer cette configuration :

# Dans /etc/kubernetes/manifests/kube-apiserver.yaml
- --encryption-provider-config=/etc/kubernetes/encryption-config.yaml

Redémarrez le serveur API et vérifiez le chiffrement en créant un secret et en consultant etcd directement :

ETCDCTL_API=3 etcdctl get /registry/secrets/default/test-secret --prefix --keys-only
# La sortie doit montrer un préfixe chiffré comme "k8s:enc:aescbc:v1:key1::"

Intégrer un magasin de secrets externe

Déployez l'External Secrets Operator pour synchroniser les secrets depuis HashiCorp Vault, AWS Secrets Manager ou Azure Key Vault :

# cluster-secret-store.yaml
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: vault-backend
spec:
  provider:
    vault:
      server: "https://vault.example.com"
      path: "secret"
      version: "v2"
      auth:
        kubernetes:
          mountPath: "kubernetes"
          role: "external-secrets"

Créez un ExternalSecret qui mappe un chemin Vault vers un secret Kubernetes :

# app-secret.yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: app-db-credentials
  namespace: production
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: vault-backend
    kind: ClusterSecretStore
  target:
    name: db-credentials
    creationPolicy: Owner
  data:
  - secretKey: username
    remoteRef:
      key: database/creds
      property: username
  - secretKey: password
    remoteRef:
      key: database/creds
      property: password

Faire pivoter et auditer

Planifiez la rotation régulière des clés de chiffrement et des identifiants externes. Auditez l'accès aux secrets :

kubectl get --raw=/metrics | grep secret | grep -E '(get|list|watch)' | head -10

Surveillance de sécurité à l'exécution

Le durcissement statique doit être complété par la détection à l'exécution des comportements anormaux. Déployez un agent de sécurité à l'exécution et définissez des procédures de réponse pour les alertes.

Déployer Falco pour la détection à l'exécution

Installez Falco via Helm avec les règles par défaut :

helm repo add falcosecurity https://falcosecurity.github.io/charts
helm install falco falcosecurity/falco -n falco --create-namespace \
  --set falco.jsonOutput=true \
  --set falco.logLevel=info

Règles personnalisées pour votre environnement

Ajoutez une règle pour détecter l'exécution inattendue de shell dans les conteneurs de production :

# falco-custom-rules.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: falco-custom-rules
  namespace: falco
data:
  custom-rules.yaml: |
    - rule: Shell in Production Container
      desc: Détecter l'apparition de shell dans les charges de travail de production
      condition: >
        spawned_process and container and proc.name in (bash, sh, zsh, ksh)
        and k8s.ns.name=production
        and not k8s.pod.label.allow-shell=true
      output: >
        Shell apparu dans un conteneur de production (user=%user.name cmd=%proc.cmdline
        pod=%k8s.pod.name ns=%k8s.ns.name)
      priority: WARNING
      tags: [shell, mitre_execution]

Appliquez et redémarrez Falco :

kubectl apply -f falco-custom-rules.yaml
kubectl rollout restart daemonset/falco -n falco

Routage des alertes et réponse

Transférez les alertes Falco vers votre pile d'observabilité (Prometheus Alertmanager, PagerDuty, Slack). Définissez des runbooks pour chaque règle :

  • Shell dans un conteneur de production : Isoler le pod via une politique réseau, capturer un vidage mémoire, enquêter sur la chaîne d'approvisionnement de l'image.
  • Connexion réseau inattendue : Bloquer la sortie au niveau du CNI, revoir les permissions du compte de service du pod.
  • Écriture de fichier sur un système de fichiers racine en lecture seule : Vérifier l'intégrité de l'image, vérifier la compromission.

Testez l'alerte en déclenchant une règle dans un espace de noms de staging :

kubectl run test-shell --image=ubuntu -n staging --restart=Never -- /bin/bash -c "sleep 30"
# Vérifier que l'alerte se déclenche dans votre système de surveillance

Conclusion

Le durcissement de la sécurité Kubernetes n'est pas une configuration ponctuelle mais un processus continu d'inventaire, d'application du moindre privilège, de segmentation, de chiffrement et de détection à l'exécution. Chaque contrôle de ce guide inclut des étapes de vérification et des procédures de retour arrière pour appliquer les changements de manière incrémentale tout en maintenant la sécurité opérationnelle. Commencez par l'audit de base du cluster, implémentez la limitation RBAC pour vos comptes de service à plus haut risque, activez les normes de sécurité des pods en mode audit sur un espace de noms de staging, et déployez des politiques réseau avec refus par défaut en entrée. Validez chaque couche avant de passer à l'application en production. Planifiez des revues mensuelles des clés de chiffrement, de la rotation des secrets et de l'ajustement des règles Falco pour faire face aux nouveaux vecteurs de menace et aux changements de version de Kubernetes. Un cluster durci rend la compromission difficile, la détection rapide et la récupération prévisible.

Recherches connexes

Score de qualité de l’article

Utilité pour le lecteur 100%
  • check_circle Guide prêt à lire
  • check_circle Exemples pratiques inclus
  • check_circle URL d’article optimisée pour le SEO