Kubernetes RBAC: rolgebaseerde toegangscontrole voor clusters

RBAC bepaalt wie wat mag doen binnen een Kubernetes-cluster. Het is het primaire autorisatiemechanisme in elk productiecluster, en fouten erin zorgen er ofwel voor dat je CI/CD-pipeline wordt geblokkeerd, of dat een service account stilletjes cluster-admin rechten krijgt terwijl het alleen leestoegang nodig had. Deze gids behandelt de vier RBAC-objecten, laat zien hoe je least-privilege rollen bouwt voor workloads en pipelines, en loopt door de debuggingworkflow voor de onvermijdelijke Forbidden-fout.

Inhoudsopgave

Wat RBAC wel en niet doet

RBAC is de autorisatielaag van Kubernetes. Het beslist of een geauthenticeerde identiteit (een user, een group of een service account) een specifieke actie mag uitvoeren op een specifieke resource. Toegang wordt standaard geweigerd: als geen enkele RBAC-regel een permissie expliciet toekent, geeft de API-server 403 Forbidden terug.

RBAC gaat niet over authenticatie. Op het moment dat een RBAC-check draait, weet Kubernetes al wie er belt. Hoe die identiteit is vastgesteld (clientcertificaat, OIDC-token, cloud IAM) is een apart vraagstuk.

Twee eigenschappen bepalen elke beslissing die je met RBAC maakt:

  1. Permissies zijn puur additief. Er bestaan geen deny-regels. Je kent toegang toe; je kunt een permissie die ergens anders is verleend niet expliciet intrekken. De officiele documentatie zegt het letterlijk: "Permissions are purely additive (there are no 'deny' rules)."
  2. RBAC staat standaard aan in elk cluster dat is aangemaakt met kubeadm en bij elke grote managed provider (GKE, EKS, AKS). Je gebruikt het al.

Wat RBAC niet regelt: netwerkverkeer tussen pods. Een gecompromitteerde pod zonder RBAC-permissies kan nog steeds elke service in het cluster bereiken, tenzij je network policies afdwingt.

De vier RBAC-objecten

RBAC gebruikt vier Kubernetes-objecten uit de rbac.authorization.k8s.io/v1 API-groep:

Object Scope Doel
Role Namespace Definieert permissies binnen een namespace
ClusterRole Cluster Definieert permissies clusterbreed of voor non-namespaced resources
RoleBinding Namespace Kent een Role of ClusterRole toe aan subjects binnen een namespace
ClusterRoleBinding Cluster Kent een ClusterRole toe aan subjects over alle namespaces

Subjects zijn de identiteiten die via bindings permissies ontvangen. Er bestaan drie soorten:

  • User (een externe identiteit; Kubernetes beheert geen user-accounts)
  • Group (een set users; lidmaatschap wordt bepaald door de authenticatielaag)
  • ServiceAccount (een namespaced, niet-menselijke identiteit die Kubernetes zelf beheert)

Elke RBAC-regel is een combinatie van API-groepen, resources, verbs en optioneel resourceNames. De lege string "" als API-groep betekent de core-groep (pods, services, secrets, configmaps).

Role vs. ClusterRole

Een Role stelt permissies in binnen een enkele namespace. Je geeft de namespace op bij het aanmaken:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: pod-reader
  namespace: production
rules:
- apiGroups: [""]             # core API-groep
  resources: ["pods"]
  verbs: ["get", "list", "watch"]

Een ClusterRole is niet namespaced. Gebruik het voor:

  • Permissies op non-namespaced resources (nodes, persistent volumes, namespaces zelf)
  • Permissies op non-resource URLs (/healthz, /metrics, /readyz)
  • Een herbruikbaar permissiesjabloon dat meerdere namespaces delen via RoleBindings
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: node-viewer
rules:
- apiGroups: [""]
  resources: ["nodes"]
  verbs: ["get", "list", "watch"]
- nonResourceURLs: ["/healthz", "/readyz"]  # alleen geldig in ClusterRoles
  verbs: ["get"]

Vuistregel: kies Role boven ClusterRole als de permissies namespace-scoped zijn. Een ClusterRole is alleen nodig als de resource non-namespaced is, als je nonResourceURLs nodig hebt, of als je permissies een keer wilt definieren en in meerdere namespaces wilt binden.

Subresources hebben aparte grants nodig

get op pods geeft geen toegang tot pods/exec, pods/log of pods/portforward. Elke subresource heeft zijn eigen regel nodig. De verb voor exec en port-forward is create, niet get:

