Aller au contenu
Conteneurs & Orchestration medium

Security Context, Contrôler les permissions des Pods

35 min de lecture

logo kubernetes

Le Security Context définit les privilèges et restrictions d'exécution pour vos Pods et conteneurs. Il contrôle l'identité d'exécution, les capabilities Linux, l'accès au système de fichiers et les restrictions de privilèges. C'est votre outil principal pour appliquer le principe du moindre privilège au niveau des workloads Kubernetes.

Ce guide couvre les paramètres essentiels pour la CKAD et les bonnes pratiques de production.

  • Ce qu'est un Security Context et où il s'applique (Pod vs conteneur)
  • Contrôler l'identité d'exécution avec runAsUser, runAsGroup, runAsNonRoot, supplementalGroups
  • Gérer les capabilities Linux (add/drop)
  • Protéger le filesystem avec readOnlyRootFilesystem et fsGroup
  • Bloquer l'escalade de privilèges avec allowPrivilegeEscalation

Un Security Context est un ensemble de paramètres de sécurité qui définissent les privilèges et restrictions d'un Pod ou d'un conteneur :

Ce que ça contrôleExemples de paramètres
Identité d'exécutionrunAsUser, runAsGroup, runAsNonRoot, supplementalGroups
Capabilities Linuxcapabilities.add, capabilities.drop
Accès filesystemreadOnlyRootFilesystem, fsGroup
Escalade de privilègesallowPrivilegeEscalation, privileged
Profils de sécuritéseccompProfile, seLinuxOptions, procMount

Pod Security Context vs Container Security Context

Section intitulée « Pod Security Context vs Container Security Context »

Les deux niveaux ne portent pas les mêmes paramètres. fsGroup et supplementalGroups n'existent qu'au niveau Pod ; capabilities, readOnlyRootFilesystem, allowPrivilegeEscalation et privileged n'existent qu'au niveau conteneur. Seuls runAsUser, runAsGroup, runAsNonRoot et les profils de sécurité acceptent les deux emplacements. Le manifeste ci-dessous montre les deux blocs côte à côte, disposition la plus courante en production.

apiVersion: v1
kind: Pod
metadata:
name: security-demo
spec:
# ─── Security Context au niveau POD ───
securityContext:
runAsUser: 1000 # UID pour tous les conteneurs
runAsGroup: 3000 # GID pour tous les conteneurs
fsGroup: 2000 # GID pour les volumes montés
supplementalGroups: # Groupes secondaires
- 4000
- 5000
containers:
- name: app
image: nginx:1.28@sha256:146adea4768b83c607d0bdfa4188464e3da6e0a3ad4475db1d1d8f64f27c29cc
# ─── Security Context au niveau CONTENEUR ───
securityContext:
runAsNonRoot: true # Refuse démarrage si root
readOnlyRootFilesystem: true # Filesystem en lecture seule
allowPrivilegeEscalation: false # Bloque setuid/setgid
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE

Quand un paramètre est présent aux deux endroits, c'est toujours le plus proche du processus qui l'emporte. Le niveau Pod joue donc le rôle de valeur par défaut, que chaque conteneur peut redéfinir pour lui seul.

Paramètre défini au niveau...Comportement
Pod uniquementS'applique à tous les conteneurs
Conteneur uniquementS'applique à ce conteneur
Pod ET conteneurLa valeur du conteneur écrase celle du Pod

L'identité d'exécution répond à une seule question : sous quel UID et quel GID le processus tourne-t-il une fois le conteneur démarré ? Sans réglage, c'est l'utilisateur déclaré par l'instruction USER de l'image, et si l'image n'en déclare aucun, c'est root. Les paramètres qui suivent permettent d'imposer cette identité depuis le manifeste, sans avoir à reconstruire l'image.

Ces paramètres définissent l'UID et le GID sous lesquels le processus principal du conteneur s'exécute :

apiVersion: v1
kind: Pod
metadata:
name: user-demo
spec:
securityContext:
runAsUser: 1000 # UID 1000 (non-root)
runAsGroup: 3000 # GID 3000
containers:
- name: app
image: busybox:1.36@sha256:73aaf090f3d85aa34ee199857f03fa3a95c8ede2ffd4cc2cdb5b94e566b11662
command: ["sh", "-c", "id && sleep 3600"]

Vérification :

Fenêtre de terminal
kubectl apply -f user-demo.yaml
kubectl logs user-demo
# uid=1000 gid=3000 groups=3000

Ce paramètre demande à Kubernetes de refuser le démarrage si le conteneur s'exécuterait en root :

spec:
containers:
- name: app
image: nginx:1.28@sha256:146adea4768b83c607d0bdfa4188464e3da6e0a3ad4475db1d1d8f64f27c29cc
securityContext:
runAsNonRoot: true

