Aller au contenu
English
Conteneurs & Orchestration high

Pod Security Standards (PSS) : sécuriser les workloads Kubernetes

35 min de lecture

Logo Kubernetes

Les Pod Security Standards (PSS) définissent 3 niveaux de restrictions de sécurité pour vos pods Kubernetes : Privileged (aucune restriction), Baseline (bloque les escalades évidentes), et Restricted (hardening maximal). Ce guide vous explique quand utiliser chaque niveau et comment les appliquer sans casser vos applications.

Ce sujet relève du domaine Minimize Microservice Vulnerabilities de la certification CKS, qui pèse 20 % de l'épreuve.

Pod Security Standards (PSS) est le standard officiel Kubernetes pour définir les niveaux de sécurité des pods. Pensez-y comme à des "profils de sécurité prédéfinis" que vous pouvez appliquer à vos namespaces.

Par défaut, Kubernetes autorise des configurations dangereuses. Sans restriction explicite, n'importe qui pouvant créer un Pod peut lui donner un accès complet au nœud hôte : c'est exactement ce qu'un attaquant recherche.

Voici ce qui est autorisé si vous ne configurez rien :

Configuration par défautRisque
Pod en root (UID 0)Escalade de privilèges si le conteneur est compromis
privileged: true autoriséAccès complet à l'hôte
Montage de / possibleLecture/écriture du système hôte
hostNetwork: true autoriséÉcoute sur les ports de l'hôte

Scénario d'attaque : Un attaquant compromet une application vulnérable dans un pod. Si le pod tourne en root avec privileged: true, l'attaquant peut s'échapper du conteneur, accéder au nœud, puis pivoter vers d'autres nœuds du cluster. Les PSS empêchent ce scénario en bloquant ces configurations dès le déploiement.

Les PSS bloquent ces configurations selon leur niveau de restriction.

Les trois niveaux forment une échelle, pas un menu : chacun contient les restrictions du précédent et en ajoute. Privileged n'interdit rien, Baseline bloque les élévations de privilèges les plus connues, Restricted impose le durcissement complet. Les onglets ci-dessous donnent, pour chacun, ce qu'il autorise et le manifeste minimal qui y passe. Lisez-les dans l'ordre : le saut de difficulté se situe entre les deux derniers.

Aucune restriction, Ce niveau autorise tout, y compris les configurations les plus dangereuses.

Quand l'utiliser :

  • Namespaces système (kube-system)
  • Outils d'infrastructure (monitoring agents, CNI, CSI drivers)
  • Jamais pour des applications métier
# Ce pod est autorisé en Privileged
apiVersion: v1
kind: Pod
metadata:
name: privileged-pod
spec:
containers:
- name: debug
image: ubuntu:24.04@sha256:224a1869083a311ef3f13648a154ba79832fbef6364d31493642ca03082da254
securityContext:
privileged: true # Accès root complet à l'hôte
runAsUser: 0 # Exécution en root

Une règle à ne pas relâcher : le niveau Privileged est réservé aux composants système, ceux qui ont réellement besoin d'accéder au nœud. Ne l'appliquez jamais à un namespace applicatif, même temporairement pour débloquer un déploiement : c'est ainsi qu'un cluster se retrouve sans aucune contrainte plusieurs mois après l'incident qui l'a justifié.

Kubernetes applique les Pod Security Standards via Pod Security Admission (PSA), un admission controller intégré depuis la version 1.25.

PSA offre trois manières d'appliquer un niveau PSS à un namespace. Ces modes peuvent être combinés pour une migration progressive et sécurisée.

ModeComportementUsage
enforceBloque les pods non conformesProduction
auditEnregistre dans les audit logsMigration
warnAffiche un warning à l'utilisateurDéveloppement

Les trois sont indépendants, et la différence se voit à l'œil nu. Sur un namespace en warn seulement, le Pod non conforme est créé, précédé d'un avertissement :