rules:
- apiGroups: [""]
  resources: ["pods/log"]
  verbs: ["get"]
- apiGroups: [""]
  resources: ["pods/exec"]
  verbs: ["create"]         # exec gebruikt de create-verb

RoleBinding vs. ClusterRoleBinding

Een RoleBinding kent permissies toe binnen een specifieke namespace. Een ClusterRoleBinding kent ze clusterbreed toe.

Het meest misverstane patroon: een RoleBinding kan verwijzen naar een ClusterRole. Als dat gebeurt, worden de permissies beperkt tot de namespace van de RoleBinding, niet clusterbreed. Dit is hoe Kubernetes bedoeld is om de vier ingebouwde ClusterRoles (view, edit, admin, cluster-admin) te gebruiken zonder hun definities in elke namespace te dupliceren.

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: dev-team-view
  namespace: staging
subjects:
- kind: Group
  name: dev-team
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole    # verwijst naar een ClusterRole, maar beperkt tot staging
  name: view
  apiGroup: rbac.authorization.k8s.io

Dit geeft de dev-team group alleen-lezen-toegang in staging. Ze kunnen geen resources in andere namespaces zien.

Ingebouwde ClusterRoles

Kubernetes levert vier ClusterRoles die ontworpen zijn voor namespace-niveau gebruik via RoleBindings:

ClusterRole Wat het toekent Wat het uitsluit
view Alleen-lezen-toegang tot de meeste resources Secrets, Role, RoleBinding
edit Volledige CRUD op workload-resources Role- en RoleBinding-beheer
admin Volledige CRUD inclusief Role/RoleBinding-beheer ClusterRole, ClusterRoleBinding
cluster-admin Onbeperkte toegang Niets

Gebruik view en edit via RoleBindings. Reserveer cluster-admin voor clusteroperators en bind het alleen via ClusterRoleBindings als de operator echt clusterbreed toegang nodig heeft.

Service accounts voor workloads

Elke pod draait als een service account. Als je geen serviceAccountName opgeeft in je pod-spec, draait de pod als default in zijn namespace. Het default service account heeft geen bruikbare RBAC-permissies, maar het token wordt toch gemount in de pod.

Tokenlevenscyclus (Kubernetes 1.22+)

Sinds Kubernetes 1.22 ontvangen pods een kortlevend, automatisch roterend projected token via de TokenRequest API. Dit vervangt het oudere mechanisme (voor 1.24) waarbij een statisch, niet-verlopen Secret-gebaseerd token automatisch werd aangemaakt voor elk service account.

Sinds Kubernetes 1.24 maakt Kubernetes geen langlevende token-Secrets meer automatisch aan. Als je pipeline of tooling nog afhankelijk is van een permanent token in een Secret, moet je dat expliciet aanmaken, en je zou weg moeten migreren van dat patroon.

Best practices

  1. Maak een dedicated service account per applicatie. Niet per team. Niet per namespace. Per applicatie.
  2. Zet automountServiceAccountToken: false op service accounts die geen API-toegang nodig hebben. Een aanvaller die kan execen in een pod kan het token lezen van /var/run/secrets/kubernetes.io/serviceaccount/token. De mount uitschakelen verwijdert dat aanvalspad.
apiVersion: v1
kind: ServiceAccount
metadata:
  name: frontend
  namespace: production
automountServiceAccountToken: false
  1. Scope Roles naar precies de resources en verbs die de applicatie aanroept. Gebruik audit2rbac om een minimaal RBAC-beleid te genereren uit Kubernetes audit-logs. Draai de workload met audit-logging aan, voer de logs dan door audit2rbac om te zien wat de applicatie daadwerkelijk aanvroeg.
  2. Cross-namespace-toegang is mogelijk. Een RoleBinding in namespace B kan een service account uit namespace A toegang geven tot resources in namespace B. Dit is bewust zo ontworpen, maar het betekent wel dat je bindings over namespaces heen moet auditen, niet alleen erbinnen.

Aggregated ClusterRoles

Een aggregated ClusterRole gebruikt aggregationRule.clusterRoleSelectors om automatisch permissies samen te stellen uit andere ClusterRoles die aan een label-selector voldoen. Het control plane voegt die regels samen in het aggregaat. Regels die je direct schrijft in een aggregated ClusterRole worden overschreven.

Zo zijn de ingebouwde view, edit en admin ClusterRoles uitbreidbaar. Wanneer je een CRD-operator installeert (Prometheus, cert-manager, Argo), kan de operator een ClusterRole meesturen met het label rbac.authorization.k8s.io/aggregate-to-view: "true", en de ingebouwde view role neemt die permissies automatisch over.

