# Kubernetes RBAC least-privilege starter guide

## TL;DR

Nobody gets cluster-admin by default: audit existing bindings, replace broad ClusterRoles with namespaced Roles that grant only the verbs each workload needs, bind them to specific ServiceAccounts, and verify with kubectl auth can-i. RBAC denies by default, so the safe direction is always "grant less, then add what breaks."

```text
Kubernetes RBAC least-privilege starter guide
```

## Use this when

- You are locking down a new or inherited cluster
- An audit flags cluster-admin bindings or wildcard verbs
- A team shares a cluster and needs namespace-scoped access
- CI deploys need just enough permission to ship

## Not for this skill when

- You need network policies or pod security standards (different controls)
- You are provisioning the cluster itself
- The question is about cloud IAM outside the cluster

## Steps

### 1. Audit who has what today

List clusterrolebindings and rolebindings and look for the usual suspects: cluster-admin granted to humans, to CI service accounts, or to the default service account in a namespace.

```bash
kubectl get clusterrolebindings -o custom-columns=NAME:.metadata.name,ROLE:.roleRef.name,SUBJECTS:.subjects[].name | sort -t: -k2
```

Expected: a sorted list of bindings. Anything bound to cluster-admin that isnt the actual cluster bootstrap admin goes on the remediation list.

### 2. Check what a workload can actually do

Pick a ServiceAccount and ask the API what it permits. This is the ground truth; the YAML you think is applied may not be.

```bash
kubectl auth can-i --list --as=system:serviceaccount:[namespace]:[sa-name]
```

Expected: a list of allowed verbs and resources. Wildcards (`*`) or broad verbs (delete, `*`) on broad resources are the findings; note them before changing anything.

### 3. Create a namespaced Role with minimal verbs

Grant only what the workload needs: typically get and list on the resources it reads, plus whatever verbs its actual job requires. Create it imperatively to avoid hand-writing YAML wrong.

```bash
kubectl create role [app]-reader --verb=get,list,watch --resource=pods,services,configmaps -n [namespace]
```

Expected: the Role is created in that namespace only. Namespaced Roles cant touch other namespaces or cluster resources, which is exactly the point.

### 4. Bind the Role to the specific ServiceAccount

RoleBindings connect the Role to the subject, scoped to the same namespace. Bind the app's own ServiceAccount, never the default one.

```bash
kubectl create rolebinding [app]-reader-binding --role=[app]-reader --serviceaccount=[namespace]:[sa-name] -n [namespace]
```

Expected: binding created. Verify with the can-i check from step 2; the allowed list should now show exactly the verbs you granted and nothing else.

### 5. Remove the broad bindings and re-verify

Delete the cluster-admin or wildcard bindings you flagged in step 1, one at a time, verifying the workload still works after each. If something breaks, you granted too little; add the specific verb back rather than restoring the broad binding.

```bash
kubectl delete clusterrolebinding [overly-broad-binding] && kubectl auth can-i --list --as=system:serviceaccount:[namespace]:[sa-name]
```

Expected: the binding is gone and the workload's permission list is unchanged (it was using the narrow Role all along). If the app errors, check its logs for forbidden messages naming the exact verb and resource it needs.

### Variant: k8s rbac tutorial from zero

The mental model: Roles grant permissions, RoleBindings attach them to subjects, ClusterRoles and ClusterRoleBindings are the cluster-wide versions. Start namespaced; reach for cluster scope only when the resource genuinely is cluster-scoped (nodes, namespaces, CRDs).

### Variant: least privilege serviceaccount for CI deploys

CI needs to apply manifests in specific namespaces: a Role with get, list, create, update, patch, delete on the resource types it manages, in those namespaces only. No secrets read unless the pipeline actually needs them; most dont.

### Variant: remove cluster-admin from developers

Give devs admin within their namespaces (the admin ClusterRole bound via a namespaced RoleBinding) instead of cluster-admin. They keep full control of their workloads and lose the ability to touch other teams or cluster config.

## Why this happens

Kubernetes defaults and quickstart guides hand out cluster-admin because it makes demos work, and nobody revisits it. RBAC is deny-by-default, which means every permission in the cluster was granted by someone, usually during a late-night debug session. Least privilege is just going back and asking whether each grant is still the smallest one that works.

## Edge cases and pitfalls

- Aggregated clusterroles: some managed providers aggregate extra rules into view and edit; check the effective rules, not just the name.
- The default service account: every namespace has one and workloads use it unless told otherwise; never bind powerful roles to it.
- Impersonation headers: anyone who can impersonate can become anyone; treat impersonate as equivalent to cluster-admin.
- Breaking the monitoring: tightening too fast breaks Prometheus or the ingress controller; do those system namespaces last and watch dashboards.
- can-i vs reality: can-i checks the current auth state; cached or aggregated rules can lag by a minute after changes.
- GitOps drift: if a GitOps tool manages RBAC, hand-edits get reverted; make the changes in the repo, not with kubectl.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_pwjQjdIwAU3glRlY-CoKkg