Sortie
Warning: would violate PodSecurity "restricted:latest": allowPrivilegeEscalation
!= false (container "non-conforme" must set
securityContext.allowPrivilegeEscalation=false), unrestricted capabilities ...
pod/non-conforme created

Sur un namespace en enforce, la même commande échoue et aucun Pod n'existe :

Sortie
Error from server (Forbidden): pods "non-conforme" is forbidden:
violates PodSecurity "restricted:latest": allowPrivilegeEscalation != false ...

Notez le pod/non-conforme created de la première sortie : un avertissement ne protège de rien, il informe.

Stratégie de migration recommandée :

  1. D'abord warn + audit : Les développeurs voient les warnings, les violations sont loggées, mais rien n'est bloqué. Cela vous donne le temps de corriger les manifests.
  2. Ensuite enforce : Une fois tous les pods conformes, activez le blocage. Toute tentative de déploiement non conforme sera refusée.

Cette approche évite de casser la production du jour au lendemain.

PSS ne s'applique pas à un Pod mais à un namespace entier, par des labels. Deux façons de les poser, strictement équivalentes dans leur effet : les écrire dans le manifeste du namespace, ce qui les rend versionnables, ou les ajouter en ligne de commande sur un namespace existant. La première convient à une infrastructure décrite en Git, la seconde à un test rapide.

Méthode 1 : Labels sur le namespace

apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
# Applique Restricted en mode enforce
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
# Audit les violations Restricted
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/audit-version: latest
# Avertit pour Restricted
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: latest

Méthode 2 : kubectl label

Fenêtre de terminal
# Appliquer Restricted sur un namespace existant
kubectl label namespace production \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/warn=restricted \
pod-security.kubernetes.io/audit=restricted
# Vérifier les labels
kubectl get namespace production -o yaml | grep pod-security

Migrer vers Restricted sans casser vos applications

Section intitulée « Migrer vers Restricted sans casser vos applications »
  1. Audit d'abord : identifiez les violations

    Appliquez d'abord en mode audit + warn pour voir ce qui casserait :

    Fenêtre de terminal
    kubectl label namespace monapp \
    pod-security.kubernetes.io/audit=restricted \
    pod-security.kubernetes.io/warn=restricted

    Créez un pod de test et observez les warnings :

    Fenêtre de terminal
    kubectl run test --image=nginx:1.25@sha256:a484819eb60211f5299034ac80f6a681b06f89e65866ce91f356ed7c72af059c -n monapp
    # Warning: would violate "restricted:latest"...
  2. Corrigez vos Deployments

    Pour chaque violation identifiée, mettez à jour le securityContext :

    spec:
    securityContext:
    runAsNonRoot: true
    seccompProfile:
    type: RuntimeDefault
    containers:
    - name: app
    securityContext:
    allowPrivilegeEscalation: false
    readOnlyRootFilesystem: true
    runAsUser: 1000
    capabilities:
    drop: ["ALL"]
  3. Testez en staging

    Déployez avec les nouveaux securityContext et validez que l'application fonctionne.

  4. Appliquez Restricted en enforce

    Une fois tous les pods conformes :

    Fenêtre de terminal
    kubectl label namespace production \
    pod-security.kubernetes.io/enforce=restricted \
    --overwrite

Le blocage le plus fréquent vient de l'image, pas du manifeste. Beaucoup d'images officielles s'exécutent en root par défaut, nginx, redis ou postgres par exemple, et aucun securityContext ne les rendra conformes si elles n'ont pas été construites pour. Cherchez leur variante non-root : nginx devient nginxinc/nginx-unprivileged, et la plupart des éditeurs publient un équivalent. C'est souvent le seul changement à faire pour passer en Restricted.

Les trois messages ci-dessous représentent la quasi-totalité des rejets après un passage en Restricted, et ils ont un point commun : ils apparaissent au démarrage du Pod, pas à l'apply. Le manifeste est accepté, le Pod est créé, et c'est l'admission qui refuse. Cherchez donc dans les événements du Pod ou de son objet parent, jamais dans les journaux du conteneur, qui n'existent pas encore.