# Deze ClusterRole aggregeert permissies van ClusterRoles met het label
# rbac.mycompany.com/aggregate-to-monitoring: "true"
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: monitoring-aggregate
aggregationRule:
  clusterRoleSelectors:
  - matchLabels:
      rbac.mycompany.com/aggregate-to-monitoring: "true"
rules: []  # control plane vult dit in; niet handmatig bewerken
---
# Een component-ClusterRole die in het aggregaat vloeit
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: monitoring-pods
  labels:
    rbac.mycompany.com/aggregate-to-monitoring: "true"
rules:
- apiGroups: [""]
  resources: ["pods", "services", "endpoints"]
  verbs: ["get", "list", "watch"]

Gebruik aggregated ClusterRoles wanneer meerdere teams of operators permissies moeten bijdragen aan een gedeelde role zonder elkaars manifests te bewerken.

Debuggen met kubectl auth can-i

Wanneer een pod of user Error from server (Forbidden) krijgt, vertelt de foutmelding precies wat er misgaat. Ontleed het:

pods is forbidden: User "system:serviceaccount:default:myapp"
  cannot list resource "pods" in API group "" in the namespace "production"

Dat geeft je: wie (system:serviceaccount:default:myapp), wat (list pods), waar (namespace production), API-groep (core).

Stap 1: controleer wat de identiteit mag

# Controleer permissies voor een specifiek service account in een specifieke namespace
kubectl auth can-i --list \
  --as=system:serviceaccount:default:myapp \
  -n production

Om een enkele actie te controleren:

kubectl auth can-i list pods \
  --as=system:serviceaccount:default:myapp \
  -n production
# Verwacht: "no" (bevestigt de Forbidden-fout)

Als kubectl auth can-i --as een fout geeft over impersonation, heeft de user die de check uitvoert geen impersonate-permissie. Je hebt cluster-admin nodig, of een role met impersonate op users en service accounts, om op deze manier te debuggen.

Stap 2: bestaande bindings vinden

# Vind alle RoleBindings en ClusterRoleBindings die naar dit service account verwijzen
kubectl get rolebindings,clusterrolebindings -A -o json | \
  jq '.items[] | select(.subjects[]? | .name=="myapp" and .namespace=="default") | {kind, name: .metadata.name, namespace: .metadata.namespace, roleRef: .roleRef}'

Stap 3: inspecteer de role

kubectl describe role <role-name> -n <namespace>
# of voor cluster-scoped:
kubectl describe clusterrole <clusterrole-name>

Controleer of de role de juiste combinatie van API-groep, resource en verb bevat. Een veelvoorkomende oorzaak: de binding bestaat maar verwijst naar een role die de verb of resource die de workload nodig heeft niet bevat.

Stap 4: pas de fix toe en verifieer

Nadat je de Role en RoleBinding hebt aangemaakt of bijgewerkt, verifieer:

kubectl auth can-i list pods \
  --as=system:serviceaccount:default:myapp \
  -n production
# Verwacht: "yes"

Gebruik kubectl auth reconcile -f rbac.yaml in plaats van kubectl apply voor RBAC-manifests. Reconcile is de aanbevolen methode omdat het voorkomt dat bindings die niet in je manifest staan per ongeluk worden verwijderd.

Least-privilege patronen voor CI/CD

Patroon 1: namespace-scoped deployer

Geef je CI/CD-pipeline een service account dat alleen workloads in de doelnamespace mag wijzigen:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: ci-deployer
  namespace: production
automountServiceAccountToken: false
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: deployer
  namespace: production
