Les 10 risques de sécurité les plus critiques dans les environnements Kubernetes et comment les atténuer.
L'OWASP Kubernetes Top 10 identifie les risques de sécurité les plus importants dans les environnements Kubernetes. Kubernetes est devenu la norme de facto pour l'orchestration de conteneurs, mais sa flexibilité et sa complexité introduisent de nombreux défis de sécurité. Ce guide couvre les mauvaises configurations de charges de travail, les problèmes RBAC, la gestion des secrets, la segmentation réseau, et plus encore.
Les configurations de charges de travail non sécurisées sont le problème de sécurité Kubernetes le plus courant. Les conteneurs exécutés en tant que root, avec des privilèges excessifs, des systèmes de fichiers en écriture, ou sans limites de ressources créent des surfaces d'attaque importantes. Les configurations par défaut sont souvent non sécurisées et doivent être explicitement renforcées.
Un conteneur compromis avec des privilèges root et un accès à l'hôte peut s'échapper du sandbox du conteneur, accéder au système de fichiers de l'hôte et pivoter vers d'autres charges de travail. L'escalade de privilèges à partir d'un pod mal configuré peut conduire à la compromission complète du cluster.
apiVersion: v1 kind: Pod metadata: name: insecure-app spec: containers: - name: app image: myapp:latest # Mutable tag! securityContext: privileged: true # Full host access! runAsUser: 0 # Running as root! volumeMounts: - mountPath: /host name: host-fs volumes: - name: host-fs hostPath: path: / # Entire host filesystem!
apiVersion: v1 kind: Pod metadata: name: secure-app spec: securityContext: runAsNonRoot: true seccompProfile: type: RuntimeDefault containers: - name: app image: myapp@sha256:abc123... # Immutable digest securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true runAsUser: 1000 capabilities: drop: ["ALL"] resources: limits: cpu: "500m" memory: "256Mi" requests: cpu: "100m" memory: "128Mi"
Le RBAC (Role-Based Access Control) de Kubernetes est puissant mais complexe. Les rôles trop permissifs Een particulier les liaisons cluster-admin, les permissions génériques et les privilèges excessifs des comptes de service Epermettent un accès non autorisé aux ressources du cluster, aux secrets et aux charges de travail.
Un attaquant qui obtient l'accès à un compte de service sur-privilégié peut lister les secrets, créer des pods privilégiés, modifier les déploiements et escalader vers cluster-admin. Les règles RBAC génériques sont l'équivalent Kubernetes de l'octroi d'un accès root.
# ClusterRoleBinding granting cluster-admin to a service account apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: app-admin-binding subjects: - kind: ServiceAccount name: my-app # App SA with cluster-admin! namespace: default roleRef: kind: ClusterRole name: cluster-admin # Full cluster control! apiGroup: rbac.authorization.k8s.io
# Scoped Role with minimum required permissions apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: app-role namespace: my-app-ns rules: - apiGroups: [""] resources: ["configmaps"] verbs: ["get", "list"] # Read-only, specific resources resourceNames: ["app-config"] # Named resource only --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: app-role-binding namespace: my-app-ns subjects: - kind: ServiceAccount name: my-app namespace: my-app-ns roleRef: kind: Role name: app-role apiGroup: rbac.authorization.k8s.io
Les Secrets Kubernetes sont uniquement encodés en base64 par défaut, et non chiffrés. Stocker des données sensibles (clés API, mots de passe de base de données, certificats TLS) dans des ConfigMaps ordinaires, des variables d'environnement ou des Secrets non chiffrés les expose à toute personne ayant accès à l'API. Les secrets sont également visibles dans etcd si le chiffrement au repos n'est pas activé.
Les secrets exposés permettent aux attaquants d'accéder aux bases de données, aux comptes cloud et aux services externes. Les secrets stockés dans etcd sans chiffrement peuvent être lus par toute personne ayant accès à etcd. Les secrets de variables d'environnement sont visibles dans les spécifications de pods et les listes de processus.
# Secret in plain ConfigMap Evisible to anyone apiVersion: v1 kind: ConfigMap metadata: name: app-config data: DB_PASSWORD: "SuperSecret123!" # Plaintext password! API_KEY: "sk-live-abc123xyz" # API key in ConfigMap! --- apiVersion: v1 kind: Pod metadata: name: app spec: containers: - name: app image: myapp:latest envFrom: - configMapRef: name: app-config # All values as env vars!
# Use External Secrets Operator with a vault backend apiVersion: external-secrets.io/v1beta1 kind: ExternalSecret metadata: name: app-secrets spec: refreshInterval: 1h secretStoreRef: name: vault-backend kind: ClusterSecretStore target: name: app-secrets data: - secretKey: db-password remoteRef: key: secret/myapp/db property: password --- apiVersion: v1 kind: Pod metadata: name: app spec: containers: - name: app image: myapp@sha256:abc123... volumeMounts: - name: secrets mountPath: /etc/secrets readOnly: true # Mount as read-only files volumes: - name: secrets secret: secretName: app-secrets
Sans politiques au niveau du cluster, il n'y a aucune barrière empêchant le déploiement de charges de travail non sécurisées. Les Pod Security Standards (PSS), les contrôleurs d'admission et les moteurs de politiques comme OPA Gatekeeper ou Kyverno sont essentiels pour appliquer des bases de sécurité dans tous les espaces de noms.
Sans application de politiques, tout développeur peut déployer des conteneurs privilégiés, utiliser des volumes hostPath ou désactiver les contrôles de sécurité. Une seule charge de travail mal configurée peut compromettre l'ensemble du cluster. La révision manuelle ne peut pas évoluer pour détecter toutes les violations.
#!/bin/bash # No admission control Eany pod spec is accepted # No Pod Security Standards configured # Anyone can deploy a privileged container kubectl run hacker-pod --image=alpine \ --overrides='{"spec":{"containers":[{ "name":"hack", "image":"alpine", "securityContext":{"privileged":true}, "command":["nsenter","--target","1","--mount","--uts","--ipc","--net","--pid","--","bash"] }]}}' # No policy blocks this Enow has host root access!
# Enforce Pod Security Standards with Kyverno apiVersion: kyverno.io/v1 kind: ClusterPolicy metadata: name: disallow-privileged spec: validationFailureAction: Enforce background: true rules: - name: deny-privileged match: any: - resources: kinds: ["Pod"] validate: message: "Privileged containers are not allowed." pattern: spec: containers: - securityContext: privileged: "false" - name: require-run-as-non-root match: any: - resources: kinds: ["Pod"] validate: message: "Containers must run as non-root." pattern: spec: securityContext: runAsNonRoot: true
Par défaut, tous les pods d'un cluster Kubernetes peuvent communiquer entre eux sans aucune restriction. Cette architecture réseau plate signifie qu'un pod compromis peut atteindre n'importe quel autre pod, service, ou même le serveur API Kubernetes. Les NetworkPolicies sont essentielles pour mettre en œuvre la micro-segmentation.
Sans segmentation réseau, les déplacements latéraux sont triviaux. Un pod frontend compromis peut accéder directement aux bases de données backend, aux services internes et à l'API de métadonnées (169.254.169.254). Cela viole le principe du moindre privilège au niveau de la couche réseau.
# No NetworkPolicy Eall pods can communicate freely apiVersion: v1 kind: Namespace metadata: name: production # No default deny policy # No network policies at all # Any pod can reach: # - Other pods in any namespace # - Kubernetes API server # - Cloud metadata API (169.254.169.254) # - External internet
# Default deny all ingress and egress apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: default-deny-all namespace: production spec: podSelector: {} policyTypes: ["Ingress", "Egress"] --- # Allow only frontend to backend communication apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-frontend-to-backend namespace: production spec: podSelector: matchLabels: app: backend policyTypes: ["Ingress"] ingress: - from: - podSelector: matchLabels: app: frontend ports: - port: 8080 protocol: TCP
Les composants du plan de contrôle Kubernetes (serveur API, etcd, kubelet, tableau de bord) et les services applicatifs peuvent être exposés par inadvertance à Internet ou à des réseaux non fiables. Les tableaux de bord exposés, les kubelets non authentifiés et les serveurs API accessibles publiquement sont des vecteurs d'attaque courants.
Les composants Kubernetes exposés fournissent un accès direct à la gestion du cluster. Une API kubelet non authentifiée permet l'exécution de conteneurs. Un tableau de bord exposé avec des identifiants par défaut accorde un accès cluster-admin. L'accès public à etcd expose toutes les données du cluster, y compris les secrets.
# Kubernetes Dashboard exposed via LoadBalancer apiVersion: v1 kind: Service metadata: name: kubernetes-dashboard namespace: kubernetes-dashboard spec: type: LoadBalancer # Public internet access! ports: - port: 443 targetPort: 8443 selector: k8s-app: kubernetes-dashboard # Dashboard bound to cluster-admin service account # No authentication required Efull cluster access!
# Dashboard accessible only via kubectl proxy apiVersion: v1 kind: Service metadata: name: kubernetes-dashboard namespace: kubernetes-dashboard spec: type: ClusterIP # Internal only ports: - port: 443 targetPort: 8443 selector: k8s-app: kubernetes-dashboard --- # Access via: kubectl proxy, then browse to: # http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/ # Or use kubectl port-forward for secure access
Les clusters Kubernetes sont constitués de nombreux composants : serveur API, etcd, kubelet, kube-proxy, CoreDNS et modules complémentaires tiers (contrôleurs Ingress, maillages de services, surveillance). Les composants mal configurés ou non corrigés introduisent des vulnérabilités. Les configurations par défaut ne sont souvent pas renforcées.
Les composants de cluster vulnérables peuvent être exploités pour l'exécution de code à distance, l'escalade de privilèges ou le déni de service. Les CVE dans kubelet, les contrôleurs Ingress ou les plugins CNI peuvent conduire à des échappements de conteneurs et à la prise de contrôle du cluster. Les composants obsolètes accumulent des vulnérabilités connues.
# kubelet with insecure configuration # /var/lib/kubelet/config.yaml apiVersion: kubelet.config.k8s.io/v1beta1 kind: KubeletConfiguration authentication: anonymous: enabled: true # Anyone can access kubelet API! webhook: enabled: false # No authorization! authorization: mode: AlwaysAllow # All requests permitted! readOnlyPort: 10255 # Unauthenticated read port open!
# Hardened kubelet configuration apiVersion: kubelet.config.k8s.io/v1beta1 kind: KubeletConfiguration authentication: anonymous: enabled: false # Deny anonymous access webhook: enabled: true # Use API server for auth x509: clientCAFile: /etc/kubernetes/pki/ca.crt authorization: mode: Webhook # API server authorizes requests readOnlyPort: 0 # Disable read-only port protectKernelDefaults: true eventRecordQPS: 5
Les clusters Kubernetes exécutés dans des environnements cloud (AWS, GCP, Azure) peuvent accéder aux API de métadonnées cloud et aux rôles IAM attachés aux nœuds. Les attaquants qui compromettent un pod peuvent les utiliser pour escalader les privilèges du cluster vers le compte cloud, accédant aux buckets S3, aux bases de données et à d'autres services cloud.
Les rôles IAM au niveau des nœuds sont hérités par tous les pods sur ce nœud. Un pod compromis peut interroger l'API de métadonnées (169.254.169.254) pour obtenir des identifiants cloud, puis accéder à toute ressource cloud autorisée par le rôle du nœud. Cela permet un mouvement latéral de Kubernetes vers l'environnement cloud plus large.
# Node IAM role with excessive permissions resource "aws_iam_role_policy_attachment" "node_admin" { role = aws_iam_role.node_role.name policy_arn = "arn:aws:iam::aws:policy/AdministratorAccess" # All pods on this node inherit admin access! } # No IMDS restrictions Eany pod can get node credentials # curl http://169.254.169.254/latest/meta-data/iam/security-credentials/
# Use IAM Roles for Service Accounts (IRSA) resource "aws_iam_role" "app_role" { name = "my-app-role" assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [{ Effect = "Allow" Principal = { Federated = aws_iam_openid_connect_provider.eks.arn } Action = "sts:AssumeRoleWithWebIdentity" Condition = { StringEquals = { "${aws_iam_openid_connect_provider.eks.url}:sub" = "system:serviceaccount:my-ns:my-app" } } }] }) } # Pod-level scoped IAM via service account annotation # Only this specific pod gets these specific permissions
Kubernetes prend en charge plusieurs mécanismes d'authentification : certificats, jetons, OIDC et authentification par webhook. L'authentification défaillante inclut l'utilisation de jetons statiques, le partage de fichiers kubeconfig, la non-rotation des certificats et l'échec d'intégration avec les fournisseurs d'identité. Le montage automatique de jetons de compte de service crée une surface d'attaque inutile.
Les fichiers kubeconfig et les jetons de compte de service volés ou divulgués fournissent un accès direct au cluster. Les jetons statiques qui n'expirent jamais donnent un accès persistant. Sans gestion centralisée des identités, révoquer l'accès des membres d'équipe partis est difficile et sujet aux erreurs.
# Static token file for API server authentication # /etc/kubernetes/token-auth-file.csv # token,user,uid,"groups" # abc123,admin,1,"system:masters" # Static token, never expires! # Pod with auto-mounted service account token apiVersion: v1 kind: Pod metadata: name: app spec: # automountServiceAccountToken defaults to true! # Token mounted at /var/run/secrets/kubernetes.io/serviceaccount/token containers: - name: app image: myapp:latest # App doesn't need K8s API access but has a token anyway
# Disable auto-mounted token for pods that don't need API access apiVersion: v1 kind: ServiceAccount metadata: name: my-app namespace: my-app-ns automountServiceAccountToken: false # No auto-mount --- # For pods that need API access, use bound tokens with expiry apiVersion: v1 kind: Pod metadata: name: api-consumer spec: serviceAccountName: api-consumer-sa automountServiceAccountToken: false containers: - name: app image: myapp@sha256:abc123... volumeMounts: - name: token mountPath: /var/run/secrets/tokens readOnly: true volumes: - name: token projected: sources: - serviceAccountToken: expirationSeconds: 3600 # 1-hour expiry audience: api-server
Kubernetes génère des événements d'audit pour les requêtes du serveur API, mais la journalisation d'audit est souvent désactivée ou mal configurée. Sans journalisation et surveillance appropriées, les activités malveillantes Etelles que l'accès non autorisé aux secrets, les modifications RBAC et les échappements de conteneurs Epassent inaperçues. La surveillance de sécurité en temps d'exécution au niveau du conteneur et du nœud est essentielle.
Sans journalisation d'audit, les attaquants opèrent sans être détectés. Ils peuvent créer des comptes de service de porte dérobée, extraire des secrets et modifier des charges de travail sans aucune trace. La réponse aux incidents est sévèrement entravée lorsqu'il n'y a aucune piste d'audit pour déterminer ce qui s'est passé, quand et par qui.
# API server with no audit logging configured # kube-apiserver flags: # --audit-log-path="" # No audit log! # --audit-policy-file="" # No audit policy! # No runtime security monitoring # No alerting on suspicious activities # Container logs not collected centrally # Default log retention Elogs lost on pod restart
# Comprehensive audit policy apiVersion: audit.k8s.io/v1 kind: Policy rules: # Log all secret access at Metadata level - level: Metadata resources: - group: "" resources: ["secrets"] # Log RBAC changes at RequestResponse level - level: RequestResponse resources: - group: rbac.authorization.k8s.io resources: ["clusterroles", "clusterrolebindings", "roles", "rolebindings"] # Log pod exec/attach (potential attacks) - level: Request resources: - group: "" resources: ["pods/exec", "pods/attach", "pods/portforward"] # Default: log at Metadata level - level: Metadata
| ID | Vulnérabilité | Sévérité | Atténuation Clé |
|---|---|---|---|
| K01 | Configurations de Charges de Travail Non Sécurisées | Critical | Non-root, FS lecture seule, suppression des capacités, limites de ressources |
| K02 | Autorisation Trop Permissive | Critical | RBAC à portée d'espace de noms, pas de génériques, audit des liaisons |
| K03 | Échecs de Gestion des Secrets | Critical | Secrets externes, chiffrement au repos, montages de fichiers |
| K04 | Manque d'Application de Politiques | High | Pod Security Standards, Kyverno/OPA Gatekeeper |
| K05 | Segmentation Réseau Manquante | High | NetworkPolicies par défaut refusant tout, micro-segmentation |
| K06 | Composants Trop Exposés | High | Services ClusterIP, kubectl proxy, règles de pare-feu |
| K07 | Composants de Cluster Mal Configurés | High | CIS Benchmark, désactiver l'authentification anonyme, gestion des correctifs |
| K08 | Mouvement Latéral du Cluster vers le Cloud | Critical | IRSA/Workload Identity, bloquer l'API de métadonnées, IMDSv2 |
| K09 | Mécanismes d'Authentification Défaillants | Critical | Intégration OIDC, jetons liés, désactiver le montage automatique |
| K10 | Journalisation et Surveillance Inadéquates | Medium | Journalisation d'audit, Falco/Tetragon, intégration SIEM |