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

ClusterRoleWho should use itGrants
cluster-adminBreak-glass onlyFull access to everything
adminNamespace ownersFull namespace control, no RBAC write
editDevelopersCRUD on most resources, no RBAC
viewRead-only usersGET/LIST/WATCH on non-sensitive resources
system:nodekubeletsNode-specific pod/PV operations
system:kube-schedulerschedulerPod/Node read, binding write
system:kube-controller-managercontroller-managerWide 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