TL;DR

RBAC decides whether a subject can perform a verb on a resource at a scope. Subjects are users, groups, or ServiceAccounts. Roles and ClusterRoles contain rules. RoleBindings and ClusterRoleBindings attach subjects to those rules. Use namespace Roles and RoleBindings by default; reserve ClusterRoleBindings and powerful verbs for cases that truly need cluster-wide access.

Mental Model

Every Kubernetes API request is checked by the API server. After authentication proves who the caller is, RBAC asks: does this subject have a binding to a role whose rules allow this verb on this resource in this namespace or cluster scope?

RBAC is additive. There is no explicit deny in standard Kubernetes RBAC. If any bound rule allows the request, the request is allowed. If no rule allows it, the API server returns Forbidden.

SubjectUser / Group / SABindingconnects subjectRole Rulesverbs/resourcesDecisionallow or forbidQuestion: can subject X do verb Y on resource Z at this scope?

Bindings connect identities to permission rules; the API server evaluates the request.

Core Objects

ObjectScopePurposeExample
RoleNamespaceRules for namespaced resources in one namespace.Read Pods in app.
RoleBindingNamespaceBinds users/groups/SAs to a Role or ClusterRole within one namespace.Give sre-readonly access to app.
ClusterRoleClusterRules for cluster resources or reusable namespaced rule sets.Read nodes, namespaces, or reusable app read-only rules.
ClusterRoleBindingClusterBinds a ClusterRole across the whole cluster.Controller needing all namespaces.
ServiceAccountNamespaceIdentity used by Pods and automation.system:serviceaccount:app:cd-bot.

Verbs, Resources, And Subresources

RBAC rules grant verbs on resources. Some actions use subresources: reading logs needs pods/log, exec needs pods/exec, scaling a Deployment can involve deployments/scale.

NeedResourceVerbNote
List PodspodslistOften paired with get and watch.
Read logspods/loggetSeparate from reading Pod specs.
Exec into Podpods/execcreateSensitive; equivalent to app shell access.
Patch DeploymentdeploymentspatchCommon for CI/CD image updates.
Scale Deploymentdeployments/scalepatch or updateNeeded by autoscalers or operators.
Read SecretssecretsgetVery sensitive; grants credential visibility.

kubectl auth can-i

kubectl auth can-i is the fastest RBAC debugger. Use it as yourself, or impersonate a ServiceAccount/user when you have permission to impersonate.

bashrbac-can-i.sh
kubectl auth can-i get pods -n app
kubectl auth can-i get pods/log -n app
kubectl auth can-i patch deployments.apps -n app
kubectl auth can-i list nodes

# Test as a ServiceAccount.
kubectl auth can-i patch deployments.apps -n app \
  --as=system:serviceaccount:app:cd-bot

# Show allowed actions for current identity.
kubectl auth can-i --list -n app

From-Scratch Read-Only Lab

This creates read-only namespace access for a group. The ClusterRole is reusable, but the RoleBinding limits it to namespace app.

yamlreadonly-rbac.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: app
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: app-readonly-template
rules:
  - apiGroups: ["", "apps", "batch"]
    resources:
      - pods
      - pods/log
      - services
      - endpoints
      - configmaps
      - deployments
      - replicasets
      - jobs
      - cronjobs
    verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: app-readonly
  namespace: app
subjects:
  - kind: Group
    name: sre-readonly@example.com
    apiGroup: rbac.authorization.k8s.io
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: app-readonly-template

ServiceAccounts For Workloads

Pods use ServiceAccounts as their Kubernetes API identity. A controller, job, CI/CD runner, or app that talks to the API should use a dedicated ServiceAccount with narrow permissions.

yamlcd-bot-rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: cd-bot
  namespace: app
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: deployment-patcher
  namespace: app
rules:
  - apiGroups: ["apps"]
    resources: ["deployments"]
    verbs: ["get", "list", "watch", "patch"]
  - apiGroups: [""]
    resources: ["pods", "pods/log"]
    verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: cd-bot-deployment-patcher
  namespace: app
subjects:
  - kind: ServiceAccount
    name: cd-bot
    namespace: app
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: deployment-patcher
bashserviceaccount-checks.sh
kubectl get serviceaccount -n app
kubectl get pod <pod> -n app -o jsonpath='{.spec.serviceAccountName}{"\\n"}'
kubectl create token cd-bot -n app --duration=15m

kubectl auth can-i patch deployments.apps -n app \
  --as=system:serviceaccount:app:cd-bot
kubectl auth can-i delete secrets -n app \
  --as=system:serviceaccount:app:cd-bot

ArgoCD And CI/CD Permissions

Deployment automation needs enough permission to manage the objects it owns, but not unlimited cluster-admin access. In client environments, check whether the deployer owns only one namespace, many app namespaces, or cluster-scoped resources like CRDs.

AutomationTypical ScopeNotes
Namespace app deployerRole + RoleBinding in one namespace.Good for app teams and CI jobs.
ArgoCD app controllerOften broader, sometimes cluster-wide.Scope by project/destination and Kubernetes RBAC.
Helm installerDepends on chart contents.Charts installing CRDs or ClusterRoles need elevated permissions.
OperatorUsually ClusterRole + ClusterRoleBinding.Review generated permissions carefully.

Inspection Commands

bashrbac-inspect.sh
NS=<namespace>

kubectl get role,rolebinding,serviceaccount -n "$NS"
kubectl get clusterrole,clusterrolebinding
kubectl describe rolebinding -n "$NS"
kubectl describe clusterrole <clusterrole>
kubectl describe clusterrolebinding <binding>

# Find bindings that mention a subject name.
kubectl get rolebinding,clusterrolebinding -A -o yaml | grep -n "name: <subject-name>"

Symptom To Cause

SymptomLikely CauseCheck First
Forbidden on PodsNo RoleBinding in namespace, wrong subject, missing verbs.kubectl auth can-i get pods -n ns --as=...
Can list Pods but cannot see logsMissing pods/log subresource permission.Role rules for pods/log.
Can update Deployment but cannot scaleMissing deployments/scale permission.Subresource rule.
RoleBinding exists but still deniedBinding subject name/namespace/kind is wrong.Subject block and ServiceAccount namespace.
Works in one namespace, denied in anotherRoleBinding is namespace-scoped.Bindings in target namespace.
Helm install fails on CRDs or ClusterRolesDeployer lacks cluster-scoped permissions.Chart resources and deployer ClusterRole.
Controller cannot watch resourcesMissing list/watch permissions.Controller logs and ClusterRole rules.

Production Patterns

  • 1Prefer least privilege: grant only the verbs and resources the subject needs.
  • 2Avoid cluster-admin: temporary emergency access should be time-bound and audited.
  • 3Separate humans and workloads: use groups for people and ServiceAccounts for Pods/automation.
  • 4Treat Secrets as privileged: read access to Secrets often means access to external systems.
  • 5Protect exec and impersonation: pods/exec and impersonate can be very powerful.

Safe Change Pattern

  1. Write the required action as a sentence: “SA X needs to patch Deployments in namespace Y.”
  2. Translate that into API group, resource, verb, and scope.
  3. Use Role/RoleBinding for namespaced access where possible.
  4. Validate with kubectl auth can-i --as=... before handing it to automation.
  5. After rollout, check audit logs or controller logs for denied requests and trim unused permissions later.