RBAC — Four Objects and One Trap
Summary
RBAC is a model that separates "what can be done" (Role) from "who can do it and where" (Binding), and the side that decides the scope is always the Binding.
Why this matters
If you write all permissions in one file, two things collapse immediately. You have to copy the same bundle of permissions every time the number of people grows, and nobody can trace what permission reaches how far. RBAC splits the two apart. The content of a permission is defined by a Role or ClusterRole, and to whom and in what scope that content is granted is decided by a RoleBinding or ClusterRoleBinding.
| Object | Scope | What it does |
|---|---|---|
| Role | Namespace | Rules for resources in that namespace |
| ClusterRole | Cluster | Cluster-scoped resources, or common permissions to be reused |
| RoleBinding | Namespace | Gives a role to a subject, in this namespace only |
| ClusterRoleBinding | Cluster | Gives a role to a subject, across the whole cluster |
How it works
A single rule has three pieces: apiGroups, resources, and verbs.
rules:
- apiGroups: [""] # 코어 그룹은 빈 문자열
resources: ["pods"]
verbs: ["get", "list", "watch"]
Inquiries saying "I can't see Pods" because apiGroups: [""] was left out still come in every week. The name of the core API group is an empty string, and things like Deployment are in the apps group.
Here comes the most important trap. If you attach a ClusterRole with a RoleBinding, that permission is valid only in that namespace. That is, it is the kind of binding, not the kind of role, that decides the scope. This combination is extremely useful in practice. You define a common bundle of permissions such as "read-only" once as a ClusterRole, and each team attaches it with a RoleBinding in its own namespace. If the same ClusterRole had been attached with a ClusterRoleBinding, it becomes cluster-wide permission at that moment. The difference between the two manifests is one word.
The second thing often missed is subresources. pods/log and pods/exec are resource names separate from pods. Having permission to read Pods does not let you read logs, and having permission to read logs does not let you attach to a shell. This is why a design is possible in which a log-collection bot gets only pods/log, and only its get, and conversely it also means that leaving pods/exec open to anyone lets them do anything inside a container.
The third is how to check. A permission is not what you wrote but what is judged, so you should ask rather than read it with your eyes.
kubectl auth can-i list pods -n rbac-lab \
--as=system:serviceaccount:rbac-lab:app-reader
# yes
--as is impersonation. As long as you know that the formal name format of a service account is system:serviceaccount:<네임스페이스>:<이름> (namespace and name), you can impersonate any subject and ask. You have to check both what works and what does not for it to mean anything. If you check only yes, you cannot tell even when the permission is broader than necessary.
What it looks like in the field
First, once a wildcard gets in, it does not come out. verbs: ["*"] is convenient, but when new resources appear later, the permission widens automatically without anyone knowing. Least privilege is not a matter of taste but a mechanism that keeps the scope from growing over time.
Second, get secrets is effectively permission to view credentials. It is common for edit permission granted for convenience to a developer to become the same as permission to view the production database password. This story continues in the next module.
Third, periodic audits. The list of ClusterRoleBindings with cluster-admin attached, the roles that use wildcards, the roles that can read secrets — these three questions should be asked of every cluster periodically. A few lines of kubectl get clusterrolebindings -o json | jq give the answer, and this one habit greatly slows the rate at which permission debt builds up.
How to find out why a permission does not work
You can ask about RBAC problems rather than guess. kubectl auth can-i asks the API server directly for a judgment.
kubectl auth can-i create pods -n prod
kubectl auth can-i list secrets -n prod --as=system:serviceaccount:prod:app
kubectl auth can-i --list --as=system:serviceaccount:prod:app -n prod
The last line is especially useful. It shows all the permissions that subject has in a table.
You must write the target exactly. A service account's name is system:serviceaccount:<네임스페이스>:<이름> (namespace and name). A human account comes from the certificate's CN or the OIDC claims. To impersonate with --as, that itself requires the impersonate permission.
Rules only add up. RBAC has no deny. If a permission is too broad, something somewhere is adding it, so you have to find the binding.
kubectl get rolebinding,clusterrolebinding -A -o json | jq -r '
.items[] | select(.subjects[]? .name=="app")
| "\(.kind) \(.metadata.namespace // "-")/\(.metadata.name) -> \(.roleRef.name)"'
Four things that are often confused.
- A
ClusterRolebound with aRoleBindingis valid only inside that namespace. It is the normal way to reuse the same role in several namespaces. - Resources that have no namespace, such as nodes, PVs, and namespaces, need a
ClusterRoleBinding. - Subresources are written separately.
pods/log,pods/exec, anddeployments/scaleare not included inpodsanddeployments. - The
createpermission alone cannot block a specific name.resourceNamesworks only for verbs that need to know the name (get, update, delete).
Do not forget what is attached by default. Every authenticated user can see the API list through system:discovery and others, and the service account token mounted in a Pod can reach the API server even with no permissions. If it is not needed, turn it off with automountServiceAccountToken: false.
What to do in the next lab
You create a read-only Role, bind it to a service account, and check the boundary with auth can-i. You create a ClusterRole for a cluster-scoped resource such as nodes, attach the same ClusterRole with a RoleBinding, and see for yourself how the scope narrows. You create a role that opens only a subresource, and at the end you build an audit report, as JSON, that sweeps the cluster for dangerous permissions.