Aller au contenu
Edouard Topin's Blog
Sécurité Kubernetes en production / Série 01/04

RBAC Kubernetes : les fondations, et les pièges qui survivent à l'audit

Chacun de ces pièges est publié sur kubernetes.io. Ce qui manque, c'est leur mise en ordre — et le chemin qui mène d'un Namespace vSphere jusqu'à cluster-admin.

Edouard Topin
18 min de lecture
Illustration éditoriale abstraite d'un trousseau dont une clé est mise en garde par une lueur d'alerte.

Un auditeur pose une question simple sur ton cluster de production : qui peut se donner plus de droits qu’il n’en a, et par quel chemin ? Tu ouvres le dépôt Git, tu relis les ClusterRole, tout paraît propre. Le problème n’est pas là. Il est dans un champ que le plan de contrôle réécrit sans erreur ni événement, dans un verbe dont le nom ne suggère pas ce qu’il accorde, et — sur VKS — dans une permission cochée dans vCenter par quelqu’un qui n’a jamais écrit une ligne de YAML.

Cet article ne vend pas un secret. Les six pièges qu’il traite sont tous publiés : par la documentation Kubernetes amont pour cinq d’entre eux, par les TechDocs Broadcom pour le sixième. Ce qui manque, c’est leur mise en ordre — ils vivent à six endroits différents et aucune page ne les rassemble. On les rassemble ici, chacun avec le mécanisme qui le produit et la phrase publiée qui l’atteste, sur un plancher Kubernetes 1.35, avec l’ancrage VKS de VCF 9. Aucun lab n’a été exécuté : ce qui suit est de la documentation lue et mise en ordre, pas un relevé de plateforme.

Plancher 1.35 — cible 1.36RBAC + Pod Security AdmissionSix pièges publiés

TL;DR

  • La décision — sortir du cluster-admin partagé, énumérer ressources et verbes au lieu d’un joker, et n’écrire jamais dans le champ rules d’un ClusterRole agrégé : le plan de contrôle l’écrase.
  • L’arbitrage qui coûte — auditer coûte exactement le droit qu’on audite. kubectl auth can-i --as=… exige le verbe impersonate, et la documentation amont publie qu’il ne se restreint ni par namespace ni par ressource : « It either grants full impersonation or none at all. »
  • L’action de lundi matin — cinq requêtes kubectl en lecture seule pour l’inventaire côté cluster, et la matrice des permissions de vSphere Namespace pour l’inventaire côté plateforme. Sur VKS, l’un sans l’autre est structurellement faux.

Le contrat de version

Cette série pose un plancher explicite parce qu’une affirmation d’API sans numéro de version n’est pas vérifiable. Plancher : Kubernetes 1.35. Cible : Kubernetes 1.36. Relevé effectué le 16 août 2026 sur kubernetes.io/releases.

Version mineure Dernier correctif Date Fin de vie
1.36 1.36.2 2026-06-09 2027-06-28
1.35 1.35.6 2026-06-09 2027-02-28
1.34 1.34.9 2026-06-09 2026-10-27

La règle de support est publiée mot pour mot : « The Kubernetes project maintains release branches for the most recent three minor releases (1.36, 1.35, 1.34). Kubernetes 1.19 and newer receive approximately 1 year of patch support. » 1.34 sort de support le 27 octobre 2026, treize jours avant la parution de cet article. C’est ce qui fixe le plancher à 1.35, et rien d’autre. Une version 1.37 est annoncée sur la même page, dans une section Upcoming Release, sans date de sortie publiée et hors de tout tableau de versions — elle ne change donc ni le plancher, ni la cible.

