Contactez-nous

Contexte de sécurité (Security Context) : Définir les privilèges au niveau Pod/conteneur

Maîtrisez le Security Context dans Kubernetes pour définir précisément les privilèges (UID, GID, capabilities...) de vos Pods et conteneurs et renforcer leur sécurité.

Introduction au Contexte de Sécurité : Contrôle granulaire à l'exécution

Le Contexte de Sécurité (Security Context) est un champ fondamental dans la spécification des Pods et des conteneurs Kubernetes. Il permet de définir un ensemble de conditions et de privilèges sous lesquels un Pod ou un conteneur spécifique doit s'exécuter. En manipulant ces paramètres, vous pouvez restreindre de manière significative ce qu'un processus conteneurisé est autorisé à faire sur le noeud hôte, appliquant ainsi le principe de moindre privilège directement au niveau de la charge de travail.

Plutôt que de laisser les conteneurs s'exécuter avec les permissions par défaut (qui peuvent être trop élevées, notamment s'ils s'exécutent en tant que root), le Security Context vous donne un contrôle fin sur des aspects critiques tels que l'identité de l'utilisateur (UID/GID), les capacités Linux, l'accès au système de fichiers racine, l'escalade de privilèges, et l'intégration avec des mécanismes de sécurité du noyau comme SELinux ou Seccomp.

Utiliser correctement les Security Contexts est une étape essentielle pour réduire la surface d'attaque de vos applications et limiter les dégâts potentiels en cas de compromission d'un conteneur.

Portée : Contexte de Sécurité au niveau du Pod vs Conteneur

Le Security Context peut être défini à deux niveaux dans le manifeste d'un Pod :

  • Au niveau du Pod (spec.securityContext) : Les paramètres définis ici s'appliquent par défaut à tous les conteneurs du Pod. Ils définissent également des propriétés spécifiques au Pod lui-même, comme l'appartenance à des groupes supplémentaires (supplementalGroups) ou la configuration de fsGroup pour les volumes partagés.
  • Au niveau du Conteneur (spec.containers[*].securityContext) : Les paramètres définis ici s'appliquent uniquement au conteneur spécifique dans lequel ils sont déclarés. Ces paramètres prévalent et peuvent écraser ceux définis au niveau du Pod pour ce conteneur particulier. Si un paramètre est défini aux deux niveaux, c'est celui du conteneur qui sera utilisé.

