English readers: this is a German-language post on Kubernetes RBAC least-privilege patterns — Role vs. ClusterRole, right-sizing ServiceAccount bindings for CI pipelines, and where RBAC's authority ends. Originally published at muellermh.blog.
API-Zugriff statt Workload-Verhalten
Admission Control entscheidet, was beim Erstellen eines Pods erlaubt ist. Runtime Security erkennt verdächtiges Verhalten, während ein Container läuft. NetworkPolicies begrenzen, mit wem ein Pod im Netzwerk sprechen darf. RBAC (Role-Based Access Control) setzt eine Ebene davor an: Es regelt, wer überhaupt mit der Kubernetes-API sprechen darf und was er dort tun darf — Pods erstellen, Secrets lesen, Nodes löschen. Das ist auch die Abgrenzung zum Artikel über Kubernetes-Secrets und base64: Dort ging es darum, dass base64 keine Verschlüsselung ist. Hier geht es darum, wer über die API überhaupt an das Secret-Objekt herankommt. Zwei verschiedene Kontrollen, zwei verschiedene Angriffsflächen.
Das eigentliche Risiko: cluster-admin für ServiceAccounts und CI-Pipelines
Eine RBAC-Fehlkonfiguration ist keine exotische Rechteausweitung, sondern eine banale Bequemlichkeitsentscheidung: Eine CI-Pipeline soll deployen, verschiedene Ressourcen in verschiedenen Namespaces anfassen, und irgendwann bindet jemand den ServiceAccount an cluster-admin, um endlich Ruhe vor Rechte-Fehlern zu haben.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: ci-pipeline-admin
subjects:
- kind: ServiceAccount
name: ci-deployer
namespace: ci
roleRef:
kind: ClusterRole
name: cluster-admin
apiGroup: rbac.authorization.k8s.io
Das Token dieses ServiceAccounts liegt danach in jedem Pipeline-Lauf — als Klartext-Umgebungsvariable oder als gemountetes Secret. Wer dieses Token in die Hände bekommt — über eine kompromittierte Dependency, einen geleakten Log, eine falsch konfigurierte Artifact-Ablage — hat nicht Zugriff auf die Deploy-Funktion der Pipeline, sondern auf den gesamten Cluster: alle Secrets in allen Namespaces lesen, beliebige privilegierte Pods erstellen und darüber auf den Node ausbrechen, RBAC-Objekte selbst verändern. Das ist kein theoretisches Risiko, das nur in Threat-Modeling-Workshops existiert — es ist die direkte Konsequenz einer Zeile YAML, die in dieser Form leicht entsteht.
Role vs. ClusterRole: RBAC nach dem Least-Privilege-Prinzip zuschneiden
Der Gegenentwurf ist Scoping auf das, was die Pipeline tatsächlich braucht. Eine Role gilt nur innerhalb eines Namespace:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: deployer
namespace: app
rules:
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "watch", "update", "patch"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
Gebunden wird sie über eine RoleBinding, ebenfalls im selben Namespace:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: ci-deployer-binding
namespace: app
subjects:
- kind: ServiceAccount
name: ci-deployer
namespace: ci
roleRef:
kind: Role
name: deployer
apiGroup: rbac.authorization.k8s.io
Damit kann der ServiceAccount ci-deployer Deployments im Namespace app aktualisieren — und sonst nichts. Kein Zugriff auf billing, kein Zugriff auf Secrets, kein Zugriff auf Cluster-weite Ressourcen wie Nodes oder Namespaces selbst.
Für genau solche Cluster-weiten Ressourcen — oder wenn dieselbe Rolle über viele Namespaces hinweg gebraucht wird — gibt es ClusterRole und ClusterRoleBinding:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: pod-reader-clusterwide
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: monitoring-pod-reader
subjects:
- kind: ServiceAccount
name: monitoring
namespace: monitoring
roleRef:
kind: ClusterRole
name: pod-reader-clusterwide
apiGroup: rbac.authorization.k8s.io
Der Unterschied liegt nicht im Namen, sondern im Scope: Ein RoleBinding kann auch auf eine ClusterRole verweisen (dann gilt sie nur im Namespace der Binding), ein ClusterRoleBinding dagegen wirkt immer clusterweit. cluster-admin ist selbst nur eine besonders weit gefasste ClusterRole — gebunden über ClusterRoleBinding, wirkt sie überall.
Rechte messen statt schätzen
Bevor du eine Rolle zuschneidest, lohnt sich der Blick auf das, was ein Subjekt aktuell tatsächlich darf. kubectl auth can-i --list mit --as simuliert einen anderen Identitätsträger, ohne dass du dessen Token brauchst:
kubectl auth can-i --list \
--as=system:serviceaccount:ci:ci-deployer -n app
# gezielter Einzel-Check auf dieselbe Identität
kubectl auth can-i delete secrets \
--as=system:serviceaccount:ci:ci-deployer -n app
Das beantwortet "was darf dieses Subjekt". Die umgekehrte Frage — "wer darf diese Aktion" — beantworten kubectl auth can-i allein nicht, dafür aber Zusatzwerkzeuge wie rbac-lookup (Fairwinds) oder kubectl-who-can (Aqua Security): Beide durchsuchen alle Rollen und Bindings im Cluster und listen auf, welche Subjekte ein bestimmtes Verb auf eine bestimmte Ressource anwenden dürfen. Gerade bei gewachsenen Clustern mit vielen ServiceAccounts ist das der einzige praktikable Weg, cluster-admin-Bindings überhaupt zu finden, statt sie zu erraten.
Aggregated ClusterRoles: erweiterbar ohne Rechteausweitung
Wenn ein Controller oder ein CRD eigene Ressourcen mitbringt, die in bestehende Standardrollen wie view oder edit einsortiert werden sollen, muss dafür nicht die eingebaute ClusterRole editiert werden. Kubernetes' Standardrollen selbst nutzen dafür Aggregation über Label-Selektoren:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: custom-metrics-reader
labels:
rbac.authorization.k8s.io/aggregate-to-view: "true"
rules:
- apiGroups: ["metrics.k8s.io"]
resources: ["pods"]
verbs: ["get", "list"]
Diese ClusterRole wird über das Label automatisch in die eingebaute view-Rolle eingemischt, sobald sie existiert. Der Vorteil: Erweiterbarkeit ohne die Basis-Rolle direkt zu verändern und ohne das Risiko, bei einem Merge versehentlich mehr Rechte einzuführen als beabsichtigt.
Wo RBAC aufhört
RBAC regelt, wer über die API worauf zugreifen darf — es sagt nichts darüber aus, was innerhalb eines Pods mit bereits legitim erteilten Rechten passiert. Ein Pod, dessen ServiceAccount berechtigt ist, Secrets im eigenen Namespace zu lesen, kann diese Rechte über eine Remote-Code-Execution-Schwachstelle in der Anwendung genauso nutzen wie über den vorgesehenen Code-Pfad. RBAC hat in diesem Fall exakt das durchgesetzt, was in der Rolle stand — das Problem liegt dann entweder in einer zu weit gefassten Rolle (ein Least-Privilege-Problem, das sich mit den Mitteln aus diesem Artikel beheben lässt) oder darin, dass der Angriff einen legitim erlaubten Zugriff über einen Code-Pfad ausnutzt, den keine API-Autorisierung einschränken kann. Genau hier ergänzen sich die anderen Bausteine dieser Serie: Admission Control verhindert von vornherein, dass Pods mit unnötig weiten Rechten oder Privilegien überhaupt starten, Runtime Security erkennt, wenn ein Pod sich anders verhält als erwartet. RBAC ist die Grundlage, nicht die vollständige Antwort.
Woran es hier weitergeht
Least-Privilege-Reviews an RBAC-Bindings sind kein einmaliges Audit, sondern ein wiederkehrendes Thema in dieser Serie — Rollen wachsen mit jeder neuen Pipeline-Anforderung, und niemand entfernt Rechte von sich aus wieder.
Top comments (0)