Safeguard
Container Security

How to set up Kubernetes RBAC

A step-by-step kubernetes RBAC setup guide covering Roles, RoleBindings, service accounts, least-privilege patterns, and how to verify and troubleshoot access.

Karan Patel
Cloud Security Engineer
7 min read

Misconfigured permissions are one of the most common root causes behind Kubernetes breaches — a service account with cluster-admin, a developer role that can read every secret in every namespace, a CI pipeline token that can delete production workloads. A solid kubernetes RBAC setup is the fastest way to shrink that blast radius, because Role-Based Access Control lets you define exactly who (or what) can do exactly what, on exactly which resources, in exactly which namespaces.

This guide walks through a complete kubernetes RBAC setup from first principles: confirming RBAC is active on your cluster, designing roles around job functions, writing Roles and ClusterRoles, binding them to users, groups, and service accounts, and applying least-privilege patterns that hold up under audit. By the end, you'll have a working, testable access model instead of a pile of ad-hoc permissions nobody remembers granting.

Step 1: Confirm Your Kubernetes RBAC Setup Is Enabled

Most managed Kubernetes distributions (EKS, GKE, AKS) enable RBAC by default, but it's worth verifying before you build anything on top of it. Check the API server's authorization mode:

kubectl api-versions | grep rbac.authorization.k8s.io

You should see rbac.authorization.k8s.io/v1 in the output. If you're running a self-managed cluster, confirm the API server was started with --authorization-mode=Node,RBAC (order matters — Node should come before RBAC). Without this flag, Roles and RoleBindings exist as objects but are never enforced, which gives a false sense of security.

It's also worth checking who currently has broad access before you add more:

kubectl get clusterrolebindings -o wide

Look for any binding to cluster-admin that isn't tied to a small, known set of break-glass identities. This is frequently where legacy over-provisioning hides.

Step 2: Design Roles Around Job Functions, Not People

Before writing YAML, map out the access patterns you actually need: a frontend developer debugging a staging namespace, an SRE who needs read access across the cluster plus write access to a specific set of resources, a CI/CD service account that deploys to one namespace, and a security scanner that needs read-only access everywhere. This is the design phase of any k8s role based access control tutorial that actually survives contact with production — skipping it is how teams end up copy-pasting cluster-admin bindings because it's "easier."

A useful heuristic: define roles by capability (pod-reader, deployment-manager, secret-admin) rather than by team or individual. Capabilities are stable; org charts and headcounts are not.

Step 3: Write Your First Role and ClusterRole

A Role is namespace-scoped; a ClusterRole applies cluster-wide or can be reused across namespaces via bindings. Here's a namespaced Role that only allows reading pods and logs in a staging namespace:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: staging
  name: pod-reader
rules:
  - apiGroups: [""]
    resources: ["pods", "pods/log"]
    verbs: ["get", "list", "watch"]

Apply it with:

kubectl apply -f pod-reader-role.yaml

For access that should span namespaces — say, a monitoring tool that reads node metrics cluster-wide — use a ClusterRole instead:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: node-viewer
rules:
  - apiGroups: [""]
    resources: ["nodes", "nodes/metrics"]
    verbs: ["get", "list"]

Notice that neither rule grants create, update, patch, or delete — start every role with the minimum verbs and add more only when a real, documented need arises.

Step 4: Bind Roles to Users and Groups With a RoleBinding

A Role by itself grants nothing — it has to be attached to a subject via a RoleBinding or ClusterRoleBinding. Here's a kubernetes rolebinding example that grants the pod-reader Role we just created to a specific user:

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: read-pods-staging
  namespace: staging
subjects:
  - kind: User
    name: jane.doe@company.com
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io

To bind the same Role to an entire group instead of one user — useful for onboarding whole teams without editing YAML per hire — change the subject kind:

subjects:
  - kind: Group
    name: staging-developers
    apiGroup: rbac.authorization.k8s.io

Apply the binding the same way:

kubectl apply -f read-pods-binding.yaml

