The Kubernetes API server can run several authorization modules — including RBAC, Node, and Webhook. How do multiple modules combine to reach a decision, and what specific problem does the Node authorizer solve?
answer
- allow / deny / no-opinion; first explicit answer wins; default deny
- RBAC never denies — it only grants (403 = nothing matched)
- Node authorizer: kubelet sees only its own node's pods and their Secrets
- without it: every node reads every Secret
- NodeRestriction admission = which fields; Node authz = which objects
basics
~20 sModules are consulted in order; each returns allow, deny, or no-opinion. The first explicit allow or deny ends the chain, and if every module abstains the request is denied with 403. RBAC only grants — it never denies. The Node authorizer restricts each kubelet to the objects tied to pods on its own node, instead of granting all kubelets blanket read access.
solid answer
~60 sThe API server is configured with an ordered list of authorizers (classically `--authorization-mode=Node,RBAC`, or in newer clusters a structured AuthorizationConfiguration file). Each is asked in turn and returns **allow**, **deny**, or **no opinion**. The first explicit allow or deny is final; if all abstain, the request is rejected with 403. That is why RBAC 403s mean "nothing granted this" — RBAC is purely additive and never returns deny. **Node** is a special-purpose authorizer for kubelets. A kubelet authenticates as `system:node:<nodename>` in group `system:nodes`, and Node grants it only what that node needs: read the Secrets, ConfigMaps, PVCs and pods actually referenced by pods bound to *that* node, plus write its own Node status and its pods' statuses and events. Without it you would need a ClusterRole letting every kubelet read every Secret in the cluster — so one compromised node would own the cluster. Node pairs with the **NodeRestriction** admission plugin, which stops a kubelet from editing other nodes' objects or self-assigning privileged labels. **Webhook** delegates decisions to an external service and *can* deny, which is how cloud providers layer their own IAM on top.
code
bash · 9 lines# The full chain's answer for the current identity
kubectl auth can-i get secrets -n payments
# As another subject (requires impersonate permission)
kubectl auth can-i list pods -A --as system:serviceaccount:ci:build-bot
kubectl auth can-i get nodes --as system:node:worker-3 --as-group system:nodes
# Everything the current identity may do in a namespace
kubectl auth can-i --list -n paymentsgo deeper
Know that RBAC is the usual authorization mechanism, that it only grants permissions, and that a 403 means nothing granted the action.
Explain the allow/deny/no-opinion chain with default deny, and that the Node authorizer exists to scope kubelets to their own node's objects.
Detail the node→pod→object graph, the cluster-wide Secret exposure that RBAC-only would force, the Node/NodeRestriction split between objects and fields, and webhook authorizer caching and failure behaviour.
Discuss the authorization architecture: where external IAM should plug in via webhook, the availability coupling that introduces, structured AuthorizationConfiguration with CEL match conditions, and the blast radius of a compromised node under each design.
## The chain Authorization in Kubernetes is a chain of independent modules, configured in order. Each module receives the same request attributes — user, groups, verb, API group, resource, subresource, namespace, object name, or for non-resource requests just the HTTP path and verb — and returns one of three answers: - **Allow** — decision made, chain stops, request proceeds to admission. - **Deny** — decision made, chain stops, 403. - **No opinion** — pass to the next module. If the chain is exhausted with every module abstaining, the default is deny (403). This structure has two consequences worth stating explicitly. First, **order matters** when a module can deny. Second, since **RBAC never denies** — it either finds a matching grant (allow) or abstains — you cannot write an RBAC rule that revokes a permission granted elsewhere. Restriction in RBAC comes from not granting, and any "deny" semantics must come from a webhook authorizer or from admission control. ## The modules **RBAC** — the main event: Roles and ClusterRoles list rules (apiGroups, resources, verbs, optionally resourceNames), and RoleBindings/ClusterRoleBindings attach them to users, groups, or ServiceAccounts. Purely additive, evaluated in memory from cached objects, and fast. **Node** — a bespoke authorizer for kubelets, covered below. **Webhook** — the API server posts a `SubjectAccessReview` to an external HTTPS service, which answers allow or deny. Managed Kubernetes offerings use this to map cloud IAM identities onto cluster permissions. It adds a network hop to authorization decisions, so results are cached (with separate TTLs for allow and deny) and the failure mode needs thought: an unreachable authorization webhook typically means denials, i.e. an unusable cluster. **ABAC** — a legacy file of policy lines requiring an API-server restart to change. Effectively obsolete; proposing it is a red flag. **AlwaysAllow / AlwaysDeny** — test-only. Newer clusters replace the flat `--authorization-mode` list with an **AuthorizationConfiguration** file, which allows multiple webhooks, per-webhook match conditions written in CEL, explicit failure policies, and reordering without the old flag's limitations. ## What the Node authorizer is for Every kubelet must read from the API server: the pods bound to its node, and the Secrets, ConfigMaps, PVCs, and PVs those pods reference. It must also write: its own Node status, its pods' statuses, and events. Expressing that with RBAC alone is impossible in any safe way, because RBAC's granularity is "resource kind, optionally specific names" — it cannot say "the Secrets referenced by pods scheduled here". The only RBAC formulation is a ClusterRole granting `get` on all Secrets to the group `system:nodes`. That means **every node can read every Secret in the cluster**, so compromising one worker — a container escape, a stolen kubelet credential — yields cluster-wide credential theft. The Node authorizer solves this by being graph-aware. It maintains a graph of node → pod → referenced objects, built from the pods actually bound to each node, and authorizes a request from `system:node:<nodename>` only if the target object is reachable in that node's subgraph. Node A cannot read a Secret used only by pods on node B. Two prerequisites: the kubelet must authenticate with a username of the form `system:node:<nodename>` and be in group `system:nodes` (which is what the standard kubelet client certificate provides), and the Node authorizer must actually be first in the chain. ## Why NodeRestriction is its partner Authorization answers "may you touch this object", not "may you set this field". A kubelet legitimately writes its own Node object — but nothing in authorization stops it from writing a *label* claiming its node sits in a special pool, or from editing another node's object. The **NodeRestriction** admission plugin closes that: a kubelet may modify only its own Node and Pod objects, may not set restricted labels (such as node-role or topology labels used for scheduling decisions), and — with the related ServiceAccount token restriction — cannot create tokens for arbitrary ServiceAccounts. This is a compact illustration of the whole pipeline's division of labour: the Node authorizer decides *which objects*, NodeRestriction admission decides *which fields*, and neither could do the other's job. ## Debugging `kubectl auth can-i <verb> <resource> --as <user> --as-group <group>` replays a decision through the whole chain, so it reflects the real answer rather than just RBAC's view. For "why was this allowed?", the audit log records the authorizing decision and, for RBAC, the API server can log the rule that matched. If a webhook authorizer is present, remember that a permission may be granted or refused entirely outside your cluster objects — a common source of confusion on managed platforms.
- A cluster runs with --authorization-mode=Node,RBAC. Can you use RBAC to take away a permission that a ClusterRoleBinding grants?No. RBAC is purely additive: rules only grant, and the module returns allow or no-opinion — never deny. To remove a permission you must remove or narrow the binding itself. If you genuinely need deny semantics, they have to come from a webhook authorizer, which can return an explicit deny, or from admission control, which sees the object and can reject it.
- What breaks if a kubelet's client certificate is issued with a Common Name that is not system:node:<nodename>?The Node authorizer keys off exactly that username plus membership in the system:nodes group to look up the node's subgraph, so a kubelet authenticating under any other name gets no opinion from Node and falls through to RBAC. It will then be denied everything it is not explicitly granted, and the usual reaction — granting the group broad access to fix it — reintroduces the cluster-wide Secret read that Node exists to prevent. NodeRestriction's per-node checks likewise rely on that identity.
A row of specialist clerks: each may stamp approved, stamp refused, or shrug and pass it on. RBAC's stamp pad has no 'refused' stamp at all — it can only approve or shrug.
saying these in an interview costs you the question
- Thinking RBAC can express a deny rule that overrides a grant.
- Believing all authorization modules must agree, rather than the first explicit answer winning.
- Assuming the Node authorizer alone secures kubelets, forgetting the NodeRestriction admission plugin.
- Suggesting ABAC as a current option for fine-grained policy.
- Claiming an authorization decision can inspect the object's fields — it sees only request attributes.