rules:
- apiGroups: ["apps"]
  resources: ["deployments", "statefulsets", "daemonsets"]
  verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: [""]
  resources: ["services", "configmaps"]
  verbs: ["get", "list", "watch", "update", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: ci-deployer-binding
  namespace: production
subjects:
- kind: ServiceAccount
  name: ci-deployer
  namespace: production
roleRef:
  kind: Role
  name: deployer
  apiGroup: rbac.authorization.k8s.io

Deze role kan geen services aanmaken of verwijderen (alleen updaten), kan niet bij Secrets of Roles, en heeft geen toegang buiten production.

Patroon 2: kortlevende tokens per pipeline-run

Sla geen langlevend service-account-token op in je CI-secrets. Sinds Kubernetes 1.24 genereer je een vers token per run:

# Genereer een token dat na 1 uur verloopt (Kubernetes 1.24+)
TOKEN=$(kubectl create token ci-deployer -n production --duration=3600s)
# Gebruik $TOKEN alleen voor deze pipeline-run; sla het niet op

Patroon 3: alleen-lezen audittoegang met ingebouwde ClusterRole

Voor beveiligingsauditors of monitoringtools die leestoegang nodig hebben in een specifieke namespace, bind de ingebouwde view ClusterRole via een RoleBinding:

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: auditor-view
  namespace: production
subjects:
- kind: User
  name: auditor@mijnbedrijf.nl
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole   # hergebruikt de ingebouwde view-role
  name: view
  apiGroup: rbac.authorization.k8s.io

De view ClusterRole sluit expliciet Secrets uit, dus de auditor kan workloadconfiguratie inspecteren zonder toegang tot secretdata.

Veelgemaakte fouten en beveiligingsrisicos

system:masters is niet hetzelfde als cluster-admin. De cluster-admin ClusterRole die via ClusterRoleBinding is toegekend, kun je intrekken door de binding te verwijderen. De system:masters group is hardcoded om alle RBAC-checks en alle autorisatiewebhooks te omzeilen. Een user in system:masters kan nergens worden geweigerd. OPA/Gatekeeper- en Kyverno-policies gelden niet voor ze. Voeg productie-identiteiten nooit toe aan deze groep.

Wildcard-permissies geven toegang tot toekomstige CRDs. Een regel met resources: ["*"] dekt niet alleen huidige resources. Het dekt elke CRD die iemand in de toekomst installeert. De RBAC good practices-documentatie raadt aan resources expliciet op te sommen.

list en watch op Secrets lekt hun inhoud. kubectl get secrets -A -o yaml uitvoeren met alleen list-permissie geeft alle secretdata in plaintext terug. De watch-verb streamt ook volledige secretdata. Dit is gedocumenteerd als een HIGH-severity risico.

Pod-aanmaakrechten zijn breder dan ze lijken. De mogelijkheid om pods aan te maken in een namespace geeft impliciet toegang tot elke Secret, ConfigMap en ServiceAccount in die namespace (door ze te mounten of serviceAccountName in te stellen). Combineer RBAC met Pod Security Standards om te beperken wat een pod daadwerkelijk kan doen tijdens runtime.

RBAC isoleert namespaces niet op netwerkniveau. Standaard kan elke pod in een cluster communiceren met elke andere pod in alle namespaces. RBAC regelt API-toegang; network policies regelen verkeer. Je hebt beide nodig.

Wanneer escaleren

Als je de debugstappen hebt doorlopen en de permissie werkt nog steeds niet zoals verwacht, verzamel dan het volgende voordat je om hulp vraagt:

  • De exacte foutmelding (volledige tekst, geen samenvatting)
  • Output van kubectl auth can-i --list --as=<identiteit> -n <namespace>
  • Output van kubectl get rolebindings,clusterrolebindings -A -o json gefilterd op de identiteit
  • De YAML van de Role of ClusterRole die je verwacht te gebruiken
  • Kubernetes-versie (kubectl version --short)
  • Of het cluster custom admission webhooks of autorisatiewebhooks gebruikt (Rancher, OpenShift en vergelijkbare platforms voegen hun eigen autorisatielagen bovenop RBAC toe)
  • Of de identiteit een user (externe auth) of een service account (in-cluster) is

Met deze informatie kan iemand vaststellen of het probleem in het RBAC-beleid zit, in de binding, in een webhook-override of in de authenticatielaag.

Herhaling voorkomen

  • Pas RBAC-manifests toe met kubectl auth reconcile, niet kubectl apply. Reconcile is idempotent en voorkomt onbedoelde verwijderingen.
  • Gebruik een service account per applicatie. Als alle apps in een namespace het default service account delen, verandert het aanpassen van permissies voor een app ze voor allemaal.
  • Audit bindings regelmatig. Draai kubectl get clusterrolebindings -o json | jq '.items[] | select(.roleRef.name=="cluster-admin") | .subjects' om elke identiteit met cluster-admin te vinden. Verwijder alles wat er niet hoort.
  • Zet automountServiceAccountToken: false op elk service account dat geen API-toegang nodig heeft.
  • Genereer kortlevende tokens voor CI/CD in plaats van statische credentials op te slaan.

Terugkerende server- of deploymentproblemen?

Ik help teams productie betrouwbaar maken met CI/CD, Kubernetes en cloud—zodat fixes blijven en deploys geen stress meer zijn.

Bekijk DevOps consultancy

Doorzoek deze site

Begin met typen om te zoeken, of blader door de kennisbank en blog.