Côté plateforme, les VKr publiés au moment de la rédaction sont v1.36.1+vmware.4-vkr.5 (Kubernetes 1.36.1, 18 juin 2026), v1.35.5+vmware.1-vkr.1 et v1.34.8+vmware.1-vkr.1. Le VKr 1.36.1 embarque Antrea 2.6.1, Calico 3.31.5, etcd 3.6.11, containerd 2.3.1 et CoreDNS 1.14.3. La version réelle de ton cluster reste REPLACE_WITH_VKR_VERSION tant qu’elle n’est pas relevée sur l’instance — la question du plancher est traitée côté cycle de vie dans Day-2 Ops VKS : lifecycle et upgrades.

Ce que cet article ne décrira pas, parce que c’est retiré

PodSecurityPolicy a été déprécié en Kubernetes v1.21 et retiré de Kubernetes en v1.25. La page amont porte un bandeau intitulé Removed feature et la phrase : « PodSecurityPolicy was deprecated in Kubernetes v1.21, and removed from Kubernetes in v1.25. » Le plancher de cette série est dix versions mineures après ce retrait : le mécanisme se nomme ici au passé, et l’article n’y revient qu’une fois, pour apprendre à lire une documentation constructeur.

La même page nomme le remplacement, et il y en a deux, pas un : Pod Security Admission, ou un greffon d’admission tiers que l’on déploie et configure soi-même. Elle renvoie aussi à un guide de migration depuis PodSecurityPolicy — je ne l’ai pas ouvert, donc je n’en cite aucune étape.

Règle appliquée dans tout ce qui suit : chaque affirmation d’API porte la version où elle vaut et l’état du mécanisme parmi alpha, beta, stable, deprecated, retiré.

Quatre objets, et c’est le binding qui fixe la portée

Le groupe d’API est rbac.authorization.k8s.io, en apiVersion: rbac.authorization.k8s.io/v1, stable — la page de référence ne porte aucun bandeau FEATURE STATE.

Objet Porte quoi Portée Piège de lecture
Role des règles (apiGroups, resources, verbs, resourceNames) un namespace aucun — c’est le seul objet dont le nom ne ment pas
ClusterRole les mêmes règles, plus nonResourceURLs cluster ou namespace, selon le binding son nom dit « cluster », sa portée effective dépend d’ailleurs
RoleBinding un roleRef + des subjects un namespace peut référencer un ClusterRole
ClusterRoleBinding un roleRef + des subjects cluster entier seul objet qui donne réellement une portée cluster

La quatrième colonne repose sur une phrase publiée : « A RoleBinding may reference any Role in the same namespace. Alternatively, a RoleBinding can reference a ClusterRole and bind that ClusterRole to the namespace of the RoleBinding. » Conséquence opérationnelle : lire le kind d’un roleRef ne suffit pas à connaître une portée, il faut lire le kind du binding. Un ClusterRole: admin lié par un RoleBinding est un rôle de namespace ; lié par un ClusterRoleBinding, c’est un rôle de cluster. Même objet, deux mondes.

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: webshop-db
  name: webshop-db-reader
rules:
  - apiGroups: [""]
    resources: ["pods", "pods/log"]
    verbs: ["get", "list", "watch"]

La syntaxe pods/log n’est pas une convention d’écriture : c’est le mécanisme publié des sous-ressources, la barre oblique délimitant la ressource et sa sous-ressource. Et resourceNames, qui semble être la façon évidente de restreindre, porte une note qu’il faut lire en entier : on ne peut restreindre par nom ni deletecollection ni un create de premier niveau — mais la limite sur create ne vaut pas pour une sous-ressource comme pods/exec ; et une restriction par nom sur list ou watch impose au client d’envoyer un sélecteur de champ metadata.name, faute de quoi la requête est refusée. Un Role restreint par resourceNames qui « ne passe pas » est presque toujours ce dernier cas, pas un défaut d’autorisation.

Les six pièges qui survivent à l’audit

Chacun est un mécanisme, plus la phrase publiée qui l’atteste, plus une conséquence concrète. Aucun n’est une opinion. Aucun n’est inédit — et c’est précisément le sujet.

