20.8 Common RBAC Patterns: Read-Only, Namespace Admin, CI/CD Bot
Alright, let’s talk about the three roles you’ll actually use. You can read all the RFCs and design docs you want, but in the real world, 90% of your RBAC needs boil down to these three patterns. They’re the workhorses. Get these right, and you’ve basically won.
The Read-Only Viewer
This is your go-to for anyone who needs to see what’s going on but shouldn’t be able to change a single byte. Think auditors, support teams, or your manager who keeps asking “what’s running in the staging cluster?” You want to give them get, list, and watch on (almost) everything. The key here is to be explicit. Don’t just grant them view access cluster-wide; that default role is a sledgehammer that often includes seeing Secrets, which is a spectacularly bad idea.
Here’s how you build a precise, safe Read-Only role for a specific namespace. We use a ClusterRole so it can be reused across namespaces, and a RoleBinding to tie it to a user or group within a single namespace.
# clusterrole-readonly-precise.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: precise-readonly
rules:
- apiGroups: [""] # The core API group, think 'v1' in 'api/v1'
resources:
- pods
- services
- configmaps
- persistentvolumeclaims
- replicationcontrollers
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources:
- deployments
- daemonsets
- statefulsets
- replicasets
verbs: ["get", "list", "watch"]
- apiGroups: ["batch"]
resources:
- jobs
- cronjobs
verbs: ["get", "list", "watch"]
# Notice what we're NOT including: secrets, serviceaccounts, roles, rolebindings.
# rolebinding-readonly.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: readonly-access
namespace: application-staging # Apply this ONLY in the namespace you want
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: precise-readonly # This is the ClusterRole we made above
subjects:
- kind: User
name: "alice@example.com"
apiGroup: rbac.authorization.k8s.io
Pitfall: The most common mistake is forgetting that get and list are separate verbs. You need both for a user to be able to list all resources and then get the details of a specific one. Also, watch out for custom resources (CRDs); you’ll need to add another rule for those if your read-only users need to see them.
The Almighty Namespace Admin
This is the “I own this piece of the cluster” role. It’s for your product team leads who need full control over their namespace but shouldn’t be able to poke the cluster-scoped stuff (like Nodes or PersistentVolumes). They can do anything inside their namespace: create Pods, delete Deployments, even create Roles and RoleBindings within that same namespace. This last part is crucial—it allows them to manage their own access controls, which is a huge win for you, the platform operator.
We build this using a ClusterRole again (for reusability) but bind it with a RoleBinding to keep its power namespace-scoped.
# clusterrole-namespace-admin.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: namespace-admin
rules:
- apiGroups: ["*"] # Yes, everything. Even future ones. Wild.
resources: ["*"] # Every resource in the API group.
verbs: ["*"] # Every verb imaginable.
- apiGroups: [""]
resources: ["namespaces"] # They need to be able to 'get' their own namespace
verbs: ["get"]
resourceNames: ["application-production"] # Lock it down to just their namespace
# rolebinding-namespace-admin.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: namespace-admin-access
namespace: application-production # The kingdom they rule
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: namespace-admin
subjects:
- kind: User
name: "bob@example.com"
apiGroup: rbac.authorization.k8s.io
Why the weird rule for namespaces? Without it, your admin can’t even run kubectl get ns to see the namespace they’re an admin of. It’s one of those quirky, annoying bits of RBAC. The resourceNames constraint is a best practice—it prevents them from using a wildcard to view all namespaces, which is information you might want to restrict.
The CI/CD ServiceAccount
This is the robot that deploys your code. It needs enough power to update Deployments, create Services, and maybe manage Ingresses, but it absolutely should not be able to read Secrets or escalate its own privileges. This is where least privilege becomes a religion. You create a dedicated ServiceAccount in the namespace and give it a tightly scoped role.
First, create the ServiceAccount itself. It’s an identity, not a permission set.
# serviceaccount-cicd.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: cicd-bot
namespace: application-staging
Now, create a Role that defines exactly what this bot can do. No more.
# role-cicd-bot.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: cicd-deployer
namespace: application-staging
rules:
- apiGroups: ["apps"]
resources: ["deployments", "replicasets"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["services", "pods"] # 'pods' is often needed for log streaming or exec in CI
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
# Notice: No 'secrets' here. Your CI system should handle that, not the in-cluster bot.
Finally, bind the Role to the ServiceAccount.
# rolebinding-cicd-bot.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: cicd-bot-access
namespace: application-staging
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: cicd-deployer
subjects:
- kind: ServiceAccount
name: cicd-bot
namespace: application-staging # This is critical. You must specify the namespace for the SA.
Critical Gotcha: When your Jenkins or GitLab Runner pod runs, it needs to be configured to use this ServiceAccount. This is done in the Pod spec. And remember, a Pod can only use a ServiceAccount that exists in its own namespace. So if your CI pod runs in a ci namespace but needs to deploy to an app namespace, you’ll need a more complex setup involving a RoleBinding in the app namespace that grants roles to the ServiceAccount from the ci namespace. It’s a bit of a mind-bender, but it’s how you keep things secure.