In Kubernetes RBAC, what are the three parts of a policy rule — apiGroups, resources and verbs — and how would you write a Role that grants read-only access to Pods in a single namespace?
answer
- rule = apiGroups + resources + verbs
- "" = core group; deployments live in apps
- pods/log and pods/exec are separate subresources
- additive only — no deny rule
- resourceNames does not work with list/watch
basics
~20 sA rule says which API groups, which resource types, and which verbs are allowed. Read-only pods is apiGroups: [""] (core group), resources: ["pods"], verbs: ["get","list","watch"]. A Role holds the rules; a RoleBinding attaches it to a user, group or ServiceAccount. RBAC is purely additive — there is no deny.
solid answer
~50 sA rule is a triple: - **`apiGroups`** — which API group the resource lives in. `""` (empty string) is the **core** group: pods, services, configmaps, secrets, nodes. Others are named: `apps` (deployments, statefulsets), `batch` (jobs, cronjobs), `rbac.authorization.k8s.io`. - **`resources`** — plural, lowercase resource names as they appear in the URL: `pods`, `deployments`. Subresources use a slash: `pods/log`, `pods/exec`, `deployments/scale`. - **`verbs`** — `get`, `list`, `watch`, `create`, `update`, `patch`, `delete`, `deletecollection`, plus non-standard ones like `impersonate`, `bind`, `escalate`. ```yaml rules: - apiGroups: [""] resources: ["pods"] verbs: ["get", "list", "watch"] ``` A `Role` is namespaced and only its rules apply inside that namespace; a `RoleBinding` binds it to subjects (User, Group, ServiceAccount). RBAC is **additive only** — permissions union across all bindings and there is no deny rule, so you remove access by removing bindings. `resourceNames` can narrow a rule to specific object names, but note it cannot restrict `list`/`watch` meaningfully.
code
yaml · 23 linesapiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: orders
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
namespace: orders
name: alice-pod-reader
subjects:
- kind: User
name: [email protected]
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.iogo deeper
Recall the triple, that "" means the core group, and that a Role needs a RoleBinding to have any effect.
Add subresources, the get-versus-list distinction, resourceNames and its list/watch limitation, and reuse of the built-in view/edit/admin ClusterRoles.
Discuss least-privilege design — narrow resourceNames for secrets, treating pods/exec as equivalent to the workload identity — and verifying with kubectl auth can-i --list.
Talk about how permission sets are authored and reviewed at scale: templated roles per workload class, generated from what the controller actually calls, with periodic audit for drift and unused grants.
## What RBAC decides Every request to the Kubernetes API server carries an authenticated identity and describes an action: a verb, an API group, a resource type, optionally a namespace and a specific object name. RBAC is the authorizer that answers yes or no. It is a pure allow-list: if no rule matches, the request is denied with `403 Forbidden`. There is no deny rule, so permissions from every applicable binding simply union together. ## The rule triple **apiGroups.** Kubernetes resources are partitioned into API groups, visible in the URL. Core resources live at `/api/v1/...` and are written as the empty string `""` in a rule — pods, services, configmaps, secrets, persistentvolumeclaims, nodes, namespaces, events, serviceaccounts. Everything else lives at `/apis/<group>/<version>/...`: `apps` (deployments, replicasets, statefulsets, daemonsets), `batch` (jobs, cronjobs), `networking.k8s.io` (ingresses, networkpolicies), `rbac.authorization.k8s.io` (roles, bindings), plus every CRD's own group. Forgetting that Deployments are in `apps` and not the core group is the single most common beginner mistake. **resources.** Use the plural, lowercase name — the same string `kubectl api-resources` prints. Subresources are separate strings with a slash and are separately grantable, which matters a great deal: - `pods/log` — read container logs - `pods/exec` and `pods/attach` — get a shell in a running container; effectively equivalent to whatever that pod can do, so treat granting them as granting the workload's identity - `pods/portforward` — tunnel to a pod's network - `deployments/scale` — change replica count without full deployment write - `*/status` — subresource used by controllers Granting `pods` does **not** grant `pods/log`; they must be listed explicitly. **verbs.** The read verbs are `get` (one named object), `list` (a collection), `watch` (a streaming list). Note that `get` alone does not let `kubectl get pods` work — that command performs a `list`. Write verbs are `create`, `update`, `patch`, `delete`, `deletecollection`. There are also special verbs that are not CRUD: `impersonate` (act as another user), `bind` and `escalate` (RBAC-specific, discussed under escalation prevention), and verbs on non-resource URLs like `/healthz` expressed via `nonResourceURLs`. `*` is legal in all three fields and means everything — appropriate for cluster-admin, almost never appropriate elsewhere. ## resourceNames A rule can be narrowed to specific object names: ```yaml - apiGroups: [""] resources: ["secrets"] resourceNames: ["app-db-credentials"] verbs: ["get"] ``` This is the right way to give a workload exactly the one Secret it needs. The catch: `list` and `watch` operate on collections, and the API server cannot filter a collection by name, so combining `resourceNames` with `list` does not produce a filtered list — the request is simply denied. Also, `create` cannot be constrained by `resourceNames` because the name does not exist yet. ## Roles and bindings Rules live in a `Role` (namespaced) or `ClusterRole` (cluster-wide definition). Neither grants anything on its own — a `RoleBinding` or `ClusterRoleBinding` attaches the rules to **subjects**: - `kind: User` and `kind: Group` — strings produced by your authenticator (certificate CN/O, OIDC claims). Kubernetes has no User object; these are just names. - `kind: ServiceAccount` — a real namespaced object, referenced with `name` and `namespace`. This is what workloads use. ## A complete example ```yaml kind: Role metadata: { namespace: orders, name: pod-reader } rules: - apiGroups: [""] resources: ["pods"] verbs: ["get", "list", "watch"] --- kind: RoleBinding metadata: { namespace: orders, name: alice-pod-reader } subjects: - kind: User name: [email protected] apiGroup: rbac.authorization.k8s.io roleRef: kind: Role name: pod-reader apiGroup: rbac.authorization.k8s.io ``` `roleRef` is immutable — to point a binding at a different role you delete and recreate it. ## Built-ins and verification Kubernetes ships default ClusterRoles you should reuse rather than reinvent: `view` (read almost everything except secrets), `edit` (write workloads, no RBAC), `admin` (edit plus RBAC within a namespace, meant for RoleBindings), `cluster-admin` (everything). Binding `view` with a RoleBinding is the usual way to grant namespace-scoped read access. Check your work with `kubectl auth can-i`: `kubectl auth can-i list pods -n orders --as [email protected]` answers yes/no, and `kubectl auth can-i --list -n orders --as ...` prints the full effective permission set.
- A Role grants get, list and watch on pods, but the user still cannot run kubectl logs. Why?Container logs are exposed through the pods/log subresource, which is a separate resource string in RBAC. Granting pods does not imply its subresources. Add "pods/log" to the resources list with the get verb. The same applies to pods/exec, pods/attach and pods/portforward, which are deliberately separate because they are far more powerful than reading pod objects.
- Why does a Role granting only the get verb fail to satisfy kubectl get pods?kubectl get pods with no name issues a collection request, which RBAC treats as the list verb; get only covers retrieving a single named object. Practically, read access should be granted as get, list and watch together — watch is needed by controllers, informers and kubectl's --watch flag.
- Can you write an RBAC rule that denies access to one specific Secret while allowing all others?No. Kubernetes RBAC is purely additive with no deny semantics, so you cannot subtract from a broader grant. You express it positively instead: grant get on secrets narrowed with resourceNames to the specific secrets that are allowed, or place the sensitive Secret in a separate namespace nobody has a binding into. If you genuinely need deny-shaped rules, that logic belongs in an admission policy, not RBAC.
A rule is a visitor badge printed with a building (apiGroup), a set of rooms (resources) and what you may do in them (verbs). The badge alone is inert; the binding is handing it to a specific person.
saying these in an interview costs you the question
- Putting deployments in apiGroups: [""] instead of "apps"
- Assuming granting pods also grants pods/log and pods/exec
- Trying to write a deny rule to carve an exception out of a broad grant
- Granting only get and wondering why kubectl get pods is forbidden
- Expecting resourceNames to filter the results of a list request