# Mécanisme Où c’est publié
1 joker sur resources ou verbs page RBAC Authorization, admonition Caution
2 champ rules d’un ClusterRole agrégé, réécrit par le plan de contrôle page RBAC Authorization, section sur l’agrégation
3 onze voies d’escalade par les verbes et sous-ressources page Role Based Access Control Good Practices
4 clause Secrets du rôle edit ; groupe system:masters page RBAC Authorization ; page Certificate Signing Requests
5 jetons de ServiceAccount et leur dérive de version pages Service Accounts et Managing Service Accounts
6 permission Can edit de vSphere Namespace → cluster-admin Broadcom TechDocs, Configuring Identity and Access for VKS Clusters

1 — Le joker qui grandit tout seul

L’admonition publiée est explicite : « Using wildcards in resource and verb entries could result in overly permissive access being granted to sensitive resources. For instance, if a new resource type is added, or a new subresource is added, or a new custom verb is checked, the wildcard entry automatically grants access, which may be undesirable. » La page Good Practices enfonce le clou : un accès joker donne des droits « not just to all object types that currently exist in the cluster, but also to all object types which are created in the future ».

Un ClusterRole avec resources: ["*"] écrit et revu en 2024 couvre en 2026 les CRD installées depuis par un opérateur tiers. Le manifeste n’a pas changé ; sa portée si. Voilà pourquoi le joker survit aux revues : la revue relit le manifeste, pas le catalogue d’API.

2 — Le champ rules d’un rôle agrégé, qui se réécrit

Un ClusterRole porteur d’un aggregationRule voit son champ rules rempli par le plan de contrôle à partir des ClusterRole désignés par le sélecteur de labels. La phrase décisive : « The control plane overwrites any values that you manually specify in the rules field of an aggregate ClusterRole. If you want to change or add rules, do so in the ClusterRole objects that are selected by the aggregationRule. »

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: webshop-platform-monitoring
aggregationRule:
  clusterRoleSelectors:
    - matchLabels:
        nordwind.corp.example/aggregate-to-monitoring: "true"
# ne pas écrire `rules:` ici — le plan de contrôle le remplit et écrase toute valeur saisie

Un correctif de sécurité posé à la main dans le champ rules d’un rôle agrégé disparaît sans erreur, sans événement, sans journal applicatif. kubectl apply répond configured, le diff du dépôt Git est propre, et seul un kubectl get clusterrole <nom> -o yaml après coup montre l’écrasement. C’est, en théorie, le piège le plus discret de la liste, parce qu’il est invisible depuis la chaîne de livraison — voir GitOps sur VKS : bootstrap Argo CD multi-tenant.

L’autre face du même mécanisme : les rôles intégrés admin, edit et view sont eux-mêmes agrégés, via les labels rbac.authorization.k8s.io/aggregate-to-admin, -edit et -view. Poser aggregate-to-view: "true" sur un ClusterRole livré par un opérateur tiers étend donc le rôle view de tout le cluster sans qu’aucun binding n’ait été touché. La documentation publie le mécanisme ; cette conséquence-là est un raisonnement de ma part, pas une phrase de la page — mais elle suit directement du mécanisme publié, et elle est invisible dans un inventaire de bindings, qui est justement ce qu’un audit RBAC regarde en premier.

Mécanisme voisin à connaître : l’auto-réconciliation. « At each start-up, the API server updates default cluster roles with any missing permissions, and updates default cluster role bindings with any missing subjects. » Une permission retirée à la main d’un rôle intégré revient donc au redémarrage du plan de contrôle — c’est-à-dire, sur VKS, au prochain upgrade. L’annotation rbac.authorization.kubernetes.io/autoupdate: "false" désactive la réconciliation, avec l’avertissement publié qui va avec : « missing default permissions and subjects can result in non-functional clusters ».

3 — Les verbes qui donnent plus que leur nom ne suggère

