Skip to main content
RunBook Academy

KubernetesIII · Kubernetes APIKubernetes API

Authorisation — RBAC, the Node authoriser, and webhook delegation

Intermediate⏱ ~18 minkubectl

What you'll learn

  • Distinguish Role, ClusterRole, RoleBinding, ClusterRoleBinding and when each applies
  • Trace a request through the authoriser chain and identify which authoriser allowed or denied it
  • Identify common RBAC mistakes that lead to over-permissioned access
  • Use `kubectl auth can-i` and audit logs to validate permissions

Prerequisites

Verified against Kubernetes 1.34.x · kubeadm 1.34.x · kubectl 1.34.x · etcd 3.6.x · CoreDNS 1.11.x · containerd 1.7.x / 2.x · 2026-08-16

Not yet marked complete on this device.

Once the API server has authenticated a request, it must decide whether the identity is allowed to perform the action. Authorisation is that decision. In production, RBAC is the default; this lesson covers how RBAC works, how it composes with the Node authoriser and webhook authorisers, and the operational patterns that arise from RBAC mistakes.

The authoriser chain

The API server runs authorisers in order; the first to explicitly allow or deny wins. If no authoriser allows, the request is denied by default.

flowchart LR
    R[Request] --> N[Node<br/>kubelet only]
    N -->|deny| RB[RBAC<br/>default]
    RB -->|deny| W[Webhook<br/>OPA/Cerbos]
    W -->|deny| AB[ABAC<br/>legacy]
    AB -->|deny| AA[AlwaysDeny<br/>default]

The chain is configured by --authorization-mode. Production:

--authorization-mode=Node,RBAC

With this mode, kubelet requests are handled by the Node authoriser (fast, special-case), and everything else goes through RBAC. Webhook is added when an external policy engine makes decisions.

Role and ClusterRole

A Role grants permissions within a single namespace; a ClusterRole grants permissions cluster-wide.

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: team-a-prod
  name: developer
rules:
- apiGroups: [""]
  resources: ["pods", "pods/log", "services", "configmaps"]
  verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
  resources: ["deployments"]
  verbs: ["get", "list", "watch", "update", "patch"]
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: cluster-reader
rules:
- apiGroups: [""]
  resources: ["nodes", "namespaces"]
  verbs: ["get", "list", "watch"]

The rules field is the same for both. The difference is the binding scope (RoleBinding vs ClusterRoleBinding).

A rule’s fields:

  • apiGroups — the API group ("" for core, e.g., apps, batch)
  • resources — the resource kind (pods, services, deployments, secrets, …)
  • verbs — the action (get, list, watch, create, update, patch, delete, deletecollection)
  • resourceNames (optional) — limit to specific objects
  • nonResourceURLs (ClusterRole only) — for /healthz, /metrics, etc.

RoleBinding and ClusterRoleBinding

A RoleBinding grants a Role (or ClusterRole) to identities within a single namespace; a ClusterRoleBinding grants a ClusterRole cluster-wide.

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  namespace: team-a-prod
  name: developers
subjects:
- kind: User
  name: alice@example.com
  apiGroup: rbac.authorization.k8s.io
- kind: Group
  name: developers
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: developer
  apiGroup: rbac.authorization.k8s.io

Subjects:

  • User — an authenticated identity (typically an OIDC user)
  • Group — a collection of users (typically an OIDC group)
  • ServiceAccount — a workload identity
# ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: monitoring
subjects:
- kind: ServiceAccount
  name: prometheus
  namespace: monitoring
roleRef:
  kind: ClusterRole
  name: cluster-reader
  apiGroup: rbac.authorization.k8s.io

A RoleBinding can reference a ClusterRole — but only the permissions of that ClusterRole that apply within the binding’s namespace. This is how view, edit, admin are implemented (they are ClusterRoles; bound per namespace).

Aggregated ClusterRoles

