RBAC Flow
Overview
Traces every step of a kubectl request through authentication, authorization (RBAC), and audit logging — from the TLS handshake to the API server returning a response.
RBAC Architecture
Every API request passes through:
1. Authentication — WHO is making the request?
2. Authorization — are they ALLOWED to do this?
3. Admission — should the request be mutated/validated?
4. etcd — persist the object
RBAC is the default authorization mode.
Multiple authorizers can run in sequence (RBAC + Node + Webhook).
API Server
│
┌─────────▼──────────┐
│ Authentication │
│ ┌──────────────┐ │
│ │ x509 client │ │ ← kubectl (kubeconfig cert)
│ │ Bearer token │ │ ← ServiceAccount JWT
│ │ OIDC token │ │ ← SSO / IRSA
│ │ Webhook │ │ ← external authn
│ └──────────────┘ │
└─────────┬──────────┘
│ UserInfo{username, groups}
┌─────────▼──────────┐
│ Authorization │
│ RBAC authorizer │
│ Node authorizer │
│ Webhook authorizer│
└─────────┬──────────┘
│ Allow / Deny
┌─────────▼──────────┐
│ Admission Chain │
└─────────┬──────────┘
│
etcd
Full RBAC Authorization Sequence
kubectl API Server RBAC Authorizer etcd Audit Log
│ │ │ │ │
│─GET pods────────►│ │ │ │
│ (Bearer token │ │ │ │
│ or cert) │ │ │ │
│ │ │ │ │
│ ┌──── Authentication ────┐ │ │ │
│ │ Validate token/cert │ │ │ │
│ │ Extract: │ │ │ │
│ │ username: alice │ │ │ │
│ │ groups: │ │ │ │
│ │ [dev-team, │ │ │ │
│ │ system:authenticated] │ │ │
│ └────────────────────────┘ │ │ │
│ │ │ │ │
│ │──Authorize?───────► │ │
│ │ verb: get │ │ │
│ │ resource: pods │ │ │
│ │ namespace: prod │ │ │
│ │ user: alice │ │ │
│ │ groups: [dev-team] │ │
│ │ │ │ │
│ │ ┌───── RBAC evaluation ──────────────┐ │
│ │ │ │ │
│ │ │ 1. Find all RoleBindings / │ │
│ │ │ ClusterRoleBindings that │ │
│ │ │ match subject alice or │ │
│ │ │ group dev-team │ │
│ │ │ │ │
│ │ │ 2. For each binding, look up │ │
│ │ │ the referenced Role / │ │
│ │ │ ClusterRole │ │
│ │ │ │ │
│ │ │ 3. Check: does any rule in any │ │
│ │ │ referenced role allow: │ │
│ │ │ verbs=[get], resources=[pods] │ │
│ │ │ namespace=production? │ │
│ │ │ │ │
│ │ │ 4. First Allow wins (no Deny) │ │
│ │ │ │ │
│ │ └────────────────────────────────────┘ │
│ │◄── Allow ─────────│ │ │
│ │ │ │ │
│ │──GET pods ───────────────────────►│ │
│ │◄── pod list ──────────────────────│ │
│◄── 200 pod list──│ │ │ │
│ │ │ │ │
│ │──────────────────────────────────────────────────►
│ │ audit: {user:alice, verb:get, │ │
│ │ resource:pods, ns:production, │ │
│ │ response:200} │ │
RBAC Object Model
ClusterRole / Role
└── Rules: [{apiGroups, resources, verbs}]
ClusterRoleBinding / RoleBinding
└── subjects: [{kind, name, namespace}]
└── roleRef: {kind, name}
Scope:
ClusterRole — cluster-wide or used across namespaces
Role — single namespace only
ClusterRoleBinding — grants ClusterRole cluster-wide
RoleBinding — grants Role or ClusterRole in ONE namespace
RBAC Rules — Verb and Resource Reference
# Role — read-only access to pods and logs in one namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-reader
namespace: production
rules:
- apiGroups: [""] # "" = core API group
resources: ["pods"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["pods/log"] # subresource — separate rule needed
verbs: ["get"]
- apiGroups: ["apps"]
resources: ["deployments", "replicasets"]
verbs: ["get", "list", "watch"]
---
# ClusterRole — read metrics across all namespaces
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: metrics-reader
rules:
- apiGroups: ["metrics.k8s.io"]
resources: ["pods", "nodes"]
verbs: ["get", "list"]
- nonResourceURLs: ["/metrics", "/healthz"]
verbs: ["get"]
---
# RoleBinding — bind pod-reader to alice in production
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: alice-pod-reader
namespace: production
subjects:
- kind: User
name: alice # matches x509 CN or OIDC sub
apiGroup: rbac.authorization.k8s.io
- kind: Group
name: dev-team # matches x509 O or OIDC group claim
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
---
# ClusterRoleBinding — grant admin to ops-team cluster-wide
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: ops-team-admin
subjects:
- kind: Group
name: ops-team
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: cluster-admin
apiGroup: rbac.authorization.k8s.io
ServiceAccount RBAC Flow
Pod running with serviceAccountName: payments-api
│
│ Mounted: /var/run/secrets/kubernetes.io/serviceaccount/token
│ (projected ServiceAccount token, audience=kubernetes)
│
▼
API request from pod:
Authorization: Bearer <JWT>
│
▼
API Server authentication:
Validates JWT signature against cluster CA
Extracts:
username: system:serviceaccount:production:payments-api
groups: [system:serviceaccounts, system:serviceaccounts:production]
│
▼
RBAC check:
Looks for RoleBinding / ClusterRoleBinding where:
subject.kind = ServiceAccount
subject.name = payments-api
subject.namespace = production
# Give payments-api SA read access to ConfigMaps/Secrets it needs
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: payments-api-config-reader
namespace: production
rules:
- apiGroups: [""]
resources: ["configmaps"]
resourceNames: ["payments-config", "feature-flags"] # scoped to specific objects
verbs: ["get", "watch"]
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["payments-db-creds"]
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: payments-api-config-reader
namespace: production
subjects:
- kind: ServiceAccount
name: payments-api
namespace: production
roleRef:
kind: Role
name: payments-api-config-reader
apiGroup: rbac.authorization.k8s.io
Built-in ClusterRoles
| ClusterRole | Who should use it | Grants |
|---|---|---|
cluster-admin | Break-glass only | Full access to everything |
admin | Namespace owners | Full namespace control, no RBAC write |
edit | Developers | CRUD on most resources, no RBAC |
view | Read-only users | GET/LIST/WATCH on non-sensitive resources |
system:node | kubelets | Node-specific pod/PV operations |
system:kube-scheduler | scheduler | Pod/Node read, binding write |
system:kube-controller-manager | controller-manager | Wide read+write for reconciliation |
Audit Logging
# Audit policy — /etc/kubernetes/audit-policy.yaml (kube-apiserver flag)
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
# Log all changes to RBAC objects (Metadata + RequestResponse)
- level: RequestResponse
resources:
- group: rbac.authorization.k8s.io
resources: ["roles", "rolebindings", "clusterroles", "clusterrolebindings"]
# Log secret reads (who is reading secrets)
- level: Metadata
verbs: ["get", "list", "watch"]
resources:
- group: ""
resources: ["secrets"]
# Log exec/attach/portforward (high-risk subresources)
- level: RequestResponse
resources:
- group: ""
resources: ["pods/exec", "pods/attach", "pods/portforward"]
# Skip health check noise
- level: None
users: ["system:kube-proxy"]
verbs: ["watch"]
resources:
- group: ""
resources: ["endpoints", "services"]
# Default — metadata only for everything else
- level: Metadata
Audit log entry (JSON):
{
"kind": "Event",
"apiVersion": "audit.k8s.io/v1",
"level": "Metadata",
"auditID": "...",
"stage": "ResponseComplete",
"requestURI": "/api/v1/namespaces/production/secrets/payments-db-creds",
"verb": "get",
"user": {
"username": "alice",
"groups": ["dev-team", "system:authenticated"]
},
"sourceIPs": ["10.0.0.15"],
"responseStatus": {"code": 200},
"requestReceivedTimestamp": "...",
"annotations": {
"authorization.k8s.io/decision": "allow",
"authorization.k8s.io/reason": "RBAC: allowed by RoleBinding \"alice-pod-reader\" of Role \"pod-reader\" to User \"alice\""
}
}
RBAC Debugging
# Check if alice can get pods in production
kubectl auth can-i get pods --namespace production --as alice
# Check via group
kubectl auth can-i get pods --namespace production \
--as alice --as-group dev-team
# Check ServiceAccount permissions
kubectl auth can-i get secrets \
--namespace production \
--as system:serviceaccount:production:payments-api
# List all permissions for a subject (requires kubectl 1.26+)
kubectl auth whoami # shows your current identity
# Find all RoleBindings for a user
kubectl get rolebindings,clusterrolebindings -A -o json | \
jq '.items[] | select(.subjects[]?.name=="alice") | {kind:.kind, name:.metadata.name, ns:.metadata.namespace}'
# Show what a ClusterRole actually grants
kubectl describe clusterrole edit | grep -A3 "pods"
# Audit: who accessed secrets recently
# (requires audit log access)
grep '"secrets"' /var/log/kubernetes/audit.log | \
jq 'select(.verb=="get") | {user:.user.username, time:.requestReceivedTimestamp}'
# Check RBAC for operator / controller SAs
kubectl get clusterrolebindings -o json | \
jq '.items[] | select(.subjects[]?.namespace=="kube-system") | .metadata.name'
Common RBAC Mistakes
Mistake: granting cluster-admin to service accounts
Fix: use least-privilege Roles with resourceNames where possible
Mistake: binding to User "system:anonymous"
Fix: disable anonymous auth (--anonymous-auth=false on API server)
Mistake: RoleBinding referencing ClusterRole grants cluster-wide access
Reality: RoleBinding + ClusterRole = access scoped to the binding's NAMESPACE only
For cluster-wide: use ClusterRoleBinding
Mistake: wildcard verbs on sensitive resources
Bad: verbs: ["*"] resources: ["secrets"]
Good: verbs: ["get"] resources: ["secrets"] resourceNames: ["specific-secret"]
Mistake: forgetting pods/exec is a separate subresource
If you want to block kubectl exec, you must explicitly deny pods/exec
Related
- 03 — Admission Flow — RBAC check runs before admission
- 02 — RBAC — RBAC design reference
- 02 — Service Account Token — how SAs authenticate