La page Role Based Access Control Good Practices publie une section entière — Kubernetes RBAC - privilege escalation risks — avec onze sous-sections nommées. Les plus coûteuses :

Verbe / ressource Ce que la page publie Conséquence
escalate sur roles/clusterroles « users with this right can effectively escalate their privileges » contourne la protection centrale de RBAC
bind sur un rôle « allowing users to create bindings to roles with rights they do not already have » sans resourceNames, permet de lier cluster-admin
impersonate « This verb allows users to impersonate and gain the rights of other users in the cluster. » non restreignable — voir plus bas
list/watch sur secrets « list and watch access also effectively allow for users to reveal the Secret contents » list sans get n’est pas une protection
create sur des charges « granting permission to create workloads also implicitly grants the API access levels of any service account in that namespace » exécute sous n’importe quel ServiceAccount du namespace
get sur nodes/proxy « get permission on nodes/proxy is not a read-only permission » et « This access bypasses audit logging and admission control » un get qui exécute, et qui échappe au journal d’audit
create sur serviceaccounts/token « can create TokenRequests to issue tokens for existing service accounts » fabrique un jeton pour n’importe quel ServiceAccount existant
create sur certificatesigningrequests + update sur …/approval crée des certificats clients « with arbitrary names including duplicates of Kubernetes system components » fabrique une identité, y compris homonyme d’un composant système

Le cas impersonate mérite sa propre phrase, parce que sa limite vit sur une page dédiée, User Impersonation : « With the impersonate verb, impersonation cannot be limited or scoped. It either grants full impersonation or none at all. Once granted permission to impersonate a user, you can perform any action that user can perform across all resources and namespaces. » La même page précise que l’impersonation n’est pas à portée de namespace et exige donc un ClusterRole et un ClusterRoleBinding, jamais un Role. impersonate ne se dose pas. Le donner à un outil d’audit, c’est lui donner le cluster.

Pour déléguer le droit de déléguer sans accorder escalate, le mécanisme publié est le verbe bind avec resourceNames :

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: nordwind-role-grantor
rules:
  - apiGroups: ["rbac.authorization.k8s.io"]
    resources: ["rolebindings"]
    verbs: ["create"]
  - apiGroups: ["rbac.authorization.k8s.io"]
    resources: ["clusterroles"]
    verbs: ["bind"]
    # commentaire de la documentation amont : « omit resourceNames to allow binding any ClusterRole »
    resourceNames: ["edit", "view"]

4 — edit n’est pas un rôle de développeur, et system:masters n’est pas un groupe comme les autres

La description publiée du rôle edit nomme la conséquence dans la même phrase que le rôle : « this role allows accessing Secrets and running Pods as any ServiceAccount in the namespace, so it can be used to gain the API access levels of any ServiceAccount in the namespace. » Donner edit à grp-webshop-devs sur webshop-app, c’est donc leur donner les Secrets du namespace et le droit de s’exécuter sous sa-webshop-app. À l’inverse, view porte l’exclusion inverse, publiée elle aussi : « This role does not allow viewing Secrets, since reading the contents of Secrets enables access to ServiceAccount credentials in the namespace ». view est le seul rôle intégré dont l’exclusion est écrite — c’est ce qui en fait le bon choix pour un groupe de lecture comme grp-secops.

Deux clauses souvent perdues, et purement dépendantes de la version : admin et edit n’accordent pas l’écriture sur les EndpointSlices « in clusters created using Kubernetes v1.22+ », en atténuation de CVE-2021-25740, et « Existing clusters that have been upgraded to Kubernetes v1.22 will not be subject to this change ». Le critère publié est la version de création du cluster, pas sa version courante. Aucune page Broadcom que j’ai consultée ne publie cette information par cluster : la version de création de vks-fret-01 reste REPLACE_WITH_CLUSTER_CREATION_VERSION, à relever sur l’instance.