Ce paramètre définit les groupes secondaires ajoutés à tous les processus des conteneurs du Pod :

apiVersion: v1
kind: Pod
metadata:
name: groups-demo
spec:
securityContext:
runAsUser: 1000
runAsGroup: 1000
supplementalGroups:
- 4000 # Groupe pour accéder à certains fichiers
- 5000 # Groupe pour accéder à d'autres ressources
containers:
- name: app
image: busybox:1.36@sha256:73aaf090f3d85aa34ee199857f03fa3a95c8ede2ffd4cc2cdb5b94e566b11662
command: ["sh", "-c", "id && sleep 3600"]

Vérification :

Fenêtre de terminal
kubectl logs groups-demo
# uid=1000 gid=1000 groups=1000,4000,5000
ParamètreRôle
runAsGroupGroupe principal du processus
fsGroupGroupe propriétaire des volumes montés
supplementalGroupsGroupes secondaires du processus

Ce manifeste réunit les trois paramètres précédents et y ajoute le blocage de l'escalade de privilèges. Il constitue le socle minimal attendu sur un workload de production.

apiVersion: v1
kind: Pod
metadata:
name: secure-user
spec:
securityContext:
runAsUser: 1000
runAsGroup: 1000
runAsNonRoot: true
supplementalGroups: [4000]
containers:
- name: app
image: nginx:1.28@sha256:146adea4768b83c607d0bdfa4188464e3da6e0a3ad4475db1d1d8f64f27c29cc
securityContext:
allowPrivilegeEscalation: false

Un conteneur dispose par défaut d'un système de fichiers racine inscriptible. Il est effacé à chaque redémarrage, mais bien réel entre-temps : un attaquant peut y déposer un binaire ou modifier une configuration. Les deux paramètres suivants traitent des problèmes distincts, readOnlyRootFilesystem verrouillant cette racine tandis que fsGroup règle l'accès aux volumes montés, qui restent, eux, inscriptibles.

Ce paramètre rend le système de fichiers racine du conteneur en lecture seule :

apiVersion: v1
kind: Pod
metadata:
name: readonly-demo
spec:
containers:
- name: app
image: nginx:1.28@sha256:146adea4768b83c607d0bdfa4188464e3da6e0a3ad4475db1d1d8f64f27c29cc
securityContext:
readOnlyRootFilesystem: true
volumeMounts:
- name: tmp
mountPath: /tmp
- name: cache
mountPath: /var/cache/nginx
- name: run
mountPath: /var/run
volumes:
- name: tmp
emptyDir: {}
- name: cache
emptyDir: {}
- name: run
emptyDir: {}

Le paramètre fsGroup définit le GID propriétaire des volumes montés :

apiVersion: v1
kind: Pod
metadata:
name: fsgroup-demo
spec:
securityContext:
runAsUser: 1000
runAsGroup: 1000
fsGroup: 2000 # Les fichiers des volumes appartiennent au groupe 2000
containers:
- name: app
image: busybox:1.36@sha256:73aaf090f3d85aa34ee199857f03fa3a95c8ede2ffd4cc2cdb5b94e566b11662
command: ["sh", "-c", "ls -la /data && sleep 3600"]
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
emptyDir: {}

Vérification :

Fenêtre de terminal
kubectl logs fsgroup-demo
# drwxrwsrwx 2 root 2000 4096 Mar 22 10:00 .

Le dossier /data appartient au groupe 2000, et le bit setgid (s) est activé.

Les paramètres précédents désignaient qui exécute le processus. Ceux-ci définissent ce qu'il a le droit de faire une fois lancé. Trois leviers se combinent : le blocage de l'escalade de privilèges, la granularité des capabilities Linux, et le mode privileged qui annule à peu près tout le reste.

Ce paramètre contrôle si un processus peut obtenir plus de privilèges que son parent (via setuid/setgid binaries) :

spec:
containers:
- name: app
image: nginx:1.28@sha256:146adea4768b83c607d0bdfa4188464e3da6e0a3ad4475db1d1d8f64f27c29cc
securityContext:
allowPrivilegeEscalation: false

Les capabilities sont des permissions granulaires qui remplacent le modèle binaire "root ou pas root". Au lieu de donner tous les droits root, vous accordez uniquement les capabilities nécessaires.

Linux découpe les pouvoirs historiques de root en une quarantaine de capabilities indépendantes. Voici celles que vous croiserez le plus souvent dans un manifeste ; les deux dernières du tableau sont à traiter comme des signaux d'alerte lors d'une revue, car elles permettent de contourner les permissions de fichiers.