Le point de méthode qui fait gagner le plus de temps : le message liste toutes les violations en une fois. Corriger la première et relancer pour découvrir la suivante est une perte de temps, tout est déjà écrit.

Voici le message complet, relevé sur un Pod nginx sans aucun securityContext dans un namespace en restricted :

Sortie
Error from server (Forbidden): pods "non-conforme" is forbidden:
violates PodSecurity "restricted:latest":
allowPrivilegeEscalation != false (container "non-conforme" must set
securityContext.allowPrivilegeEscalation=false),
unrestricted capabilities (container "non-conforme" must set
securityContext.capabilities.drop=["ALL"]),
runAsNonRoot != true (pod or container "non-conforme" must set
securityContext.runAsNonRoot=true),
seccompProfile (pod or container "non-conforme" must set
securityContext.seccompProfile.type to "RuntimeDefault" or "Localhost")

Quatre violations, quatre corrections, et le manifeste ci-dessous les couvre toutes. Il a été appliqué avec succès dans un namespace en restricted, ce qui en fait le plus petit Pod conforme possible :

pod-conforme.yaml
apiVersion: v1
kind: Pod
metadata:
name: conforme
spec:
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: nginx:1.30@sha256:d5792f71a9496b833bc08ea834a758c46e2b6a6306c10f4be926f38a656cdc1c
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]

Deux champs se posent au niveau du Pod, deux au niveau du conteneur : c'est l'erreur de placement la plus fréquente. Les sections suivantes reprennent chaque violation isolément, pour le cas où vous n'en rencontreriez qu'une.

Erreur 1 : "container has runAsNonRoot and image will run as root"

Section intitulée « Erreur 1 : "container has runAsNonRoot and image will run as root" »

Cause : L'image utilise root mais vous avez runAsNonRoot: true.

Solution :

securityContext:
runAsUser: 1000 # Forcer un UID non-root
runAsGroup: 1000
runAsNonRoot: true

Cause : Le niveau Restricted exige un profil seccomp.

Solution :

spec:
securityContext:
seccompProfile:
type: RuntimeDefault

Cause : Vous n'avez pas droppé toutes les capabilities.

Solution :

containers:
- name: app
securityContext:
capabilities:
drop: ["ALL"]
# Si vraiment nécessaire, ajoutez explicitement :
# add: ["NET_BIND_SERVICE"]

Ce tableau résume ce qui est autorisé ou bloqué à chaque niveau. Utilisez-le comme référence rapide lors de vos audits de sécurité.

ConfigurationPrivilegedBaselineRestricted
privileged: true
hostNetwork/PID/IPC
hostPath volumes
Root (UID 0)
allowPrivilegeEscalation
Capabilities dangereuses
Seccomp requis

Comment lire ce tableau ?

  • Baseline bloque les vulnérabilités critiques (privileged, hostNetwork) mais autorise encore des pratiques risquées (hostPath, root).
  • Restricted va plus loin : il exige un profil seccomp, interdit root, et limite les volumes montés. C'est la posture "zéro confiance" pour vos workloads.
  • Attention à hostPath : Même en Baseline, un attaquant peut monter /etc/shadow s'il contrôle le manifest du pod. C'est pourquoi Restricted est recommandé en production.
  • 3 niveaux : Privileged (tout autorisé), Baseline (bloque le pire), Restricted (hardening maximal)
  • Production = Restricted : C'est le niveau recommandé par la documentation Kubernetes
  • Migration progressive : Utilisez audit et warn avant enforce
  • Pod Security Admission : L'admission controller natif qui applique les PSS via des labels
  • CKS : Les PSS représentent 20% du domaine "Minimize Microservice Vulnerabilities"

Sept questions sur ce qui coûte le plus cher en pratique : la différence réelle entre les trois niveaux, les trois modes d'application, et la lecture des rejets qui suivent un passage en Restricted.

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

7 questions
8 min.
70% 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