Volet system:masters : c’est le ClusterRoleBinding par défaut de cluster-admin. La page Certificates and Certificate Signing Requests publie la réserve qui compte : « The CertificateSubjectRestriction admission plugin is enabled by default to restrict system:masters, but it is often not the only cluster-admin subject in a cluster. » Ce greffon figure bien dans la liste des contrôleurs d’admission activés par défaut en 1.36. Un porteur de certificat client O=system:masters n’apparaît, lui, dans aucun objet du cluster — j’y reviens dans la section vigilance.

5 — Le jeton de ServiceAccount, et ce qui a réellement changé

Domaine à forte dérive de version : chaque ligne porte la sienne.

Fait publié Version État
jeton court à rotation automatique via l’API TokenRequest, monté en volume projeté — « Starting from v1.22 onwards » v1.22 comportement par défaut
avant cela, « Kubernetes provides a long-lived, static token to the Pod as a Secret » < v1.22 historique
durée de vie par défaut d’un jeton TokenRequest : 1 heure, rafraîchi par le kubelet avant expiration comportement publié
gate LegacyServiceAccountTokenNoAutoGeneration activé par défaut v1.24 → v1.26 gate
« The feature gate is removed in v1.27, because it was elevated to GA status; you can still create indefinite service account tokens manually » v1.27 stable, gate retiré
annotation kubernetes.io/enforce-mountable-secrets v1.32 deprecated

La croyance « depuis 1.24 les jetons permanents n’existent plus » est fausse. Ce qui a changé, c’est qu’ils ne sont plus créés automatiquement : la création manuelle d’un Secret de type kubernetes.io/service-account-token reste un chemin publié, et produit un jeton sans expiration. L’audit doit donc chercher les Secrets de ce type, et non se fier au numéro de version du cluster. La page d’authentification ajoute la conséquence en une phrase : « any user with write access to Secrets can request a token, and any user with read access to those Secrets can authenticate as the service account. »

Le levier de réduction est publié des deux côtés — page Service Accounts et page Good Practices : automountServiceAccountToken: false, à poser sur le ServiceAccount, sur le pod, ou sur les deux.

apiVersion: v1
kind: ServiceAccount
metadata:
  name: sa-webshop-app
  namespace: webshop-app
automountServiceAccountToken: false
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webshop-app
  namespace: webshop-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: webshop-app
  template:
    metadata:
      labels:
        app: webshop-app
    spec:
      serviceAccountName: sa-webshop-app
      automountServiceAccountToken: false
      containers:
        - name: app
          image: "REPLACE_WITH_IMAGE_REFERENCE"

L’injection automatique n’a lieu que « provided that neither the ServiceAccount’s automountServiceAccountToken field nor the Pod’s automountServiceAccountToken field is set to false » — poser les deux est donc redondant, et explicite.

6 — Sur VKS, une permission vSphere accorde cluster-admin Kubernetes

C’est le seul piège de la liste qui ne soit pas amont, et le plus coûteux dans un contexte VCF. La page Configuring Identity and Access for VKS Clusters l’ouvre par une phrase qui vaut à elle seule la lecture : « There are two types of role based access control (RBAC) systems for VKS clusters: vSphere Namespace permissions and Kubernetes RBAC authorization. » Deux systèmes, et l’un alimente l’autre dans un seul sens.

Permission de vSphere Namespace Ce qu’elle donne côté vSphere Effet dans les clusters Kubernetes
Can edit créer, lire, mettre à jour et supprimer des clusters VKS le système crée un ClusterRoleBinding sur chaque cluster du vSphere Namespace et le lie au ClusterRole nommé cluster-admin
Can view accès en lecture seule aux objets de cluster VKS aucun privilège accordé dans les clusters
Owner administre les clusters, peut créer et supprimer des vSphere Namespaces via kubectl réservé à vCenter SSO — « You cannot use the owner role with a user/group from an external identity provider. »

