TT Lab
Get started
Learn Learning paths Courses

Kubernetes Operations

RBAC — Four Objects and One Trap

Continue in TT Lab

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.

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.