Some ClusterRoles have aggregationRule:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: monitoring
aggregationRule:
  clusterRoleSelectors:
  - matchLabels:
      rbac.authorization.k8s.io/aggregate-to-monitoring: "true"
rules: []  # aggregated from matching ClusterRoles

Other ClusterRoles label themselves with rbac.authorization.k8s.io/aggregate-to-monitoring: "true", and the monitoring ClusterRole’s rules become the union. This is how view, edit, admin work in newer clusters: they are aggregates of per-resource ClusterRoles.

The Node authoriser

The Node authoriser is a special-case authoriser that runs first (when --authorization-mode=Node,RBAC). It allows kubelets to perform a narrow set of operations on their own node only:

  • Read Pods assigned to their node
  • Write Pod status for Pods on their node
  • Read Node status
  • Write Node status (including lease renewal)

A kubelet cannot read Pods on other nodes or write to its own Node object’s metadata. The Node authoriser’s permission is implicit; it does not require an RBAC binding.

Webhook authoriser

Delegates authorisation to an external service. The webhook returns allowed: true or false based on its own policy (OPA, Cerbos, custom logic).

kind: AuthenticationConfiguration  # similar structure for auth-config
# For webhook authoriser:
apiVersion: apiserver.config.k8s.io/v1beta1
kind: AuthorizationConfiguration
authorizers:
- name: Webhook
  webhook:
    authorizedTTL: 5m
    unauthorizedTTL: 30s
    kubeConfigFile: /etc/kubernetes/authz-webhook-kubeconfig
    # ...

Webhook authoriser is used for:

  • Centralised policy (OPA/Gatekeeper, Cerbos)
  • Time-bound access (e.g., “developers can delete Pods only during business hours”)
  • Compliance-mandated controls

A failing webhook (timeout, connection error) is treated as deny unless --authorization-webhook-failure-policy=Allow (rarely safe).

How to test permissions

# Substitute your own values before running:
VERB=delete
RESOURCE=pods
NS=team-a-prod
AS_USER=alice@example.com
AS_GROUP=developers

kubectl auth can-i "$VERB" "$RESOURCE" -n "$NS"
kubectl auth can-i "$VERB" "$RESOURCE" --all-namespaces
kubectl auth can-i "$VERB" "$RESOURCE" --as="$AS_USER" --as-group="$AS_GROUP"

The --as and --as-group flags impersonate an identity. Useful for testing:

# What can the developer group do in team-a-prod?
kubectl auth can-i delete pods -n team-a-prod \
  --as=alice@example.com --as-group=developers

# What would a developer in a different group be able to do?
kubectl auth can-i delete pods -n team-a-prod \
  --as=mallory@example.com --as-group=contractors

kubectl auth whoami shows the identity of the current kubeconfig.

Common RBAC mistakes

Mistake 1: Cluster-admin by default

kind: ClusterRoleBinding
metadata:
  name: everyone-is-admin
subjects:
- kind: Group
  name: system:authenticated
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: cluster-admin
  apiGroup: rbac.authorization.k8s.io

This binds cluster-admin to every authenticated identity. A compromised ServiceAccount or a leaked kubeconfig becomes full cluster compromise. Never do this in production.

Mistake 2: cluster-admin in the bootstrapping kubeconfig

kubeadm produces an admin.conf with cluster-admin. This is necessary for initial setup but should be replaced with a narrower kubeconfig for normal operations.

Mistake 3: ServiceAccount with broad permissions

apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-app
---
kind: ClusterRoleBinding
metadata:
  name: my-app-cluster-admin
subjects:
- kind: ServiceAccount
  name: my-app
  namespace: my-app-prod
roleRef:
  kind: ClusterRole
  name: cluster-admin
  apiGroup: rbac.authorization.k8s.io

A Pod running with this SA can do anything. Production ServiceAccounts get only the permissions they need: read ConfigMaps in their namespace, write metrics, etc.

Mistake 4: verbs that allow mutations when reads were intended

rules:
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["get", "list", "watch", "create", "update", "patch"]