CapabilityCe qu'elle permet
NET_BIND_SERVICEÉcouter sur un port < 1024
NET_RAWCréer des raw sockets (ping, tcpdump)
SYS_PTRACETracer des processus (debug)
CHOWNChanger le propriétaire des fichiers
SETUID / SETGIDChanger d'identité (escalade potentielle)
DAC_OVERRIDEIgnorer les permissions de fichiers

La bonne pratique est de supprimer toutes les capabilities puis d'ajouter uniquement celles nécessaires :

apiVersion: v1
kind: Pod
metadata:
name: cap-demo
spec:
containers:
- name: app
image: nginx:1.28@sha256:146adea4768b83c607d0bdfa4188464e3da6e0a3ad4475db1d1d8f64f27c29cc
securityContext:
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE # Nginx écoute sur port 80

Vérification des capabilities actives :

Fenêtre de terminal
kubectl exec -it cap-demo -- cat /proc/1/status | grep Cap
# CapPrm: 0000000000000400
# CapEff: 0000000000000400

Le mode privileged: true désactive une grande partie de l'isolation habituelle du conteneur :

# ⚠️ NE JAMAIS FAIRE EN PRODUCTION
spec:
containers:
- name: dangerous
image: busybox
securityContext:
privileged: true # Désactive l'isolation conteneur

Les réglages vus jusqu'ici portent sur l'identité et les permissions. Ceux qui suivent activent des mécanismes du noyau Linux relayés par le runtime : filtrage des appels système, étiquetage SELinux, isolation des identifiants utilisateur. On les configure moins souvent à la main, mais le niveau Restricted des Pod Security Standards en réclame une partie.

Le profil Seccomp limite les appels système (syscalls) qu'un conteneur peut effectuer :

spec:
securityContext:
seccompProfile:
type: RuntimeDefault # Utilise le profil par défaut du runtime
TypeDescription
RuntimeDefaultProfil par défaut du runtime (recommandé)
UnconfinedPas de restriction (déconseillé)
LocalhostProfil personnalisé sur le nœud

Pour les clusters avec SELinux activé (RHEL, OpenShift) :

spec:
securityContext:
seLinuxOptions:
level: "s0:c123,c456"

Le paramètre procMount contrôle comment /proc est monté dans le conteneur. Par défaut, /proc est masqué partiellement pour la sécurité. Les Pod Security Standards contraignent ce paramètre au niveau Baseline et Restricted.

Depuis Kubernetes 1.36, les User Namespaces sont GA et activés par défaut (KEP-127). Ils permettent à un pod de tourner dans son propre namespace utilisateur Linux, distinct de celui de l'hôte. Conséquence : un processus qui se croit root (UID 0) à l'intérieur du conteneur correspond en réalité à un UID non privilégié côté hôte. Une évasion de conteneur ne donne plus root sur le nœud.

Le champ s'écrit spec.hostUsers (au niveau pod, pas dans securityContext) :

apiVersion: v1
kind: Pod
metadata:
name: app-with-userns
spec:
hostUsers: false # nouveau namespace utilisateur pour ce pod
securityContext:
runAsUser: 1000
runAsNonRoot: true
containers:
- name: app
image: myapp:1.0.0
ValeurEffet
true (défaut)Le pod partage le namespace utilisateur de l'hôte (comportement historique)
falseKubernetes crée un namespace utilisateur dédié et map les UIDs/GIDs du pod vers une plage non privilégiée de l'hôte

Voici un template de Security Context comme point de départ pour les workloads de production :

apiVersion: v1
kind: Pod
metadata:
name: production-secure
spec:
securityContext:
runAsUser: 1000
runAsGroup: 1000
runAsNonRoot: true
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: myapp:1.0.0
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "128Mi"
cpu: "500m"
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}

Ces sept réglages sont ceux qu'un audit de conformité contrôle en premier. La dernière colonne rappelle ce que chacun empêche : c'est elle qui permet d'arbitrer quand une application refuse de démarrer et qu'on est tenté de relâcher un paramètre.

ParamètreValeur recommandéePourquoi
runAsNonRoottrueRefuse le démarrage si root
runAsUser>= 1000UID non-root explicite
allowPrivilegeEscalationfalseBloque setuid/setgid (à définir explicitement)
readOnlyRootFilesystemtrueEmpêche l'écriture non contrôlée
capabilities.dropALLSupprime toutes les capabilities
privilegedfalse (ou absent)Jamais true en production
seccompProfileRuntimeDefaultLimite les syscalls autorisés

Les Pod Security Standards (PSS) définissent trois niveaux de restriction :

NiveauCe qui est autorisé
PrivilegedTout, aucune restriction
BaselineRestrictions minimales, pas de privileged, pas de hostNetwork, procMount limité
RestrictedHardening complet, ensemble plus large de contraintes