Cette hiérarchie permet de définir des politiques de sécurité générales au niveau du Pod tout en autorisant des ajustements spécifiques pour certains conteneurs qui pourraient avoir des besoins légèrement différents (bien qu'il soit toujours préférable de maintenir la cohérence et le moindre privilège autant que possible).

Exemple illustrant les deux niveaux :

apiVersion: v1
kind: Pod
metadata:
  name: security-context-demo
spec:
  securityContext: # Au niveau Pod
    runAsUser: 1000
    runAsGroup: 3000
    fsGroup: 2000
  containers:
  - name: container-1
    image: busybox
    command: [ "sh", "-c", "sleep 1h" ]
    # Hérite de runAsUser: 1000 et runAsGroup: 3000 du Pod

  - name: container-2
    image: busybox
    command: [ "sh", "-c", "sleep 1h" ]
    securityContext: # Au niveau Conteneur (écrase)
      runAsUser: 2000 # S'exécute en tant qu'utilisateur 2000
      # runAsGroup n'est pas défini ici, donc hérite de 3000 du Pod
      allowPrivilegeEscalation: false # Paramètre spécifique au conteneur

Contrôle de l'identité : `runAsUser`, `runAsGroup`, `fsGroup`, `runAsNonRoot`

L'une des fonctionnalités les plus importantes du Security Context est la capacité de contrôler sous quel ID utilisateur (UID) et ID groupe (GID) les processus du conteneur s'exécutent.

  • runAsUser: : Spécifie l'UID sous lequel le point d'entrée (entrypoint) et les processus du conteneur doivent s'exécuter. Si non défini, il utilise l'UID spécifié dans l'image (souvent root, UID 0). Définir un UID non-root (> 0) est une pratique de sécurité fondamentale.
  • runAsGroup: : Spécifie le GID primaire sous lequel les processus du conteneur doivent s'exécuter. Si non défini, utilise le GID de l'image ou le GID associé à runAsUser si possible.
  • runAsNonRoot: true : C'est une validation au niveau du Kubelet. Si défini à true, le Kubelet vérifiera que le conteneur ne s'exécute pas en tant qu'UID 0. Si runAsUser est défini à 0 ou si l'image tente de démarrer en root sans runAsUser défini, le conteneur échouera au démarrage. C'est une sécurité supplémentaire pour imposer l'exécution non-root.
  • fsGroup: (Niveau Pod uniquement) : Spécifie un GID spécial qui sera propriétaire de certains types de volumes attachés au Pod (comme les volumes emptyDir ou les volumes bloc) et tous les fichiers créés dans ces volumes appartiendront à ce GID. Cela permet aux conteneurs du Pod (qui peuvent s'exécuter avec des GID différents) de partager et d'écrire sur ces volumes.
  • supplementalGroups: [, ] (Niveau Pod uniquement) : Ajoute des appartenances à des groupes supplémentaires pour tous les processus du premier conteneur du Pod. Utile pour accéder à des fichiers nécessitant une appartenance à un groupe spécifique.

Bonne pratique : Toujours essayer d'exécuter vos conteneurs en tant qu'utilisateur non-root (runAsUser > 0) et activez runAsNonRoot: true pour renforcer cette politique.

Contrôle des privilèges et du système de fichiers

Le Security Context permet également de contrôler des aspects liés aux privilèges élevés et à l'accès au système de fichiers.

  • privileged: (Niveau Conteneur uniquement) : Si défini à true, le conteneur obtient quasiment tous les privilèges de l'hôte, désactivant la plupart des mécanismes d'isolation (cgroups, namespaces partiels, capabilities étendues). C'est extrêmement dangereux et doit être évité à tout prix, sauf dans des cas très spécifiques et contrôlés (ex: certains agents système ou de débogage de bas niveau). Par défaut, c'est false.
  • allowPrivilegeEscalation: (Niveau Conteneur uniquement) : Contrôle si un processus à l'intérieur du conteneur peut obtenir plus de privilèges que son processus parent (via des mécanismes comme setuid ou setgid). Définir ce champ à false est une mesure de sécurité importante pour empêcher qu'un processus non-privilégié n'en exécute un autre avec des droits élevés. C'est fortement recommandé.
  • readOnlyRootFilesystem: (Niveau Conteneur uniquement) : Si défini à true, le système de fichiers racine (root filesystem) du conteneur est monté en lecture seule. C'est une excellente pratique de sécurité car cela empêche une application compromise d'écrire ou de modifier les binaires, librairies ou configurations de base du conteneur. L'application devra utiliser des volumes (comme emptyDir ou des volumes persistants) pour écrire des données temporaires ou persistantes.

Gestion fine des capacités Linux (`capabilities`)

Linux divise les privilèges traditionnellement associés à l'utilisateur root en unités distinctes appelées capacités (capabilities). Au lieu de donner tous les droits root à un processus, on peut lui accorder uniquement les capacités spécifiques dont il a besoin (ex: la capacité d'ouvrir des ports réseau inférieurs à 1024, ou de modifier l'heure système).

Le runtime de conteneur accorde un ensemble de capacités par défaut aux conteneurs. Le champ securityContext.capabilities permet de modifier cet ensemble par défaut :

  • capabilities.drop: [] : Liste les capacités à retirer de l'ensemble par défaut. Pour appliquer le principe de moindre privilège, la meilleure pratique est souvent de tout supprimer (drop: ["ALL"]) puis de rajouter uniquement le strict nécessaire.
  • capabilities.add: [] : Liste les capacités spécifiques à ajouter au conteneur, en plus de celles par défaut (ou après avoir tout supprimé avec `drop: ["ALL"]`).

Exemple : Un conteneur qui a besoin d'écouter sur le port 80 (nécessite NET_BIND_SERVICE) mais rien d'autre :

# ... spec.containers[].securityContext ...
      capabilities:
        drop:
        - "ALL" # Supprime toutes les capacités par défaut
        add:
        - "NET_BIND_SERVICE" # Ajoute uniquement celle nécessaire
      allowPrivilegeEscalation: false
      runAsNonRoot: true
      runAsUser: 1001

La gestion fine des capacités est une technique puissante pour réduire significativement les privilèges des conteneurs.

Intégrations avancées : SELinux, Seccomp, Windows

Le Security Context permet également l'intégration avec d'autres mécanismes de sécurité du système d'exploitation :

  • seLinuxOptions : Permet de spécifier le niveau et le rôle SELinux sous lesquels le conteneur doit s'exécuter sur les noeuds où SELinux est activé. Nécessite une bonne compréhension de SELinux.
  • seccompProfile : Permet de définir le profil seccomp (secure computing mode) à appliquer au conteneur. Seccomp filtre les appels système (syscalls) qu'un processus est autorisé à effectuer, réduisant ainsi la surface d'attaque exposée au noyau. Kubernetes supporte des profils par défaut (comme `RuntimeDefault`, `Unconfined`) ou des profils personnalisés chargés sur le noeud. L'utilisation de `RuntimeDefault` est une bonne pratique de base.
  • windowsOptions (Niveau Pod et Conteneur) : Pour les conteneurs Windows, ce champ permet de spécifier des options spécifiques comme le `gmsaCredentialSpecName` pour utiliser des comptes de service managés de groupe (gMSA) ou `runAsUserName`.

Ces options avancées offrent des couches de sécurité supplémentaires mais demandent une configuration et une compréhension plus approfondies de ces technologies spécifiques.

Conclusion : Le Security Context comme fondation

Le Contexte de Sécurité est un outil fondamental et puissant dans l'arsenal de sécurité de Kubernetes. En définissant méticuleusement les paramètres runAsUser, runAsNonRoot, allowPrivilegeEscalation, readOnlyRootFilesystem et capabilities, vous pouvez considérablement renforcer la posture de sécurité de vos Pods et conteneurs.

Appliquer ces configurations de manière systématique, en visant toujours le moindre privilège, est une étape essentielle pour construire des applications résilientes aux failles de sécurité. Ces paramètres constituent également la base sur laquelle reposent les standards de sécurité de Pod définis par Pod Security Admission, que nous aborderons dans la section suivante.