Every KCSA candidate learns RBAC: Roles, ClusterRoles, bindings, verbs, least privilege. What most study material skips is that RBAC is only one link in a chain. The Kubernetes API server can run several authorizers in sequence, each one can say yes, no, or no opinion, the order you list them changes the outcome, and the kubelet runs its own, separate chain for requests to port 10250. Since Kubernetes v1.32 that chain can also be described in a structured configuration file with per-webhook failure policies and CEL pre-filters, which the exam’s Kubernetes Security Fundamentals domain lists under Authorization alongside RBAC, ABAC, Node and Webhook.
Our KCSA authentication, authorization and RBAC guide owns the RBAC objects themselves and the “union of bindings” reasoning. This article is the deep dive on the rest of the layer: how a decision is actually reached, what each non-RBAC authorizer contributes, how a Webhook authorizer talks to the API server through SubjectAccessReview, what the new AuthorizationConfiguration file changes, and how kubectl auth can-i fits in. Everything is usable on a real cluster, and every section ends with the angle a multiple-choice question would take.
Where Authorization Sits in the Request Pipeline
A request to the API server passes through three gates in a fixed order: authentication (who are you?), authorization (may you do this?), and admission control (should this object be accepted or mutated?). Two consequences are tested constantly:
- Authorization runs after authentication, so it sees a resolved identity: a username, a list of groups, and optional
extrakey/value attributes. It never sees a certificate or a token. - Admission plugins run only if authorization allows the request. A
ValidatingAdmissionPolicyor Pod Security Admission never gets a chance to reject a request that RBAC has already denied, and it cannot grant access that authorization refused.
The authorizer receives a small, fixed set of request attributes, and every authorization mode reasons about these same attributes:
| Attribute | Source | Notes |
|---|---|---|
user, groups, extra | authentication layer | extra is a map of string lists; populated by OIDC claims or impersonation headers |
| request path type | URL | resource (/api/..., /apis/...) or non-resource (/healthz, /metrics, /version) |
verb | HTTP method + URL shape | get, list, watch, create, update, patch, delete, deletecollection; non-resource requests use the lowercased HTTP verb |
apiGroup, resource, subresource | URL | the core group is the empty string; pods/log is resource pods, subresource log |
namespace, name | URL | get, update, patch and delete must carry a resource name |
A handful of verbs never appear in a URL and exist only for authorization: impersonate (on users, groups, serviceaccounts, userextras), approve (on CertificateSigningRequests), and bind and escalate (on roles and clusterroles). The CKS privilege-escalation guide covers those three in depth.
How the Chain Decides: Allow, Deny, No Opinion
Each authorizer in the chain returns one of three verdicts:
| Verdict | Meaning | Effect on the chain |
|---|---|---|
| Allow | this authorizer explicitly permits the request | evaluation stops, request proceeds to admission |
| Deny | this authorizer explicitly forbids the request | evaluation stops, client receives HTTP 403 |
| No opinion | this authorizer has nothing to say | the next authorizer is consulted |
Two rules follow, and both are heavily examined:
- The first authorizer with an opinion wins. Authorizers are checked in the order you configure them. An earlier module has higher priority to allow or deny.
- If every authorizer has no opinion, the request is denied. Kubernetes authorization is default-deny. There is no implicit “allow if nobody objected”.
RBAC is purely additive: it can return Allow or No opinion but never Deny, because RBAC has no deny rules. That single fact explains the most famous trap in this domain. --authorization-mode=AlwaysAllow,RBAC is identical to --authorization-mode=AlwaysAllow, because AlwaysAllow approves everything before RBAC is reached, and --authorization-mode=RBAC,AlwaysAllow is also identical to AlwaysAllow, because whenever RBAC has no opinion the fallback approves. Order cannot save you; AlwaysAllow anywhere in the chain disables authorization.
One identity bypasses the chain entirely. Members of the system:masters group are granted unrestricted access by the API server and skip RBAC and Webhook authorizers. The official guidance is to avoid adding users to that group and to grant administrative access with a ClusterRoleBinding to the built-in cluster-admin ClusterRole instead, which stays visible and revocable through the API. The kubeadm-generated admin.conf certificate belongs to system:masters, which is why the KCSA client security guide treats that file as a root credential.
The Six Authorization Modes
The --authorization-mode flag accepts a comma-delimited, ordered list drawn from six values. When neither --authorization-mode nor --authorization-config is set, the API server defaults to AlwaysAllow, which is why every hardening benchmark checks this flag first.
| Mode | What it does | Where it is used |
|---|---|---|
AlwaysAllow | approves every request; the default when nothing is configured | testing only; never on a cluster with untrusted clients |
AlwaysDeny | rejects every request | testing only |
ABAC | attribute-based policies read line by line from a local JSON file (--authorization-policy-file) | legacy; needs an API server restart to change policy |
RBAC | roles and bindings stored as API objects in the rbac.authorization.k8s.io group | the standard; changeable through the API without restarts |
Node | special-purpose authorizer for kubelets, scoped to the Pods bound to each node | production, listed before RBAC |
Webhook | synchronous HTTPS call to an external service that returns a SubjectAccessReview verdict | integrating external policy engines or central IAM |
The production baseline for a kubeadm cluster is --authorization-mode=Node,RBAC. The KCSA cluster component security guide lists the full set of API server flags; here we go inside the two modes that are not RBAC.
The Node authorizer and NodeRestriction
Kubelets need to read Secrets, ConfigMaps, PersistentVolumeClaims and PersistentVolumes for the Pods they run, write Node and Pod status, and create Events. Granting that through RBAC would mean every kubelet could read every Secret in the cluster, because RBAC cannot express “only the objects referenced by Pods on your node”.
The Node authorizer solves that with a graph. It authorizes a kubelet to:
- read Services, Endpoints, Nodes, Pods, and the Secrets, ConfigMaps, PVCs and PVs related to Pods bound to that kubelet’s node;
- write Nodes and Node status, Pods and Pod status, and Events.
To be recognised, a kubelet must present a credential in the system:nodes group with the username system:node:<nodeName>, where <nodeName> matches the registered Node name exactly. A kubelet certificate with a group of system:nodes but a username that does not follow that pattern is not authorized by the Node authorizer and falls through to the next module.
The Node authorizer limits what a kubelet may read, but on its own it still lets a kubelet write to any Node or Pod object. The NodeRestriction admission plugin (--enable-admission-plugins=...,NodeRestriction) closes that: a kubelet may only modify its own Node object and Pods bound to itself, and it is blocked from setting or removing certain labels. In exam terms, the Node authorizer is the authorization half and NodeRestriction is the admission half of the same control; a question that asks how to stop a compromised node from editing other nodes’ labels or taints is asking for NodeRestriction, not for an RBAC change.
Since v1.33 a related beta control, service account token audience restriction, further limits kubelets to requesting tokens only for audiences referenced by Pods on that node; RBAC can grant extra audiences through the request-serviceaccounts-token-audience verb.
ABAC: why it is still on the exam
ABAC evaluates each request against policy lines in a local file, one JSON object per line, matching on user, group, namespace, resource and verb. It is listed in the KCSA curriculum for recognition. The two things to know are that ABAC policies are not API objects (changing them means editing a file on every control-plane node and restarting the API server) and that this is exactly the property that made RBAC replace it. Choose ABAC in a question only when the scenario explicitly describes a file-based policy.
The Webhook Authorizer and the SubjectAccessReview Contract
Webhook mode makes the API server POST a SubjectAccessReview object to an external HTTPS service and block the request until the service responds. It is configured with a kubeconfig-format file:
# --authorization-webhook-config-file=/etc/kubernetes/authz-webhook.yaml
apiVersion: v1
kind: Config
clusters:
- name: remote-authz
cluster:
certificate-authority: /etc/kubernetes/pki/authz-ca.pem # verifies the remote service
server: https://authz.example.com/authorize # must be https; no query parameters
users:
- name: kube-apiserver
user:
client-certificate: /etc/kubernetes/pki/authz-client.pem # the API server authenticates to the webhook
client-key: /etc/kubernetes/pki/authz-client-key.pem
current-context: webhook
contexts:
- name: webhook
context:
cluster: remote-authz
user: kube-apiserver
In the file, clusters describes the remote service and users describes the API server as a client, which reads backwards until you notice that the API server is the client here.
The request
For a resource request the API server sends something like this (with --authorization-webhook-version=v1 the group version is authorization.k8s.io/v1; the flag still defaults to v1beta1):
{
"apiVersion": "authorization.k8s.io/v1",
"kind": "SubjectAccessReview",
"spec": {
"resourceAttributes": {
"namespace": "payments",
"verb": "get",
"group": "",
"resource": "secrets",
"name": "db-credentials"
},
"user": "jane",
"group": ["developers", "system:authenticated"]
}
}
Non-resource requests carry nonResourceAttributes with a path and a verb instead. Since v1.34, resourceAttributes can also carry parsed fieldSelector and labelSelector requirements, so a webhook can authorize “list Pods with this label” differently from “list all Pods”. Clients always need access to /api, /api/*, /apis, /apis/* and /version for discovery, so a webhook that denies non-resource paths wholesale breaks kubectl.
The three possible responses
The webhook answers by filling status. The shape of the answer is the part exam questions probe:
| Response | allowed | denied | Verdict | Behaviour |
|---|---|---|---|---|
| allow | true | omitted | Allow | request proceeds, no further authorizer consulted |
| no opinion | false | omitted or false | No opinion | later authorizers (e.g. RBAC) may still allow it |
| hard deny | false | true | Deny | request rejected immediately, later authorizers skipped |
The documentation is explicit that no opinion is the preferred way to say “not me”, and that a hard deny should be used only by webhooks that understand the full authorizer chain, because a webhook that denies everything it does not recognise will also deny requests RBAC would have allowed. reason is a free-text explanation surfaced in the 403, and evaluationError reports that an error occurred during evaluation without necessarily changing the verdict (RBAC, for example, can hit a missing Role and still decide based on the roles that exist).
Caching and timeouts
Because every webhook call is a synchronous network round trip, the API server caches verdicts:
| Flag | Default | Meaning |
|---|---|---|
--authorization-webhook-cache-authorized-ttl | 5m0s | how long an allow is remembered |
--authorization-webhook-cache-unauthorized-ttl | 30s | how long a no opinion / deny is remembered |
--authorization-webhook-version | v1beta1 | SubjectAccessReview version sent to and expected from the webhook |
The asymmetry is deliberate: a revoked permission must disappear within seconds, while a granted one can be cached for minutes. A question describing “a user who was just granted access through the external authorizer still gets 403 for up to half a minute” is describing the unauthorized TTL, and “a user whose access was removed in the external system can still read Secrets for a few minutes” is the authorized TTL. Both are tunable; setting either to 0 disables that cache.
The Structured AuthorizationConfiguration File
The flag-based chain has three limitations: only one webhook, no way to decide what happens when the webhook is unreachable, and no way to avoid calling it for requests it does not care about. Kubernetes v1.29 introduced a structured configuration file to fix all three; it is stable since v1.32 and enabled by default. You point the API server at it with --authorization-config=/etc/kubernetes/authorization.yaml, and that flag is mutually exclusive with --authorization-mode and every --authorization-webhook-* flag: setting both makes the API server exit at startup.
apiVersion: apiserver.config.k8s.io/v1
kind: AuthorizationConfiguration
authorizers:
- type: Webhook
name: central-iam # required; appears in metrics
webhook:
authorizedTTL: 30s # default 5m0s
unauthorizedTTL: 30s # default 30s
timeout: 3s # required; max 30s
subjectAccessReviewVersion: v1
matchConditionSubjectAccessReviewVersion: v1
failurePolicy: Deny # Deny | NoOpinion; required
connectionInfo:
type: KubeConfigFile # or InClusterConfig (not allowed for kube-apiserver itself)
kubeConfigFile: /etc/kubernetes/authz-webhook.yaml
matchConditions:
- expression: has(request.resourceAttributes) # resource requests only
- expression: request.resourceAttributes.namespace == 'kube-system'
- expression: "!('system:serviceaccounts:kube-system' in request.groups)"
- type: Node
name: node
- type: RBAC
name: rbac
What each addition buys you:
- Multiple webhooks. The
authorizerslist can contain severalWebhookentries, each with its own name, timeout and policy. The flags allowed exactly one. failurePolicy. Controls the verdict when the webhook times out, returns a malformed response, or amatchConditionsexpression errors.Denyfails closed;NoOpinionfalls through to the next authorizer. There is no default, so you are forced to decide. Fail-closed webhooks are the safer answer for anything guarding sensitive namespaces, at the cost of availability if the webhook goes down.- CEL
matchConditions. Up to 64 boolean expressions evaluated against theSubjectAccessReview(v1). If any expression is false the webhook is skipped; if all are true it is called; if one errors (and none is false),failurePolicydecides. The example above sends only resource requests inkube-systemthat do not come fromkube-systemservice accounts, which turns an expensive, latency-adding webhook into a narrow guard. - Hot reload. The file is reloaded when the API server sees it change, and on a 60-second schedule even without change events. Reloads may reorder authorizers and edit webhooks, but may not add or remove the
NodeorRBACauthorizers. - Named authorizers and metrics. The
namefield feeds per-authorizer metrics, so you can see which link in the chain allowed or denied, and which webhook is slow.
The ordering rules do not change. In the file above the webhook runs first, then Node, then RBAC. If the webhook returns no opinion, RBAC still gets its turn; if the webhook returns deny, RBAC never sees the request. All control-plane nodes must carry identical files, and during upgrades the file must stay in a format both versions understand.
The SubjectAccessReview API Family and kubectl auth can-i
The same SubjectAccessReview object that a webhook receives is also an API you can create to ask the API server “would this be allowed?”. Four review resources live in the authorization.k8s.io group, and they are create-only: you POST one, the server fills in status, nothing is stored.
| Resource | Scope | Who it checks | Typical caller |
|---|---|---|---|
SelfSubjectAccessReview | cluster | the caller | kubectl auth can-i |
SubjectAccessReview | cluster | any user/group/extra you name | kubelet, extension API servers, admission webhooks, dashboards |
LocalSubjectAccessReview | one namespace | any user, restricted to a namespace | namespace-scoped delegation |
SelfSubjectRulesReview | one namespace | the caller | kubectl auth can-i --list, UIs that hide buttons |
kubectl auth can-i is a thin wrapper around SelfSubjectAccessReview and therefore works with every authorization mode, not just RBAC. Its answer is whatever the configured chain would return:
kubectl auth can-i create deployments --namespace dev # yes
kubectl auth can-i list secrets --namespace dev --as dave # no (needs impersonate permission)
kubectl auth can-i list pods -n prod --as system:serviceaccount:dev:dev-sa
kubectl auth can-i get pods --subresource=log # subresources are separate
kubectl auth can-i get /logs/ # non-resource URL
kubectl auth can-i '*' '*' # am I effectively cluster-admin here?
kubectl auth can-i --list --namespace=foo # SelfSubjectRulesReview
Two subtleties: --as turns the call into an impersonated request, so you need impersonate permission on users (and --as-group on groups) to check someone else, and the result reflects authorization only. A yes from can-i create pods does not mean Pod Security Admission or a validating policy will accept the Pod you then submit.
Creating the review object directly shows what kubectl does under the hood:
apiVersion: authorization.k8s.io/v1
kind: SelfSubjectAccessReview
spec:
resourceAttributes:
group: apps
resource: deployments
verb: create
namespace: dev
kubectl create -f ssar.yaml -o yaml | grep -A3 '^status:'
# status:
# allowed: true
# denied: false
The status block uses the same allowed / denied / reason / evaluationError fields as a webhook response, which is no coincidence: a webhook is just another consumer of the same contract.
The Kubelet Runs Its Own Chain
The authorization chain discussed so far protects the API server on port 6443. The kubelet serves its own HTTPS API on port 10250 (exec, logs, stats, metrics), and it authenticates and authorizes those requests independently. Its defaults are the insecure ones:
- requests not rejected by another method are treated as anonymous (
system:anonymous,system:unauthenticated) unless--anonymous-auth=false(orauthentication.anonymous.enabled: falsein the kubelet config); - the default authorization mode is
AlwaysAllow.
The fix is to make the kubelet delegate both decisions to the API server:
# /var/lib/kubelet/config.yaml
authentication:
anonymous:
enabled: false
webhook:
enabled: true # kubelet calls TokenReview for bearer tokens
x509:
clientCAFile: /etc/kubernetes/pki/ca.crt
authorization:
mode: Webhook # kubelet calls SubjectAccessReview for every request
With mode: Webhook, every request to the kubelet becomes a SubjectAccessReview sent through the kubelet’s own kubeconfig, and the API server’s chain (Node, RBAC, any webhooks) answers it. The kubelet maps its endpoints onto the nodes resource with subresources, so RBAC rules can be written against them:
| Kubelet endpoint | resource | subresource |
|---|---|---|
/stats/* | nodes | stats |
/metrics/* | nodes | metrics |
/logs/* | nodes | log |
/spec/* | nodes | spec |
/checkpoint/* | nodes | checkpoint |
everything else (/exec, /run, /attach, /portForward, …) | nodes | proxy |
The attribute that catches people out: get on nodes/proxy is not read-only. Several of those endpoints speak WebSocket over an HTTP GET, so a subject with get on nodes/proxy can execute commands in any container on the node. A role that grants nodes/proxy “just to read logs” has granted node-wide code execution. The API server’s own kubelet client identity is the one subject that legitimately needs proxy, stats, log, spec and metrics.
This is the single most useful mental model in the domain: the kubelet’s authorization.mode: Webhook and the API server’s Webhook mode are the same mechanism viewed from two ends. One component asks via SubjectAccessReview; the other answers via its chain.
Aggregated ClusterRoles: Rules That Appear From Nowhere
One more mechanism changes what RBAC decides without anyone editing the role being consulted. A ClusterRole with an aggregationRule has its rules filled in by a control-plane controller from every other ClusterRole whose labels match the selector:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: monitoring
aggregationRule:
clusterRoleSelectors:
- matchLabels:
rbac.example.com/aggregate-to-monitoring: "true"
# rules: is omitted on purpose; the control plane manages it
Any ClusterRole created later with that label contributes its rules automatically. The built-in admin, edit and view roles are aggregated this way through the rbac.authorization.k8s.io/aggregate-to-admin|edit|view labels, which is how CRD authors extend them. Three facts matter for security:
- The control plane overwrites anything you put in
ruleson the aggregate role, and even settingrules: []claims field ownership under server-side apply and causes a conflict loop with GitOps tooling. - Anyone who can create a ClusterRole with the right label can silently widen
view,editoradminfor every subject bound to them. The CKS privilege-escalation guide treats this as an escalation path; auditcreate clusterrolesas carefully asbind. kubectl describe clusterrole viewshows the resolved rules, which is where you look when a subject can do something no binding appears to grant.
Related to that self-healing behaviour: the API server re-creates missing permissions and subjects on its default system: roles and bindings at every start (opt out per object with the rbac.authorization.kubernetes.io/autoupdate: "false" annotation). Tightening a default system: ClusterRole by hand is undone at the next restart; the supported approach is to stop binding subjects to it.
Scenario Drill: Mapping Symptoms to the Chain
The KCSA is multiple-choice and conceptual, so practise the mapping rather than the YAML.
| Scenario | What is going on | Answer the exam wants |
|---|---|---|
--authorization-mode=RBAC,AlwaysAllow; a user with no bindings can delete nodes | RBAC has no opinion, AlwaysAllow approves the fallback | remove AlwaysAllow; it disables authorization wherever it appears |
External authorizer webhook is down; all kubectl calls hang then fail | Webhook mode is synchronous | set timeout and failurePolicy in AuthorizationConfiguration (or accept fail-closed) |
Webhook returns allowed: false, denied: true for a request RBAC allows | hard deny short-circuits the chain | change the webhook to no opinion unless it must veto |
| Access removed in the external IAM but still works for a few minutes | authorized-response cache | authorizedTTL (default 5m) |
| A compromised node changes labels on another node | Node authorizer alone does not scope writes | enable NodeRestriction admission |
A kubelet with cert CN node-7, group system:nodes gets 403 on its own Pods | username is not system:node:<nodeName> | reissue the kubelet certificate with the right CN |
| Webhook is called for every request and adds latency cluster-wide | no pre-filtering | matchConditions in AuthorizationConfiguration |
A subject bound only to view can read a new CRD nobody granted | a ClusterRole labelled aggregate-to-view was added | audit ClusterRole creation; inspect resolved rules |
kubectl auth can-i create pods says yes but the Pod is rejected | authorization passed; admission (PSA / policy) failed | authorization ≠ admission |
Anyone can curl -k https://node:10250/pods | kubelet anonymous auth on, AlwaysAllow | anonymous.enabled: false, authorization.mode: Webhook |
Common Mistakes
- Treating RBAC as able to deny. It cannot. If you need a veto, it has to come from a Webhook returning
denied: true, or from not granting the permission at all. - Assuming chain order does not matter. It decides which authorizer’s opinion is heard first; it just cannot rescue a chain that contains
AlwaysAllow. - Granting
getonnodes/proxyas a “read” permission. It is remote code execution on every container on the node. - Mixing
--authorization-configwith--authorization-mode. The API server refuses to start. - Putting
rules:on an aggregated ClusterRole. The controller overwrites it and server-side apply fights you forever. - Hand-editing
system:ClusterRoles. Auto-reconciliation restores them on restart. - Reading
can-ias a full admission preview. It answers the authorization question only.
How to Practise This for the KCSA
The exam will not ask you to write an AuthorizationConfiguration file from memory. It will describe a cluster, a flag or a symptom and offer four controls, three of which act at the wrong layer: an RBAC fix for an admission problem, an admission plugin for an authorizer-ordering problem, a kubelet flag for an API server problem. The skill is fast, confident mapping, and it is built by working scenario questions whose distractors are deliberately plausible. The KCSA mock exam bundle is organised by the six exam domains and includes authorization-chain scenarios like the ones above, each with an explanation that names the exact control. Pair it with the free KCSA practice questions, the KCSA exam guide for logistics and domain weights, and the KCSA study plan to schedule the remaining domains. If you are heading on to the hands-on CKS afterwards, the CKS cluster setup and hardening guide shows the same flags being set under exam time pressure.
Frequently Asked Questions
What is the order of evaluation for Kubernetes authorization modes?
Authorizers are evaluated in the order listed in --authorization-mode (or in the authorizers list of an AuthorizationConfiguration file). The first authorizer to return an explicit allow or deny decides; an authorizer with no opinion passes the request to the next one. If none has an opinion, the request is denied with HTTP 403.
Why is --authorization-mode=AlwaysAllow,RBAC the same as AlwaysAllow?
AlwaysAllow approves every request before RBAC is consulted. Reversing the order does not help either, because RBAC can only allow or abstain, never deny, so every request RBAC abstains on falls through to AlwaysAllow. Any chain containing AlwaysAllow effectively has authorization disabled.
What is the difference between the Node authorizer and the NodeRestriction admission plugin?
The Node authorizer is an authorization module that lets a kubelet read only the objects related to Pods on its own node. NodeRestriction is an admission plugin that limits what a kubelet may write: only its own Node object and the Pods bound to it. Production clusters enable both with --authorization-mode=Node,RBAC and --enable-admission-plugins=NodeRestriction.
What does a Webhook authorizer send and receive?
The API server POSTs a SubjectAccessReview describing the user, groups, extra attributes and either resourceAttributes or nonResourceAttributes. The service fills status.allowed; allowed: false alone means no opinion and later authorizers may still allow the request, while allowed: false with denied: true rejects it immediately. Allowed responses are cached for 5 minutes by default and unauthorized ones for 30 seconds.
What is the AuthorizationConfiguration file?
A structured file (apiserver.config.k8s.io/v1, kind AuthorizationConfiguration) passed through --authorization-config, stable since Kubernetes v1.32. It defines the ordered authorizer chain and lets you configure multiple webhooks, a per-webhook failurePolicy (Deny or NoOpinion), a required timeout, cache TTLs, and CEL matchConditions that decide which requests reach a webhook. It is reloaded automatically and is mutually exclusive with --authorization-mode.
How does kubectl auth can-i work?
It creates a SelfSubjectAccessReview and prints the verdict the API server’s full authorization chain returns, so it works with RBAC, Webhook, ABAC or any combination. With --as or --as-group it impersonates another subject, which requires impersonate permission. It checks authorization only, not admission control.
Does the kubelet use the API server’s authorization chain?
Only if configured to. By default the kubelet’s own API on port 10250 allows anonymous requests and uses AlwaysAllow. With authorization.mode: Webhook (and authentication.webhook.enabled: true), the kubelet sends a SubjectAccessReview to the API server for every request, and its endpoints are authorized as subresources of nodes such as nodes/proxy, nodes/stats and nodes/log.
Conclusion
Authorization in Kubernetes is a chain, not a single switch. Each authorizer returns allow, deny or no opinion; the first opinion wins; silence means denial; and RBAC can never say no, which is why AlwaysAllow anywhere in the chain is fatal. The Node authorizer and NodeRestriction together scope kubelets to their own node, the Webhook authorizer extends the chain to external systems through the SubjectAccessReview contract, and the AuthorizationConfiguration file turns that contract into something you can filter with CEL and fail closed. The same contract powers kubectl auth can-i and the kubelet’s delegated authorization, so once you can read a SubjectAccessReview, you can read every link in the chain.
Continue with the KCSA authentication and RBAC guide for the objects RBAC itself evaluates, the Kubernetes audit logging guide to see authorization decisions recorded, and the KCSA threat model guide for how an attacker approaches each link described here.