Un securityContext accepté par l'API n'est pas pour autant appliqué comme vous l'imaginez : l'image, le runtime ou le type de volume peuvent en modifier l'effet réel. Les commandes suivantes s'exécutent depuis l'intérieur du conteneur et montrent l'état effectif, à savoir l'identité du processus, les capabilities conservées et la possibilité d'écrire.

Générez un Pod avec Security Context :

Fenêtre de terminal
# Créer un Pod de base
kubectl run secure-test --image=nginx:1.28 \
--dry-run=client -o yaml > secure-pod.yaml
# Éditer pour ajouter le securityContext

La commande id affiche l'identité effective du processus. Les valeurs doivent correspondre exactement à runAsUser, runAsGroup et supplementalGroups déclarés dans le manifeste.

Fenêtre de terminal
kubectl exec -it secure-test -- id
# uid=1000 gid=1000 groups=1000

Les capabilities apparaissent en hexadécimal dans /proc/1/status. La ligne CapEff est celle qui compte : elle représente l'ensemble réellement utilisable par le processus à cet instant.

Fenêtre de terminal
kubectl exec -it secure-test -- cat /proc/1/status | grep -i cap

Un touch à la racine constitue le test le plus rapide : il doit échouer. S'il réussit, le paramètre n'a pas été pris en compte.

Fenêtre de terminal
kubectl exec -it secure-test -- touch /test.txt
# touch: /test.txt: Read-only file system

La quasi-totalité des incidents liés au securityContext se déclarent à deux moments : au démarrage du Pod, quand Kubernetes refuse la configuration, ou à la première écriture, quand l'application découvre ses restrictions. Le tableau associe chaque symptôme à sa cause, les commandes qui suivent permettent de confirmer le diagnostic.

Ces messages remontent dans la section Events de kubectl describe pod, et non dans les logs du conteneur : quand le démarrage est refusé, aucun log n'existe encore.

SymptômeCause probableSolution
Error: container has runAsNonRoot and image will run as rootImage définit USER 0, ou Kubernetes ne peut pas validerAjouter runAsUser: 1000 explicitement
Pod ne démarre pas, permission deniedrunAsUser incompatible avec l'imageVérifier l'UID attendu par l'image
Application ne peut pas écrirereadOnlyRootFilesystem: trueMonter des emptyDir pour les dossiers d'écriture
Bind port 80 échouecapabilities.drop: ALLAjouter NET_BIND_SERVICE ou utiliser port > 1024
Fichiers volumes non accessiblesfsGroup non supporté par le volumeVérifier la compatibilité du backend de stockage

Quand le tableau ne suffit pas, ces trois commandes donnent l'état complet : les événements du Pod, le securityContext réellement enregistré par l'API après fusion des niveaux Pod et conteneur, puis un conteneur jetable qui sert de point de comparaison sans restriction.

Fenêtre de terminal
# Voir les événements
kubectl describe pod <pod-name>
# Vérifier les paramètres de sécurité appliqués
kubectl get pod <pod-name> -o yaml | grep -A 20 securityContext
# Tester interactivement
kubectl run debug --image=busybox --rm -it -- sh

Pour l'examen CKAD, maîtrisez ces paramètres essentiels :

  1. runAsNonRoot: true, refuse le démarrage si Kubernetes ne peut pas valider un utilisateur non-root
  2. runAsUser: 1000, définit explicitement l'UID (souvent requis avec runAsNonRoot)
  3. allowPrivilegeEscalation: false, à définir explicitement pour bloquer les escalades
  4. capabilities.drop: [ALL], supprime toutes les capabilities, puis add si nécessaire
  5. readOnlyRootFilesystem: true, avec des emptyDir pour les dossiers d'écriture

Différence Pod vs conteneur : les valeurs conteneur écrasent les valeurs Pod.

Au-delà de la CKAD, ajoutez ces pratiques :

  1. seccompProfile: RuntimeDefault, limite les syscalls autorisés
  2. supplementalGroups, pour les accès à des groupes secondaires
  3. fsGroup avec vérification, testez la compatibilité avec vos volumes
  4. Combinaison avec PSS/PSA, appliquez les standards au niveau namespace
  5. Test des images, vérifiez que l'image supporte vraiment vos contraintes

Sept questions tirées de ce guide, avec un seuil de réussite fixé à 80 %.

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

7 questions
5 min.
80% requis

Informations

  • Le chronomètre démarre au clic sur Démarrer
  • Questions à choix multiples, vrai/faux et réponses courtes
  • Vous pouvez naviguer entre les questions
  • Les résultats détaillés sont affichés à la fin

Lance le quiz et démarre le chronomètre

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn