Kubernetes RBAC Viewer

Paste your Roles, ClusterRoles and their bindings, and get the answer per subject: what can this service account actually do, which binding gave it that, and does any of it lead to cluster-admin or to reading credentials.

Paste below, or drop a file anywhere on this panel

Or drop a file anywhere on this panel. Nothing is uploaded: the analysis runs in this tab.

The answer appears here

Paste on the left and press Resolve. Nothing leaves this tab.

Examples

Real input you can load into the tool above. Each one shows a different thing going wrong, because that is what the tool is for.

cluster-admin in all but name

Wildcards on groups, resources and verbs, which is every permission there is

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: admin
rules:
  - apiGroups: ["*"]
    resources: ["*"]
    verbs: ["*"]

A scoped Role

Read-only access to one resource, which is what most application accounts need

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: reader
  namespace: app
rules:
  - apiGroups: [""]
    resources: ["configmaps"]
    verbs: ["get", "list"]

Common mistakes

These are the ones that fail silently. The config is accepted, nothing raises an error, and the consequence arrives later.

  1. Granting a wildcard verb to save time

    verbs: ["*"] includes delete, deletecollection and impersonate. Impersonate in particular lets the holder act as any user, which is privilege escalation with extra steps.

    Instead:List the verbs. get, list and watch cover most read use.

  2. Using a ClusterRoleBinding where a RoleBinding was meant

    A ClusterRoleBinding grants the role in EVERY namespace. The distinction is one word and the blast radius is the whole cluster.

    Instead:RoleBinding for namespace scope, and check which one you wrote.

  3. Forgetting that secrets are readable by list

    get on a named Secret is narrow; list on Secrets returns every Secret's contents in the response. They are very different grants.

    Instead:Avoid list on Secrets. Use get with resourceNames where possible.

What it does

Reading one Role tells you very little. RBAC only becomes legible once the bindings are resolved and you can ask the question by subject.

Bindings resolved, not roles read

A ClusterRole grants nothing until something binds it, and a RoleBinding can reference a ClusterRole to scope its rules to one namespace. Paste the roles and the bindings together and you get the answer per subject: what can this service account actually do, and which binding gave it that.

The permissions that do not look dangerous

Nobody writes verbs: ["*"] by accident. They grant create on pods, which lets the holder mount any Secret in the namespace and run as a more privileged service account. Or create on Deployments, which is the same thing one step removed. Those paths are the ones worth finding.

Routes to cluster-admin, named

impersonate on groups reaches system:masters. escalate removes the check that stops you granting what you do not hold. bind attaches cluster-admin to anyone. serviceaccounts/token mints credentials for a more privileged account. Each is reported with the binding that granted it.

It does not invent findings

A rule narrowed with resourceNames is a materially smaller grant and is not reported as reading every Secret. A role nothing binds raises no security finding at all, because it grants nobody anything: it is reported as unbound instead, which is usually a missing binding or dead config.

How Kubernetes RBAC actually resolves

Four object types, and the thing that makes RBAC confusing is that permissions live in one pair and identity lives in the other. A role is a list of rules with nobody attached. A binding attaches subjects to a role. Nothing grants anything until both exist.

Four objects, two axes

Role and RoleBinding are namespaced. ClusterRole and ClusterRoleBinding are cluster-wide. The combination that catches people is the third one: a RoleBinding may reference a ClusterRole, which applies that ClusterRole's rules but only inside the binding's namespace. That is the intended way to reuse a role definition across namespaces without granting it everywhere.

RoleBinding        -> Role           rules apply in that namespace
RoleBinding        -> ClusterRole    rules apply in that namespace only
ClusterRoleBinding -> ClusterRole    rules apply everywhere
ClusterRoleBinding -> Role           not allowed

Permissions are purely additive

There is no deny rule in Kubernetes RBAC. A subject's permissions are the union of every binding that names them, and nothing subtracts. This is the opposite of AWS IAM, and it means you cannot fix an over-broad grant by adding a narrower one: the broad one has to be removed or replaced.

Subresources are separate resources

pods/exec, pods/log, pods/portforward and serviceaccounts/token are resources in their own right, not verbs on their parent. Granting get on pods does not grant pods/log, and: much more importantly: a rule written as resources: ["pods"] with verbs: ["*"] does not include exec. This cuts both ways: it is why least-privilege roles need subresources listed explicitly, and why a wildcard on resources is so much broader than it looks.

resources: ["pods"]        verbs: ["*"]   -> no exec
resources: ["pods/exec"]   verbs: ["create"] -> exec
resources: ["*"]           verbs: ["*"]   -> everything, including exec

The escalation prevention, and the two verbs that remove it

The API server normally refuses to let you create a role granting permissions you do not hold yourself, which stops a limited account writing itself an admin role. The escalate verb turns that check off. The bind verb lets the holder attach an existing role: cluster-admin included: to any subject. Either one converts a limited account into an unlimited one in a single call, and both exist mainly so that controllers can manage RBAC on your behalf.

Why an empty apiGroup means core

The core API group: Pods, Services, Secrets, ConfigMaps, ServiceAccounts: has an empty string as its name. A rule listing resources without apiGroups: [""] silently matches nothing, which produces a role that looks correct in review and grants nothing at runtime. It is the most common reason a hand-written role appears not to work.

- apiGroups: [""]                          core: pods, secrets, services
  resources: ["secrets"]
  verbs: ["get"]

- apiGroups: ["apps"]                      deployments, statefulsets
- apiGroups: ["rbac.authorization.k8s.io"] roles, bindings