Expert Cybersécurité & IAv9.0
Centres de ressources conformité
Besoin d'un accompagnement expert ?
Devis personnalisé sous 24h — audit, conformité, incident
Checklists Sécurité — Audit & Durcissement
Formats disponibles
📄 PDF 📊 Excel 🌐 Web

11 checklists professionnelles couvrant 2 200+ points de contrôle. Téléchargement gratuit, aucune inscription.

Aller au contenu Aller au quiz
Lu

Pod Security et isolation

En route — chaque ligne compte.

~30 min
Programme complet

Pod Security et isolation

Comment un pod Kubernetes isole réellement ses processus du nœud hôte, et comment verrouiller cette isolation avec les security contexts, les capabilities Linux, seccomp et Pod Security Admission.

Ch. 4/10 Intermédiaire
Table des matières

    Pourquoi l'isolation d'un pod ne va pas de soi

    Un conteneur n'est pas une machine virtuelle. Une VM embarque son propre noyau, isolé par un hyperviseur au niveau matériel. Un conteneur, lui, n'est qu'un processus Linux ordinaire auquel on a appliqué des restrictions de vue et de privilèges — il partage le même noyau que tous les autres conteneurs du nœud, et que le nœud lui-même. Cette différence structurelle a une conséquence directe : la frontière de sécurité d'un pod n'est pas garantie par construction, elle dépend entièrement de la configuration que vous lui appliquez.

    Si un attaquant obtient l'exécution de code dans un conteneur mal configuré — root, toutes les capabilities, pas de filtrage syscall — il n'a pas seulement compromis une application. Il dispose potentiellement d'un accès proche de celui d'un processus root sur le nœud hôte, avec toutes les portes de sortie que cela implique : montage de volumes hôte, lecture de secrets d'autres pods via /proc, voire évasion complète du conteneur selon les vulnérabilités du runtime.

    Le securityContext n'est pas une option de durcissement facultative réservée aux environnements sensibles. C'est la seule couche qui transforme un processus Linux nu en conteneur réellement isolé. Un pod sans securityContext explicite hérite des valeurs par défaut du runtime, qui sont historiquement permissives.

    Ce chapitre part du noyau et remonte jusqu'à la politique de cluster : namespaces Linux, security context, capabilities, seccomp, puis Pod Security Admission comme mécanisme d'application à grande échelle.

    Les namespaces Linux : la base de l'isolation

    Kubernetes ne réinvente pas l'isolation, il orchestre des primitives du noyau Linux. Chaque conteneur — et par extension chaque pod — s'exécute dans un ensemble de namespaces, chacun limitant la visibilité d'une ressource système précise :

    • PID — le conteneur ne voit que son propre arbre de processus, pas ceux du nœud ni des autres pods.
    • NET — pile réseau dédiée (interfaces, routes, portsportRéseauxNuméro sur 16 bits qui désigne l'application destinataire sur une machine. Les ports 0 à 1023 sont réservés aux services système, comme 443 pour HTTPS.Voir dans le glossaire). Dans un pod multi-conteneurs, ce namespace est partagé entre tous les conteneurs du pod.
    • MNT — vue du système de fichiers propre au conteneur.
    • UTS — hostname et nom de domaine indépendants.
    • IPC — segments de mémoire partagée et files de messages isolés.
    • USER — fait correspondre l'UID 0 (root) vu à l'intérieur du conteneur à un UID non privilégié côté hôte. Disponible depuis Kubernetes 1.25 en alpha puis stabilisé progressivement ; à vérifier sur votre distribution avant d'en dépendre en production.
    • CGROUP — limite la visibilité de la hiérarchie de cgroups, complément aux quotas CPU/mémoire eux-mêmes gérés par les cgroups.

    Le partage du namespace réseau entre conteneurs d'un même pod signifie qu'ils se partagent aussi l'espace des ports. Deux conteneurs du même pod ne peuvent pas écouter sur le même port — un détail qui surprend souvent lors de la conception de patterns sidecar.

    Nœud — noyau Linux unique Pod PID namespace NET namespace (partagé pod) MNT namespace UTS namespace IPC namespace Conteneur A runAsUser: 10001, capabilities réduites Conteneur B runAsUser: 10001, capabilities réduites USER namespace : UID 0 conteneur ↦ UID non privilégié hôte CGROUP namespace : hiérarchie de contrôle limitée au pod Toutes les ressources ci-dessus restent adossées au même noyau que le nœud
    Un pod encapsule ses conteneurs dans des namespaces Linux distincts, mais tous restent des processus du même noyau hôte.

    Ces namespaces isolent la vue, pas les privilèges. Un processus root dans son PID namespace reste root au sens des capabilities Linux, sauf si un USER namespace remappe explicitement son UID. C'est précisément l'angle mort que les couches suivantes viennent combler.

    Security Context : verrouiller le comportement du conteneur

    Le securityContext se déclare à deux niveaux dans un manifeste de pod : au niveau pod.spec.securityContext (valeurs par défaut pour tous les conteneurs) et au niveau container.securityContext (surcharge par conteneur, prioritaire).

    Les champs qui comptent réellement en production :

    Champ Effet Recommandation
    runAsNonRoot Le kubelet refuse de démarrer le conteneur si l'utilisateur effectif est root true systématiquement
    runAsUser / runAsGroup Fixe l'UID/GID d'exécution UID fixe non nul, ex. 10001
    allowPrivilegeEscalation Empêche setuid/setgid d'élever les privilèges pendant l'exécution false
    readOnlyRootFilesystem Rootfs monté en lecture seule true + emptyDir pour les chemins réellement inscriptibles
    privileged Désactive quasiment toute isolation (accès aux devices du nœud) false, sans exception hors cas d'infrastructure justifiés
    fsGroup GID appliqué aux volumes montés à définir si l'image tourne avec un UID non root et doit écrire sur un volume

    runAsNonRoot: true seul ne fixe pas d'UID précis — il vérifie seulement que l'UID final n'est pas 0. Combinez-le toujours avec runAsUser pour un comportement déterministe, reproductible entre environnements.

    Un piège classique : runAsNonRoot: true sur une image dont le Dockerfile ne définit aucun utilisateur non root fait échouer le démarrage du pod (CreateContainerConfigError). C'est le comportement voulu — mieux vaut un pod qui ne démarre pas qu'un pod qui tourne en root — mais cela signifie que la sécurité du security context est plafonnée par ce que l'image autorise. Un runAsUser: 10001 imposé sur une image qui écrit en dur dans /var/lib/app appartenant à root cassera l'application au runtime, pas à l'admission.

    spec:
      securityContext:
        runAsNonRoot: true
        runAsUser: 10001
        runAsGroup: 10001
        fsGroup: 10001
      containers:
        - name: api
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities:
              drop: ["ALL"]
              add: ["NET_BIND_SERVICE"]
          volumeMounts:
            - name: tmp
              mountPath: /tmp
      volumes:
        - name: tmp
          emptyDir: {}
    

    Capabilities Linux : réduire les privilèges au strict nécessaire

    Avant les capabilities, Linux ne connaissait que deux états : root (tout-puissant) ou utilisateur standard. Les capabilities découpent les privilèges root en une quarantaine de droits granulaires — CHOWN, NET_BIND_SERVICE, SYS_ADMIN, SYS_PTRACE, NET_RAW, etc. Un conteneur peut ainsi disposer d'exactement le privilège dont il a besoin, sans hériter du reste.

    Par défaut, les runtimes de conteneurs (containerd, CRI-O) accordent un jeu d'une douzaine de capabilities jugées à faible risque — largement suffisant pour la plupart des workloads applicatifs, mais toujours supérieur au strict nécessaire pour un service HTTP qui ne fait qu'écouter un port et écrire des logs.

    La pratique de durcissement standard consiste à tout retirer puis ne rajouter que ce qui est prouvé nécessaire :

    securityContext:
      capabilities:
        drop: ["ALL"]
        add: ["NET_BIND_SERVICE"]
    

    NET_BIND_SERVICE est l'exemple canonique : sans elle, seul root peut ouvrir un port inférieur à 1024. Plutôt que de faire tourner tout le conteneur en root pour ce seul besoin, on accorde uniquement cette capability à un utilisateur non privilégié.

    Identifier les capabilities réellement utilisées par une application legacy sans documentation demande souvent un audit dynamique — strace en environnement de test, ou l'observation des échecs EPERM après un drop: ["ALL"] en pré-production. Ne devinez pas, mesurez.

    seccomp : filtrer les appels système

    Les capabilities régulent quels privilèges un processus possède ; seccomp (secure computing mode) régule quels appels système il a le droit d'invoquer, indépendamment de ses privilèges. C'est une couche de filtrage au niveau du noyau, basée sur BPF, qui bloque des syscalls jugés dangereux ou inutiles pour un conteneur applicatif — ptrace, mount, reboot, kexec_load, add_key, entre autres.

    Kubernetes expose seccomp via securityContext.seccompProfile :

    • RuntimeDefault — applique le profil par défaut du runtime de conteneurs, qui bloque environ 300 syscalls réputés dangereux tout en préservant la compatibilité applicative large. C'est le point de départ recommandé pour tout workload.
    • Localhost — charge un profil JSON personnalisé stocké sur le nœud, pour les cas où RuntimeDefault est trop permissif ou trop restrictif.
    • Unconfined — aucun filtrage. À réserver aux workloads d'infrastructure qui en ont un besoin documenté (agentsagentIASystème qui enchaîne des appels d'outils de façon autonome pour atteindre un objectif : il planifie, agit, observe, recommence. Sa fiabilité décroît exponentiellement avec le nombre d'étapes.Voir dans le glossaire de monitoring bas niveau, par exemple).
    securityContext:
      seccompProfile:
        type: RuntimeDefault
    

    Ne passez à un profil Localhost sur mesure qu'après avoir constaté un besoin réel — un appel système légitime bloqué par RuntimeDefault — plutôt que par anticipation. Construire un profil trop restrictif à l'aveugle casse des comportements applicatifs difficiles à diagnostiquer, souvent des semaines après le déploiement.

    Pod Security Standards et Pod Security Admission

    Configurer un securityContext correct pod par pod ne suffit pas à sécuriser un cluster : rien n'empêche une équipe d'oublier, ou un manifeste tiers d'ignorer ces bonnes pratiques. C'est le rôle des Pod Security Standards (PSS), trois profils normalisés par la communauté Kubernetes :

    Niveau Portée Usage typique
    Privileged Aucune restriction Composants d'infrastructure (CNI, CSI, monitoring bas niveau)
    Baseline Bloque les élévations de privilèges connues (privileged: true, capabilities dangereuses, hostNetwork, hostPID) Workloads legacy en transition
    Restricted Applique les meilleures pratiques de durcissement (runAsNonRoot, capabilities drop: ALL, seccomp obligatoire, allowPrivilegeEscalation: false) Workloads applicatifs standards

    Ces profils sont appliqués par le contrôleur d'admission Pod Security Admission (PSA), intégré nativement à l'API server depuis Kubernetes 1.25 (GA), en remplacement de PodSecurityPolicy (PSP), retiré à cette même version. PSA s'active par des labels sur le namespace :

    apiVersion: v1
    kind: Namespace
    metadata:
      name: production
      labels:
        pod-security.kubernetes.io/enforce: restricted
        pod-security.kubernetes.io/enforce-version: latest
        pod-security.kubernetes.io/audit: restricted
        pod-security.kubernetes.io/warn: restricted
    

    Le mode enforce rejette à l'admission tout pod non conforme ; audit journalise sans bloquer ; warn affiche un avertissement côté client kubectl. Combiner les trois permet une migration progressive : on active warn et audit en restricted avant de faire évoluer enforce, ce qui donne de la visibilité sur l'impact sans casser les déploiements existants.

    La suppression de PodSecurityPolicy en 1.25 a laissé certains clusters migrés à la hâte sans équivalent PSA effectif — le namespace existe, mais sans labels d'enforcement, aucune politique n'est réellement appliquée. Vérifiez systématiquement les labels PSA après une migration depuis PSP, l'absence de politique est silencieuse et ne génère aucune alerte par défaut.

    Un point souvent mal compris : PSA évalue les pods au moment de l'admission. Changer le label enforce d'un namespace ne remet pas en conformité les pods déjà en cours d'exécution — seuls les nouveaux pods, ou ceux recréés (rolling update, suppression manuelle), sont contrôlés contre la nouvelle politique.

    Checklist de durcissement

    • Définir runAsNonRoot: true et un runAsUser explicite sur chaque pod.
    • Positionner allowPrivilegeEscalation: false sur chaque conteneur.
    • Passer readOnlyRootFilesystem: true, en complétant par des emptyDir pour les répertoires réellement inscriptibles.
    • Retirer toutes les capabilities par défaut (drop: ["ALL"]) et n'en rajouter que sur preuve de besoin.
    • Activer seccompProfile.type: RuntimeDefault comme plancher minimal.
    • Bannir privileged: true, hostNetwork, hostPID, hostIPC sauf justification d'infrastructure documentée.
    • Appliquer le label pod-security.kubernetes.io/enforce: restricted sur les namespaces applicatifs.
    • Auditer les pods existants après tout changement de politique PSA — l'enforcement n'est pas rétroactif.

    Pièges fréquents

    Le premier piège est l'oubli du fsGroup : un conteneur qui tourne avec un UID non root mais tente d'écrire sur un volume monté avec des permissions root échoue avec une erreur de permission difficile à relier au security context si on ne connaît pas ce mécanisme.

    Le deuxième est de considérer runAsNonRoot comme suffisant en soi, sans vérifier que l'image le supporte réellement — certaines images publiques ne définissent aucun utilisateur non root et nécessitent un Dockerfile personnalisé pour être conformes.

    Le troisième, plus insidieux, concerne les clusters multi-tenants où un namespace Baseline côtoie un namespace Restricted : l'absence d'uniformité crée une fausse impression de sécurité globale alors que seule une partie du cluster est réellement durcie. La politique PSA doit être pensée au niveau du cluster, pas namespace par namespace de façon ad hoc.

    L'essentiel à retenir

    Un conteneur n'est pas une machine virtuelle : il partage le noyau du nœud hôte, et son isolation repose entièrement sur des mécanismes Linux configurables — namespaces, capabilities, seccomp — que Kubernetes expose via le securityContext. Ce chapitre détaille ces mécanismes, montre comment les combiner pour appliquer le principe du moindre privilège, et explique le rôle de Pod Security Admission dans l'application de politiques cohérentes à l'échelle du cluster. Il se termine par une checklist de durcissement et les pièges les plus fréquents constatés en production.

    Questions fréquentes

    Un pod avec `runAsNonRoot: true` est-il automatiquement sécurisé ?
    Non. C'est une condition nécessaire mais pas suffisante. Elle garantit seulement que le processus ne s'exécute pas en tant que root, sans fixer d'UID précis ni retirer les capabilities Linux, ni filtrer les appels système. Un durcissement réel combine `runAsNonRoot`, `runAsUser` explicite, `capabilities.drop: [ALL]`, `allowPrivilegeEscalation: false` et un profil seccomp.
    Pourquoi mon pod refuse-t-il de démarrer avec `runAsNonRoot: true` alors qu'il fonctionnait avant ?
    L'image de base ne définit probablement aucun utilisateur non root dans son Dockerfile, ce qui fait que son UID effectif reste 0. Le kubelet bloque alors le démarrage plutôt que de laisser tourner un conteneur root malgré la contrainte demandée. La solution consiste à créer un utilisateur dédié dans l'image (instruction `USER`) ou à imposer `runAsUser` explicitement dans le manifeste.
    Quelle est la différence entre les capabilities Linux et seccomp ?
    Les capabilities régulent quels privilèges un processus possède (modifier le réseau, monter un système de fichiers, changer un propriétaire de fichier). seccomp régule, indépendamment des privilèges, quels appels système le processus a le droit d'invoquer. Les deux mécanismes sont complémentaires : on peut avoir la capability sans le syscall autorisé par seccomp, et inversement.
    Faut-il appliquer le niveau Restricted à tous les namespaces d'un cluster ?
    C'est l'objectif à viser pour les namespaces applicatifs, mais certains composants d'infrastructure (CNI, CSI, agents de monitoring bas niveau) ont un besoin légitime de privilèges élevés et relèvent du niveau Privileged. L'important est que ce choix soit explicite et documenté par namespace, pas hérité par défaut faute de configuration.
    Pod Security Admission bloque-t-il aussi les pods au moment du build de l'image ?
    Non, PSA agit uniquement au moment de l'admission du pod par l'API server Kubernetes, pas pendant la construction de l'image. Le contrôle de la sécurité des images elles-mêmes (vulnérabilités, base image, secrets embarqués) relève d'outils de scan d'image distincts, en amont dans la chaîne CI/CD.
    Pourquoi `readOnlyRootFilesystem: true` casse-t-il parfois une application qui fonctionnait sans cette option ?
    De nombreuses applications écrivent des fichiers temporaires, des caches ou des logs directement dans le système de fichiers du conteneur. Avec un rootfs en lecture seule, ces écritures échouent. Il faut identifier ces chemins et les monter explicitement en `emptyDir` ou volume dédié, inscriptible, tout en gardant le reste du système de fichiers protégé.
    Le namespace USER de Kubernetes remplace-t-il le besoin de `runAsNonRoot` ?
    Non, les deux sont complémentaires. Le namespace USER remappe l'UID 0 vu à l'intérieur du conteneur vers un UID non privilégié côté hôte, ce qui limite l'impact d'une évasion de conteneur même si le processus se croit root. `runAsNonRoot` agit au niveau applicatif, en empêchant le processus lui-même de s'exécuter avec l'UID 0 dans son propre référentiel. Un durcissement complet utilise les deux quand la maturité du support USER namespace sur le cluster le permet.
    Comment savoir si les labels Pod Security Admission d'un namespace sont réellement appliqués ?
    Il faut inspecter directement les labels du namespace avec `kubectl get namespace <nom> --show-labels` et vérifier la présence de `pod-security.kubernetes.io/enforce` avec un niveau explicite. L'absence de ce label ne génère aucune erreur ni alerte : le namespace se comporte alors comme si aucune politique PSA n'était active, un état silencieux fréquent après une migration depuis PodSecurityPolicy.

    Progression sauvegardée dans votre navigateur.

    Quiz de validation

    Quiz de validation

    Quiz indisponible (données invalides).

    De la formation à l'action Nos experts peuvent auditer, tester ou certifier votre organisation.
    Devis gratuit
    Ch. 4/10 Pod Security et isolation 40% ~30 min Mode lecture v2.7.9