Blog
May 23, 2017 Marie H.

Kubernetes RBAC — Locking Down Cluster Access the Right Way

Kubernetes RBAC — Locking Down Cluster Access the Right Way

Photo by <a href="https://unsplash.com/@packetdiscards?utm_source=cloudista&utm_medium=referral" target="_blank" rel="noopener">Andy Kennedy</a> on <a href="https://unsplash.com/?utm_source=cloudista&utm_medium=referral" target="_blank" rel="noopener">Unsplash</a>

Kubernetes 1.6 shipped with RBAC enabled by default, which meant a lot of people upgrading clusters suddenly had to care about access control for the first time. I was one of them. If you've been hand-waving at --authorization-mode=AlwaysAllow or just binding everything to cluster-admin, this post is for you. Let's fix that.

What RBAC Actually Is

Role-Based Access Control lets you define what actions are allowed on what resources, then attach those permissions to users, groups, or service accounts. Before 1.6 you could lock things down, but it wasn't on by default and a lot of clusters ran wide open internally.

The four objects you need to understand:

  • Role — permissions scoped to a single namespace
  • ClusterRole — permissions that apply cluster-wide (or reused across namespaces)
  • RoleBinding — binds a Role to a subject (user, group, or service account) within a namespace
  • ClusterRoleBinding — binds a ClusterRole cluster-wide

For most real-world access control, you want Role + RoleBinding. ClusterRole is for things that genuinely need cluster-wide reach — reading nodes, managing PersistentVolumes, that kind of thing.

The Common Mistake

I see this constantly: someone hits a permissions error, Googles the fix, and applies this:

kubectl create clusterrolebinding my-app --clusterrole=cluster-admin --serviceaccount=default:default

It works! And now everything in your cluster can do anything to anything. Don't do this. It's the RBAC equivalent of chmod 777.

Creating a Read-Only Role for a Namespace

Here's a realistic starting point — a role that lets a service account list and read pods and deployments in a specific namespace, nothing else:

# role-readonly.yaml
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
  namespace: my-app
  name: pod-reader
rules:
- apiGroups: [""]
  resources: ["pods", "pods/log"]
  verbs: ["get", "list", "watch"]
- apiGroups: ["apps", "extensions"]
  resources: ["deployments"]
  verbs: ["get", "list", "watch"]

Apply it:

kubectl apply -f role-readonly.yaml

Service Account + RoleBinding

Now create a service account and bind the role to it:

# serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: ci-reader
  namespace: my-app
# rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
  name: ci-reader-binding
  namespace: my-app
subjects:
- kind: ServiceAccount
  name: ci-reader
  namespace: my-app
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io
kubectl apply -f serviceaccount.yaml
kubectl apply -f rolebinding.yaml

Real-World Example: CI/CD System

My CI system needs to deploy to the my-app namespace — it runs kubectl set image to update deployments and kubectl rollout status to wait for them to finish. That's it. Here's the role I give it:

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
  namespace: my-app
  name: deployer
rules:
- apiGroups: ["apps", "extensions"]
  resources: ["deployments"]
  verbs: ["get", "list", "watch", "update", "patch"]
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list", "watch"]

The CI service account gets this role via a RoleBinding, scoped to my-app only. It can't touch anything in kube-system, can't read secrets, can't delete anything. Least privilege, applied.

Verifying Permissions

kubectl auth can-i is your best friend for testing RBAC without actually breaking anything:

# Can the ci-reader service account list pods in my-app?
kubectl auth can-i list pods \
  --namespace my-app \
  --as system:serviceaccount:my-app:ci-reader
# yes

# Can it delete pods?
kubectl auth can-i delete pods \
  --namespace my-app \
  --as system:serviceaccount:my-app:ci-reader
# no

# List all permissions for a service account in a namespace
kubectl auth can-i --list \
  --namespace my-app \
  --as system:serviceaccount:my-app:ci-reader

That last command gives you the full picture of what a subject can do. I run it after every RBAC change to make sure I haven't accidentally granted more than intended.

Wrapping Up

RBAC isn't complicated once you internalize the Role/ClusterRole and RoleBinding/ClusterRoleBinding split. Start with the minimum permissions your workload actually needs, scope to the namespace it lives in, and use kubectl auth can-i to verify. The cluster-admin binding is always right there waiting for you to cave — resist it.