If you need the node-viewer ClusterRole granted cluster-wide rather than in one namespace, use a ClusterRoleBinding instead of a RoleBinding — the difference between the two objects is scope, not syntax.

Step 5: Bind Roles to Service Accounts for Workloads

Humans aren't the only identities that need scoped access — pods, CI runners, and controllers authenticate via service accounts, and these are frequently over-permissioned by default because it's convenient during initial setup. Create a dedicated service account per workload rather than reusing default:

kubectl create serviceaccount ci-deployer -n staging

Bind it to a role that only allows what the deployment pipeline actually does — creating and updating Deployments, not reading Secrets across the cluster:

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: ci-deployer-binding
  namespace: staging
subjects:
  - kind: ServiceAccount
    name: ci-deployer
    namespace: staging
roleRef:
  kind: Role
  name: deployment-manager
  apiGroup: rbac.authorization.k8s.io

Then reference the service account explicitly in your workload spec (spec.serviceAccountName: ci-deployer) so the pod never falls back to the namespace's default identity.

Step 6: Apply RBAC Least Privilege Best Practices

With the mechanics in place, harden the model. These are the rbac least privilege best practices that separate a working RBAC setup from one that's actually resilient:

  • Avoid wildcards. resources: ["*"] or verbs: ["*"] defeats the purpose of scoping roles — enumerate exactly what's needed.
  • Never bind to cluster-admin for routine work. Reserve it for a small, logged, MFA-protected break-glass identity.
  • Separate read from write. A role that lists Secrets should rarely also be able to modify them.
  • Scope by namespace by default. Reach for ClusterRole/ClusterRoleBinding only when access genuinely needs to span namespaces.
  • Set expirations on temporary access. Use short-lived bindings or an access-request workflow for elevated privileges instead of permanent grants.
  • Review bindings on a cadence. Roles drift as teams reorganize and projects wind down; stale bindings are the most common source of unnecessary privilege in older clusters.

Verifying and Troubleshooting Your RBAC Setup

Before trusting any role in production, verify it behaves the way you intended using kubectl auth can-i, which impersonates a subject and checks a specific action:

kubectl auth can-i delete pods --namespace staging --as jane.doe@company.com
kubectl auth can-i list secrets --namespace staging --as system:serviceaccount:staging:ci-deployer

A yes where you expected no almost always traces back to one of these causes:

  • A broader binding elsewhere. RBAC permissions are additive — check for a second RoleBinding or a ClusterRoleBinding granting the same subject extra access via a different role.
  • Wrong subject kind or name. User, Group, and ServiceAccount subjects are matched exactly; a typo in the service account's namespace silently produces no effective binding rather than an error.
  • Aggregated ClusterRoles. If you're using the rbac.authorization.k8s.io/aggregate-to-* labels, a role can inherit rules from other ClusterRoles in ways that aren't obvious from reading a single manifest.
  • Caching. The API server's authorization decisions are near-real-time, but client-side kubeconfig caching or stale tokens can make a recent change appear not to have taken effect — re-authenticate before assuming RBAC itself is broken.

To get a full picture of what a subject can do rather than checking one verb at a time, tools like kubectl-who-can (a krew plugin) or rbac-lookup list every role and binding that grants a given permission across the cluster, which is far more reliable than manually tracing YAML.

How Safeguard Helps

Writing correct Roles and RoleBindings is only half the job — the harder problem is knowing when that carefully designed model drifts. Safeguard continuously monitors your Kubernetes environments for RBAC misconfigurations as part of its software supply chain security platform: overly broad ClusterRoleBindings, service accounts with unused cluster-admin access, wildcard verbs sitting undetected in a Role that was meant to be temporary, and CI/CD identities that have accumulated permissions well beyond what their pipelines actually use.

Because Safeguard correlates RBAC posture with the rest of your supply chain — build pipelines, container images, and deployed workloads — it can flag the specific service accounts and bindings that represent real exposure, not just theoretical over-permissioning, and prioritize remediation accordingly. That turns RBAC from a one-time setup exercise into a continuously enforced least-privilege baseline, which is what actually holds up as clusters, teams, and pipelines keep changing.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.