Un administrateur vSphere qui accorde Can edit sur le vSphere Namespace de vks-fret-01, simplement pour permettre à une équipe de créer un cluster, lui accorde par la même action cluster-admin sur chaque cluster de ce namespace. Aucun manifeste RBAC n’a été écrit, aucune revue de code ne l’a vu, et l’origine du binding est hors du cluster — donc hors du dépôt Git. Le modèle de délégation qui produit cette situation est détaillé dans VCF 9.1 : Kubernetes en self-service.

Réserve d’honnêteté sur cette source : la page a bien été consultée, mais son rendu m’est parvenu partiellement reformulé par l’outil de chargement. Je paraphrase donc le mécanisme du binding cluster-admin en nommant la page, plutôt que de le citer entre guillemets — la citation exacte demande une reconfirmation que je n’ai pas. Autre borne à connaître : l’équivalent de cette page dans l’arbre de documentation 9.1 répond 404 à l’adresse construite par analogie ; l’ancrage RBAC de plateforme se cite donc depuis l’arbre 9.0, et je préfère le dire.

Sous quel profil un pod démarre : Pod Security Admission

RBAC répond à « qui peut appeler l’API ». Pod Security Admission répond à « à quoi un pod a le droit de ressembler ». Le mécanisme porte le bandeau FEATURE STATE: Kubernetes v1.25 [stable], et le contrôleur PodSecurity figure dans la liste des contrôleurs d’admission activés par défaut en Kubernetes 1.36. La page d’admission le formule sobrement : « PodSecurity replaced an older admission controller named PodSecurityPolicy. »

Trois niveaux — privileged, baseline, restricted — et trois modes, chacun avec sa définition publiée : enforce (« Policy violations will cause the pod to be rejected »), audit (annotation d’audit, sinon autorisé) et warn (avertissement à l’utilisateur, sinon autorisé). Les labels de namespace suivent la forme pod-security.kubernetes.io/<MODE>: <LEVEL>, avec un épinglage optionnel pod-security.kubernetes.io/<MODE>-version.

D’où le choix de poser warn et audit au même niveau que enforce, pour que l’objet de charge soit signalé avant que ses pods ne soient refusés. L’épinglage à v1.35 fige le jeu de règles au plancher de la série ; laisser latest fait varier le comportement d’un upgrade à l’autre. C’est un choix éditorial, il se justifie, il ne se subit pas.

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

Sur VKS, le constructeur publie que « VKS releases v1.25 and later enable the Pod Security Admission (PSA) controller », avec trois namespaces explicitement exemptés — kube-system, tkg-system et vmware-system-cloud-provider — parce que certains pods système y demandent des privilèges élevés. Ne pas leur appliquer ces labels.

Un dernier point relie PSA à RBAC, et justifie de traiter les deux dans le même article : les exemptions PSA se déclinent sur trois dimensions — noms d’utilisateur, RuntimeClassNames, namespaces. Un sujet exempté par son nom d’utilisateur n’est pas soumis au contrôle, quels que soient les labels du namespace. Un inventaire RBAC seul ne le montre pas.

Cinq requêtes qui ne modifient rien

Aucune page amont ne publie cette séquence — c’est un assemblage de ma part, chaque commande étant documentée individuellement. Les cinq sont en lecture seule.