create, update, patch are mutations. A “read Secrets” role needs get, list, watch only. Including mutations is a privilege escalation vector.

Mistake 5: wildcard apiGroups or resources

rules:
- apiGroups: ["*"]
  resources: ["*"]
  verbs: ["*"]

Equivalent to cluster-admin. Avoid.

How to audit RBAC

Production clusters audit RBAC regularly:

# List all ClusterRoleBindings to system:masters
kubectl get clusterrolebindings -o json | \
  jq -r '.items[] | select(.roleRef.name == "cluster-admin") | .subjects[]'

# List every ServiceAccount with cluster-wide permissions
kubectl get clusterrolebindings -o json | \
  jq -r '.items[] | select(.subjects[]?.kind == "ServiceAccount") |
    select(.roleRef.name == "cluster-admin") |
    .metadata.name + ": " + (.subjects[]?.namespace + "/" + .subjects[]?.name)'

Audit logs are the source of truth for “who did what”:

journalctl -u kube-apiserver | grep -i "forbidden\|denied" | tail -20

The audit log entry records the verb, the resource, the identity, the decision (allow/deny), and the reason.

How to remove access

To revoke RBAC permissions, delete the binding:

kubectl delete rolebinding developers -n team-a-prod

A user with no bindings cannot do anything in the cluster (via RBAC). They may still authenticate, but every action is denied.

For compromised ServiceAccount tokens, the immediate response is to:

  1. Delete the ServiceAccount token Secret
  2. Delete the ServiceAccount’s SessionList (if OIDC)
  3. Delete the Pods that use the compromised token

Projected tokens are short-lived; deleting the Secret that the Pod mounts invalidates it within seconds.

Cross-course references

  • The Linux course part XXVII-Linux-Auth covers central identity (LDAP, Kerberos) which often backs OIDC groups used in RBAC.
  • The Linux course part XXIX-Linux-Hardening covers least-privilege principles that map directly onto RBAC.
  • The Observability course part IX-Observability-Exporters covers the metrics the API server exposes about RBAC decision latency.
  • The Docker course part XXXVIII-Docker-Secrets covers secret management patterns that interact with RBAC (secrets resource permissions).

Quiz

Knowledge check · 4 questions

  1. Q1. A RoleBinding in namespace `team-a-prod` references a ClusterRole. What does the binding grant?

  2. Q2. Binding `cluster-admin` to the `system:authenticated` group is a safe default for production clusters because it ensures every user can manage their own workloads.

  3. Q3. A developer runs `kubectl delete pod web-7c8 -n team-a-prod` and gets `Error from server (Forbidden): pods "web-7c8" is forbidden: User "alice@example.com" cannot delete resource "pods" in API group "" in the namespace "team-a-prod"`. The developer is in the `developers` group, which has a RoleBinding in team-a-prod granting `get, list, watch` on pods. Diagnose and remediate.

    RoleBinding: ```yaml apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: developers namespace: team-a-prod subjects: - kind: Group name: developers apiGroup: rbac.authorization.k8s.io roleRef: kind: ClusterRole name: view apiGroup: rbac.authorization.k8s.io ``` The `view` ClusterRole grants `get, list, watch` on most resources — including pods. It does not grant `delete`. `kubectl auth can-i delete pods -n team-a-prod --as=alice@example.com --as-group=developers` returns `no`.

  4. Q4. Explain least-privilege RBAC for production ServiceAccounts. What permissions should a typical application ServiceAccount have, and what should it never have?

Passing score: 75%. Answers are checked in this browser.

Production discipline

  • Bind specific permissions to specific identities. Never grant cluster-admin broadly.
  • Test permissions with kubectl auth can-i --as=... before granting.
  • Use the principle of least privilege: each ServiceAccount gets exactly the permissions its code needs.
  • Audit RBAC regularly: list every ClusterRoleBinding to system:masters, every ServiceAccount with broad permissions, every wildcard verb.
  • Treat the view ClusterRole as the default for human developers; grant additional verbs only when justified.