Kubernetes RBAC
Four object types and one rule: permissions are **purely additive**, there is no deny. If someone can do something they should not, you remove a binding — you cannot add a rule to stop them.
What is it?#
RBAC answers the authorization question — the second gate in the API server, after authentication has established who you are.
What it is made of#
Four objects, and the only real complexity is the two-by-two grid.
| Object | Defines | Scope |
|---|---|---|
| Role | What may be done | One namespace |
| ClusterRole | What may be done | Cluster-wide, or cluster-scoped resources |
| RoleBinding | Who gets a role | One namespace |
| ClusterRoleBinding | Who gets a role | Cluster-wide |
How to use it#
A Role and a binding#
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: production
name: pod-reader
rules:
- apiGroups: [""] # "" is the CORE group: pods, services...
resources: ["pods", "pods/log"] # subresources are separate. This matters.
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
namespace: production
name: devs-read-pods
subjects:
- kind: User
name: alice@example.com # from your identity provider
apiGroup: rbac.authorization.k8s.io
- kind: ServiceAccount
name: ci-deployer
namespace: ci # a SA from ANOTHER namespace is allowed
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io| Field | Gotcha |
|---|---|
apiGroups: [""] | The core group. Pods, Services, ConfigMaps, Secrets, Nodes |
resources | Plural, lowercase: pods not Pod |
pods/log, pods/exec | Subresources are separate permissions |
verbs | get list watch create update patch delete deletecollection |
kind: User | Kubernetes has no user objects — these come from your IdP |
get does not imply list, and list leaks contentsTwo independent traps in the same place.
get and list are separate verbs. A dashboard that "can get pods" but cannot list them shows nothing, because listing is what populates the view. Grant get, list and watch together for anything read-only.
And list returns full objects, not names. Granting list on secrets is granting get on every secret in scope. There is no "list the names only" permission — if they can list, they can read.
| الحقل | الفخ |
|---|---|
apiGroups: [""] | دي المجموعة الأساسية: pods، services، secrets |
resources | جمع وحروف صغيرة: pods مش Pod |
pods/log، pods/exec | الموارد الفرعية صلاحيات منفصلة |
kind: User | كوبرنيتيس مفيهوش يوزرز أصلاً — دول جايين من مزوّد الهوية بتاعك |
والنقطة الأخيرة دي بتصدم الناس: مفيش kubectl create user. كوبرنيتيس مش بيخزّن مستخدمين. هو بس بيثق في شهادة أو توكن أو OIDC، وبياخد الاسم منهم.
get مش بتشمل list، والـ list بتسرّب المحتوىفخين في نفس المكان:
١. get و list فعلين منفصلين تماماً.
فداشبورد «بيقدر يعمل get للبودات» بس مش قادر يعمل list، مش هيعرض أي حاجة — عشان الـ list هي اللي بتملا الشاشة.
فللقراءة، إدي get و list و watch مع بعض دايماً.
٢. والـ list بترجّع الأوبجكتس كاملة، مش الأسماء بس.
يعني إنك تدي list على الـ secrets = إنك إديت get على كل secret في النطاق ده.
ومفيش صلاحية اسمها «اعرض الأسماء بس». لو يقدر يعمل list، يبقى يقدر يقرا. خد بالك من دي وإنت بتكتب أدوار للمراقبة.
ServiceAccounts — identity for pods#
Every pod runs as a ServiceAccount. If you do not name one, it is default.
spec:
serviceAccountName: my-app
automountServiceAccountToken: false # if the app never calls the APIkubectl create serviceaccount my-app -n prod
kubectl create rolebinding my-app-reader \
--role=pod-reader --serviceaccount=prod:my-app -n prodBy default Kubernetes mounts a ServiceAccount token into every pod at /var/run/secrets/kubernetes.io/serviceaccount/token.
Most applications never call the Kubernetes API. For them that token is pure downside: anyone who achieves code execution in the container gets a cluster credential for free, and can start enumerating what it can reach.
Set automountServiceAccountToken: false on the ServiceAccount or the pod. It is a one-line change that removes an entire escalation path.
Testing permissions — the commands that actually answer the question#
# Can I?
kubectl auth can-i create deployments -n prod
# Can SOMEONE ELSE? (needs impersonation rights)
kubectl auth can-i delete pods -n prod --as=alice@example.com
kubectl auth can-i '*' '*' --as=system:serviceaccount:prod:default
# Everything a subject can do — the review command
kubectl auth can-i --list --as=system:serviceaccount:prod:my-app -n prod
# Who has cluster-admin? Run this on your cluster today.
kubectl get clusterrolebindings -o json | jq -r '
.items[] | select(.roleRef.name=="cluster-admin")
| .metadata.name + " -> " + ([.subjects[]?.name] | join(", "))'كل بود بيشتغل بهوية اسمها ServiceAccount. ولو ما حددتش واحدة، بياخد default.
كوبرنيتيس افتراضياً بيركّب توكن في كل بود على المسار: /var/run/secrets/kubernetes.io/serviceaccount/token
وأغلب التطبيقات عمرها ما بتكلّم الـ API بتاع كوبرنيتيس.
فبالنسبالهم التوكن ده خسارة صافية: أي حد ينجح ينفّذ كود في الكونتينر بياخد صلاحية كلاستر ببلاش، ويبدأ يشوف يقدر يوصل لإيه.
automountServiceAccountToken: falseسطر واحد بيقفل مسار تصعيد كامل.
والأوامر اللي بتجاوب فعلاً:
kubectl auth can-i create deployments -n prod # أنا أقدر؟
kubectl auth can-i --list --as=system:serviceaccount:prod:my-app -n prodوالأمر التاني ده أهم أمر في الصفحة — بيعرضلك كل حاجة الهوية دي تقدر تعملها. شغّله على الـ default ServiceAccount في البرودكشن النهاردة وشوف هتتفاجئ ولا لأ.
What goes wrong#
| Symptom | Cause | Fix |
|---|---|---|
403 forbidden and the message names the missing verb | No rule allows it | kubectl auth can-i --list; add a rule |
| Read-only dashboard shows nothing | get without list | Grant get, list, watch |
cannot get resource "pods/log" | Subresource not granted | Add pods/log explicitly |
| Binding exists but has no effect | Wrong namespace, or a typo in roleRef | roleRef is immutable — delete and recreate |
| Works for you, fails in CI | You are admin; the ServiceAccount is not | --as=system:serviceaccount:<ns>:<name> |
attempt to grant extra privileges | You cannot grant what you do not have | Escalation prevention — see below |
cluster-admin on a ServiceAccount is how clusters get ownedThe most common real-world escalation is not exotic. It is:
Audit it now:
kubectl get clusterrolebindings -o wide | grep cluster-adminAnything in that list that is a ServiceAccount deserves a written justification. Most were granted to "make it work" during an incident and never narrowed afterwards.
You cannot create a Role granting permissions you do not hold — otherwise any user with create role would be cluster-admin in two steps.
Error: user "alice" is attempting to grant extra privilegesThis is RBAC working. Legitimate paths: be granted the permission yourself first, or hold the escalate verb on roles (which should be rare and deliberate).
| العَرَض | السبب | الحل |
|---|---|---|
403 والرسالة بتسمّي الفعل الناقص | مفيش قاعدة بتسمح | auth can-i --list وضيف قاعدة |
| داشبورد قراءة مش بيعرض حاجة | get من غير list | إدي التلاتة |
cannot get "pods/log" | المورد الفرعي مش مسموح | ضيف pods/log صريح |
| الربط موجود ومش شغّال | namespace غلط، أو غلطة في roleRef | الـ roleRef ثابت — امسح واعمل من الأول |
| شغّال معاك وفاشل في الـ CI | إنت admin، والـ SA لأ | جرّب بـ --as= |
cluster-admin على ServiceAccount هي إزاي الكلاسترات بتتخترقأشهر تصعيد صلاحيات في الواقع مش معقّد خالص:
راجعها دلوقتي:
kubectl get clusterrolebindings -o wide | grep cluster-adminأي ServiceAccount في اللستة دي محتاجة مبرر مكتوب.
وأغلبهم اتعملوا عشان «نخلي الحاجة تشتغل» وقت أزمة، ومحدش ضيّقهم بعد كده.
Interview corner · الأسئلة اللي بتتسأل#
What is the difference between a Role and a ClusterRole, and when do you combine them?
Role is namespaced; ClusterRole is cluster-wide. The binding determines the effective scope, which is where the useful combination lives.
| Role kind | Binding kind | Effective scope |
|---|---|---|
| Role | RoleBinding | That one namespace |
| ClusterRole | RoleBinding | That one namespace — the pattern you want |
| ClusterRole | ClusterRoleBinding | Every namespace, plus cluster-scoped resources |
| Role | ClusterRoleBinding | Impossible |
Use a ClusterRole with a RoleBinding to define a permission set once and grant it per namespace — that is how the built-in view, edit and admin roles are meant to be used.
You need a ClusterRole with a ClusterRoleBinding for genuinely cluster-scoped resources: nodes, PersistentVolumes, namespaces, CustomResourceDefinitions. Those have no namespace, so a RoleBinding cannot express permission over them.
الـ Role محلي، والـ ClusterRole على مستوى الكلاستر. والـ binding هو اللي بيحدد النطاق الفعلي — وهنا التركيبة المفيدة.
| نوع الدور | نوع الربط | النطاق الفعلي |
|---|---|---|
| Role | RoleBinding | الـ namespace دي بس |
| ClusterRole | RoleBinding | الـ namespace دي بس ← النمط المطلوب |
| ClusterRole | ClusterRoleBinding | كل الـ namespaces + موارد الكلاستر |
| Role | ClusterRoleBinding | مستحيل |
استخدم ClusterRole مع RoleBinding عشان تعرّف الصلاحيات مرة واحدة وتديها لكل namespace — وكده الأدوار الجاهزة view و edit و admin المفروض تتستخدم.
ومحتاج ClusterRoleBinding للموارد اللي مالهاش namespace أصلاً: النودات، والـ PersistentVolumes، والـ namespaces نفسها، والـ CRDs. دي مفيش RoleBinding يقدر يعبّر عن صلاحية عليها.
How would you debug a 403 Forbidden from a pod's application?
The pod acts as its ServiceAccount, so reproduce it as that identity rather than as yourself — that is the whole trick.
# 1. Which ServiceAccount is it actually using?
kubectl get pod <pod> -n prod -o jsonpath='{.spec.serviceAccountName}{"\n"}'
# 2. Ask the API server directly, as that identity
kubectl auth can-i list configmaps \
--as=system:serviceaccount:prod:my-app -n prod
# 3. Everything it CAN do — usually the fastest way to spot the gap
kubectl auth can-i --list --as=system:serviceaccount:prod:my-app -n prod
# 4. Which bindings actually reference it?
kubectl get rolebindings,clusterrolebindings -A -o json | jq -r '
.items[] | select(.subjects[]?.name=="my-app") | .metadata.name'Read the 403 message itself — Kubernetes names the verb, resource, API group and namespace it wanted. That usually identifies the missing rule immediately.
Common causes, in order: wrong namespace in the RoleBinding; a missing subresource like pods/log; get granted without list; the pod using default because serviceAccountName was never set.
Whether you test as the failing identity instead of as yourself. "It works when I run it" is meaningless here — you are cluster-admin and the workload is not. A candidate who reaches for --as= has debugged RBAC before.
البود بيتصرّف بهوية الـ ServiceAccount بتاعته، فاختبر بالهوية دي، مش بهويتك إنت. ودي كل الحكاية.
kubectl get pod <pod> -o jsonpath='{.spec.serviceAccountName}{"\n"}' # ١. بأنهي SA؟
kubectl auth can-i list configmaps --as=system:serviceaccount:prod:my-app -n prod
kubectl auth can-i --list --as=system:serviceaccount:prod:my-app -n prod # ٣. الأنفعواقرا رسالة الـ 403 نفسها — كوبرنيتيس بيسمّي الفعل والمورد والمجموعة والـ namespace اللي كان عايزهم. ودي غالباً بتحدد القاعدة الناقصة فوراً.
والأسباب المعتادة بالترتيب: namespace غلط في الـ RoleBinding، أو مورد فرعي ناقص زي pods/log، أو get من غير list، أو البود شغّال بـ default عشان محدش حدد serviceAccountName.
واللي بيتقاس عليه: إنك تختبر بالهوية الفاشلة مش بهويتك.
«هي شغالة لما أنا بشغّلها» مالهاش أي معنى هنا — إنت cluster-admin والتطبيق لأ. واللي بيمد إيده على --as= ده ظبّط RBAC قبل كده فعلاً.
There is no "deny" rule in RBAC. What are the consequences?
RBAC is purely additive: the API server evaluates every rule bound to the subject, and allows the request if any rule permits it. There is no way to subtract.
Four practical consequences:
- To remove access you remove a binding, not add a rule. So you must be able to find every binding that grants it — which is why broad ClusterRoleBindings are dangerous: they are easy to add and hard to audit.
- A wildcard is unrecoverable except by deletion.
verbs: ["*"]onresources: ["*"]cannot be narrowed by a second rule. - Auditing means enumerating, so
kubectl auth can-i --list --as=...is the only reliable answer to "what can this identity do?" Reading individual Roles will mislead you. - For real deny semantics you need admission control — a validating webhook, OPA Gatekeeper or Kyverno — because that stage can reject. RBAC answers "may you", admission answers "should this specific object be allowed".
The mature framing: RBAC is coarse authorization, and policy engines are where nuanced rules belong ("no container may run as root", "images must come from our registry"). Trying to express those in RBAC is a category error, and recognising that is the senior signal.
الـ RBAC تجميعي بحت: الـ API server بيقيّم كل القواعد المربوطة بالهوية، ولو أي واحدة سمحت، اتسمح. مفيش طرح.
وأربع نتايج عملية:
١. عشان تشيل صلاحية، بتشيل ربط — مش بتضيف قاعدة. يعني لازم تكون قادر تلاقي كل ربط بيديها. وعشان كده الـ ClusterRoleBindings الواسعة خطيرة: سهلة الإضافة، صعبة المراجعة.
٢. الـ wildcard مالهوش رجعة غير بالمسح. verbs: ["*"] على resources: ["*"] مفيش قاعدة تانية تقدر تضيّقها.
٣. المراجعة = تعداد. فـ kubectl auth can-i --list --as=... هو الإجابة الموثوقة الوحيدة لسؤال «الهوية دي تقدر تعمل إيه؟». وقراءة الأدوار واحد واحد هتضللك.
٤. وللمنع الحقيقي إنت محتاج admission control — webhook، أو OPA Gatekeeper، أو Kyverno — عشان المرحلة دي هي اللي بتقدر ترفض.
والصياغة الناضجة: الـ RBAC تصريح خشن، ومحركات السياسات هي مكان القواعد الدقيقة («ممنوع كونتينر يشتغل كـ root»، «الصور لازم تيجي من الـ registry بتاعنا»).
وإنك تحاول تعبّر عن دي في RBAC غلطة في التصنيف نفسه — وإنك تعرف ده هي إشارة السينيورتي في السؤال.
Key takeaways#
- Permissions are additive; there is no deny. Remove bindings, do not add rules.
- A RoleBinding referencing a ClusterRole is the pattern: define once, grant per namespace.
getdoes not implylist, andlistreturns full objects — soliston secrets isgeton all of them.- Subresources are separate:
pods/log,pods/exec,pods/portforward. - Test as the failing identity with
--as=, never as yourself. automountServiceAccountToken: falsefor apps that never call the API.- Audit
cluster-adminbindings — a ServiceAccount with it turns any code execution into cluster compromise. - Nuanced policy belongs in admission control, not RBAC.
- الصلاحيات بتتجمع، ومفيش منع. شيل الربط، متضفش قاعدة.
- RoleBinding بيشاور على ClusterRole هو النمط: عرّف مرة، وادي لكل namespace.
getمش بتشملlist، والـlistبترجّع الأوبجكت كامل — فـlistعلى الـ secrets =getعليهم كلهم.- الموارد الفرعية منفصلة:
pods/logوpods/exec. - اختبر بالهوية الفاشلة بـ
--as=، مش بهويتك إنت. automountServiceAccountToken: falseلأي تطبيق مش بيكلّم الـ API.- راجع ربط
cluster-admin— ServiceAccount معاها بتحوّل أي تنفيذ كود لاختراق كامل للكلاستر. - السياسات الدقيقة مكانها admission control، مش RBAC.
Last updated 2026-09-13