query can delete secrets subjects found 7 via group membership 4 of them opinion, unless asked none

Kubernetes · OpenShift · RBAC · access review

rbacspy

Who can delete secrets in production — and can you prove it?

Kubernetes RBAC is easy to write and nearly impossible to read back. rbacspy answers the question you actually have — who can do this, and how — by tracing every binding, role and group membership that grants it, and printing the exact path for each subject.

$go install github.com/vikozs/rbacspy/cmd/rbacspy@latest
Source on GitHub Download a binary

Why

Permissions arrive from four directions at once.

A subject's access is the sum of every RoleBinding and ClusterRoleBinding that names it, each pointing at a Role or ClusterRole, each widened by wildcards, and each reaching people through Group membership synced from your identity provider.

To answer one simple question — who can actually do this? — you have to trace all of that by hand. kubectl auth can-i --list answers it for one subject you already suspect; it will not enumerate every subject that can, and it will not tell you how. For an access review, the how is the whole point: you cannot remove access you cannot locate.

The query

$ rbacspy can delete secrets -n payments7 grant paths
KindSubjectScopeVia
Groupplatform-adminscluster-wideClusterRoleBinding/admins → ClusterRole/cluster-admin
Useralicecluster-widevia Group/platform-admins → cluster-admin
Userbobcluster-widevia Group/platform-admins → cluster-admin
ServiceAccountargocd/…-controllercluster-wideClusterRoleBinding → cluster-admin
Usercontractor-jonespaymentsRoleBinding/payments/contractor-edit → ClusterRole/edit

The last column is the point. Knowing that someone has access isn't enough for an audit — you need to show how, so you can remove exactly the right binding.

The hop nobody sees

grant path
can delete secrets
User · alice
↳ via Group · platform-admins
↳ bound by ClusterRoleBinding · admins
↳ to ClusterRole · cluster-admin  (*:*)

alice was never named in a binding. She can delete secrets in every namespace because she is a member of a group that a ClusterRoleBinding grants cluster-admin — a fact spread across three objects and an identity provider. rbacspy resolves OpenShift Group membership, so the person who only ever gets access through a group still appears, with via Group/… in the path.

And it was asked, who among them holds the keys? And the answer was not written on any one door, but scattered: a name in a group, a group in a binding, a binding upon the master key; and only he who followed the whole chain could say, with proof, who might enter in.

Two modes

Neutral — report who can do what

No opinions, just the facts of the RBAC graph. Good for answering questions and for producing evidence an auditor will accept.

rbacspy can delete secrets
rbacspy can delete secrets -n payments
rbacspy can '*' secrets      # anyone,
                             # any verb
rbacspy subjects             # full
                             # inventory

Opinionated — lint against conventions

Off by default. Turn it on and rbacspy flags deviations from a least-privilege, GitOps-run posture: humans with write access, individual-user bindings, wildcard roles, and every cluster-admin grant.

rbacspy audit --policy \
  --allow-writers \
  ServiceAccount:argocd/app-controller

The lint, when you ask for it

$ rbacspy audit --policy4 rules
SeverityRuleSubject / objectIssue
CRITICALcluster-admin-grantGroup:platform-adminsA group of people is bound to cluster-admin
WARNINGhuman-write-accessUser:contractor-jonesA human can modify resources directly, not via CD
WARNINGwildcard-roleClusterRole:cluster-adminGrants all verbs on all resources
INFOuser-direct-bindingUser:contractor-jonesBind a synced Group, not the individual

The defaults encode a common regulated posture — humans read-only, changes through a CD service account, groups over users. If yours differ, the neutral mode still gives you everything; the policy layer is opt-in and its allow-list is yours.

Why the path is evidence

Segregation-of-duties isn't a claim. It's something you demonstrate.

Least-privilege and separation-of-duties controls — Solvency II, ISO 27001, SOC 2 — don't just ask whether access is limited. They ask you to show it, subject by subject, with the reason each person has what they have.

rbacspy can <verb> <resource> -o json produces exactly that: a grant list with the binding chain for each subject. It is the artifact the review wants, and the artifact kubectl won't assemble across every subject at once.

How it behaves

It never writes to your cluster

There is no mutating command in the tool at all. It reads RBAC objects — Roles, ClusterRoles, bindings, and OpenShift Groups — through your existing oc or kubectl, and reasons about them locally. No operator, no client-go skew, nothing to deploy.

  • Writesnone — read-only by construction
  • Installone static binary
  • PlatformsLinux, macOS, Windows · amd64, arm64
  • LicenceApache-2.0

Review access you can't reach live

--from runs the same queries and the same lint against a directory of JSON dumps — no credentials, no live connection. Hand a reviewer a bundle; they answer "who can do X" without touching the cluster.

mkdir dump
for r in roles clusterroles rolebindings \
         clusterrolebindings groups; do
  oc get $r -o json > dump/$r.json
done

rbacspy can delete secrets --from dump
rbacspy audit --policy --from dump