# 1 — qui détient cluster-admin, directement, à l'échelle du cluster
kubectl get clusterrolebindings -o json \
  | jq -r '.items[] | select(.roleRef.name=="cluster-admin")
           | [.metadata.name, (.subjects//[] | map(.kind+":"+.name) | join(","))] | @tsv'

# 2 — qui détient les verbes d'escalade, où qu'ils soient déclarés
kubectl get clusterroles,roles -A -o json \
  | jq -r '.items[] | . as $r | (.rules//[])[]
           | select((.verbs//[]) | any(. == "escalate" or . == "bind" or . == "impersonate" or . == "*"))
           | [$r.kind, ($r.metadata.namespace // "-"), $r.metadata.name, (.verbs|join("|"))] | @tsv'

# 3 — quels ClusterRole portent un joker sur les ressources ou les verbes
kubectl get clusterroles -o json \
  | jq -r '.items[] | . as $r | (.rules//[])[]
           | select(((.resources//[]) | index("*")) or ((.verbs//[]) | index("*")))
           | [$r.metadata.name, ((.apiGroups//["-"])|join("|"))] | @tsv'

# 4 — quels ClusterRole étendent silencieusement les rôles intégrés par agrégation
kubectl get clusterroles -l 'rbac.authorization.k8s.io/aggregate-to-view=true' \
  -o custom-columns=NAME:.metadata.name
kubectl get clusterroles -l 'rbac.authorization.k8s.io/aggregate-to-edit=true' \
  -o custom-columns=NAME:.metadata.name

# 5 — ce qu'un sujet donné peut réellement faire, du point de vue de l'API server
kubectl auth can-i --list \
  --as="sso:REPLACE_WITH_SSO_GROUP@corp.example" \
  --namespace=webshop-app

Sur VKS, le format de sujet publié est sso:<USER-NAME>@<DOMAIN> pour un utilisateur et sso:<GROUP-NAME>@<DOMAIN> pour un groupe d’annuaire — l’authentification passant par vCenter Single Sign-On ou par un IdP OIDC externe via Pinniped, avec un webhook d’authentification qui s’exécute en pod dans le cluster.

Deux réserves sur l’outillage. D’abord, la requête 5 exige elle-même le verbe impersonate : --as est une usurpation d’identité. Auditer coûte le droit qu’on audite ; l’inventaire se fait donc depuis un compte d’audit dédié, dont le droit impersonate est lui-même inventorié et daté. Ensuite, kubectl auth whoami — utile quand l’authentification passe par un webhook, ce qui est le cas ici — est publié avec un synopsis qui commence par « Experimental: ». Je le cite comme tel : ce n’est pas un outil d’audit stable.

Enfin, la documentation amont publie un levier de journalisation des refus RBAC : lancer le kube-apiserver avec --vmodule=rbac*=5 ou --v=5 fait apparaître les refus dans le journal, préfixés par RBAC. Aucune page Broadcom que j’ai consultée ne décrit la modification des arguments du kube-apiserver d’un cluster VKS. Ce levier se cite donc comme amont, et pas comme disponible sur VKS.

Pièges & points de vigilance

Le plancher de droits d’un authentifié n’est pas zéro. Les ClusterRole system:basic-user et system:discovery sont liés au groupe system:authenticated, et system:public-info-viewer — introduit en v1.14 — est lié à system:authenticated et system:unauthenticated. Une réponse d’audit qui dit « ce compte n’a aucun droit » est fausse : la formulation exacte est « aucun droit au-delà des rôles de découverte ». Le levier publié pour l’accès anonyme est le drapeau --anonymous-auth=false sur l’API server.

Le vestige de documentation, et comment le lire. Quatre pages Broadcom traitant de PodSecurityPolicy ont été consultées le 16 août 2026. Trois bornent correctement le mécanisme par version : Configure PSA for VKr 1.25 and Later écrit que le contrôleur PSA remplace le contrôleur PSP, « which is deprecated and removed » ; Security for VKS Clusters écrit « For vSphere Kubernetes releases up to v1.24 » ; Apply Default Pod Security Policy to VKS Clusters écrit « VKS clusters using VKr 1.24 and eariler include default pod security policy » (la coquille est dans la source, je ne la corrige pas en citant). La quatrième, vSphere Supervisor Security, porte une phrase sans borne de version : « Restrictive Pod Security Admission (PSA) and PodSecurityPolicy (PSP) are available for VKS clusters. »

Je ne fais aucun procès à l’éditeur, et il n’y en a pas à faire : trois pages sur quatre sont correctes, sur un corpus dont les objets — vmware-system-privileged, vmware-system-restricted — appartiennent à une génération révolue. Ce qui compte est la règle de lecture, et elle vaut bien au-delà de VMware : sur un cluster VKr ≥ 1.25, une consigne de documentation — interne ou constructeur — qui demande un RoleBinding vers un PSP est un vestige ; le mécanisme applicable est Pod Security Admission.

Le retour arrière, enfin. RBAC n’a pas de « rollback » comme mécanisme : revenir en arrière, c’est appliquer l’état antérieur. L’outil publié pour cela est kubectl auth reconcile, qui crée ou met à jour des objets rbac.authorization.k8s.io/v1 depuis un manifeste, avec --remove-extra-permissions et --remove-extra-subjects en option. Un essai à blanc se fait par kubectl auth reconcile -f rbac-nordwind.yaml --dry-run=client. Ce qui compte réellement, c’est de ne jamais retirer le dernier ClusterRoleBinding vers cluster-admin avant d’avoir vérifié le chemin de rupture — et sur VKS, le chemin de rupture documenté est précisément le piège 6, utilisé cette fois-ci volontairement et écrit comme tel dans le changement.

Conclusion

RBAC autorise des appels d’API. Il n’autorise pas des comportements. Un ClusterRole en lecture seule n’empêche aucun pod déjà démarré d’ouvrir une socket vers n’importe quel autre pod du cluster : sa-webshop-app, dont on vient de couper le montage automatique de jeton, reste joignable depuis webshop-web comme depuis n’importe quel namespace, parce que rien dans ce plan ne parle de réseau. C’est une frontière, pas un défaut — et c’est exactement là que commence l’article suivant.

Ce qui se décide

Rôles nommés à la place du cluster-admin partagé, ressources et verbes énumérés, écriture uniquement dans les rôles sélectionnés par l’agrégation.

Ce qui coûte

Auditer demande impersonate, qui ne se restreint ni par namespace ni par ressource. Compte d’audit dédié, droit inventorié et daté.

Ce qu’on fait lundi

Les cinq requêtes en lecture seule, la recherche des Secrets kubernetes.io/service-account-token, et la matrice des permissions de vSphere Namespace.

Suite de la série : Network policies et Cilium : construire un default-deny défendable, qui prend la deuxième question de l’auditeur — qui peut parler à qui — et hérite d’ici les namespaces webshop-*, les service accounts devenus sélecteurs, et le profil PSA appliqué.

Lecture voisine sur le blog : Premier cluster VKS sur VCF 9 pour le prérequis de plateforme, La nouvelle architecture VCF 9 expliquée aux architectes pour le modèle fleet / instance, et VCF 9.1 : sécurité et résilience pour le contexte de durcissement.

Reçois le prochain par email

Les nouveaux articles et séries, envoyés à leur publication. Aucun autre courrier.

Désabonnement en un clic, à tout moment.

Retour au blog
Partager

Articles similaires

  1. 23 min de lecture

    Network policies et Cilium : construire un default-deny défendable

    L'API NetworkPolicy est livrée avec Kubernetes ; l'appliquer est le travail du CNI. Ce que Cilium ajoute, ce qui reste standard, et comment atteindre le default-deny sans casser la production.

  2. 18 min de lecture

    Sécurité runtime : Falco et Tetragon, sur quel critère trancher

    Falco et Tetragon collectent tous deux en eBPF. Ce qui les sépare est ailleurs : périmètre d’événements, modèle de règles, et surtout ce que chacun peut réellement empêcher.

  3. 18 min de lecture

    Supply chain : Sigstore, SBOM, admission control

    Kubernetes ne vérifie aucune signature d'image par lui-même. Signer avec Sigstore, inventorier avec un SBOM, refuser à l'admission — et ce que chacun de ces verbes recouvre réellement.

Suivre le blog

Nouveaux articles, réflexions et mises à jour.