RBAC: Least Privilege in Practice

Real RBAC hygiene means scoping ServiceAccounts to a namespace, granting only the verbs a workload needs, and never binding cluster-admin to an app.

RBAC is easy to get working (grant everything) and hard to get right (grant exactly enough). The senior habit is to start from zero and add only the verbs a workload actually calls.

The four objects

  • Role — a set of permissions within one namespace.
  • ClusterRole — the same, but cluster-wide or for cluster-scoped resources.
  • RoleBinding — grants a Role (or ClusterRole) to a subject in one namespace.
  • ClusterRoleBinding — grants cluster-wide. Use sparingly; this is where over-permissioning hides.

Rules of thumb

  1. Give every workload its own ServiceAccount — never let Pods use the namespace default SA.
  2. Prefer a namespaced Role + RoleBinding over ClusterRole. If an app only reads ConfigMaps in its own namespace, that is all it should be able to do.
  3. List explicit verbsget, list, watch for read-only. Avoid the * wildcard on verbs and resources.
  4. Set automountServiceAccountToken: false on Pods that never call the Kubernetes API, so a compromised container has no token to steal.

Audit what a subject can do

kubectl auth can-i is your fastest sanity check — it answers yes/no for any verb, resource, and ServiceAccount without trial and error.

Example

Example · yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: config-reader
  namespace: prod
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role                      # namespaced, not ClusterRole
metadata:
  namespace: prod
  name: read-configmaps
rules:
  - apiGroups: [""]
    resources: ["configmaps"]
    verbs: ["get", "list", "watch"]   # read-only, no wildcards
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  namespace: prod
  name: config-reader-binding
subjects:
  - kind: ServiceAccount
    name: config-reader
    namespace: prod
roleRef:
  kind: Role
  name: read-configmaps
  apiGroup: rbac.authorization.k8s.io

When to use it

  • A CI robot ServiceAccount is given a Role that can only update Deployment images in its namespace, preventing it from reading Secrets or creating new resources.
  • A team audits its RBAC configuration with kubectl auth can-i --list to identify ServiceAccounts with excessive permissions before a security review.
  • A developer creates a separate read-only ClusterRole for external monitoring tools so they can list pods and metrics without write access.

More examples

Minimal CI deploy Role

Creates a minimal Role that allows a CI robot to update Deployments and read pods in production, nothing more.

Example · yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: ci-deployer
  namespace: production
rules:
  - apiGroups: [apps]
    resources: [deployments]
    verbs: [get, list, patch, update]
  - apiGroups: [""]     # Only read pods, not write
    resources: [pods]
    verbs: [get, list]

Audit permissions for a ServiceAccount

Audits all permissions held by the ci-robot ServiceAccount and verifies it cannot perform a sensitive operation like deleting secrets.

Example · bash
kubectl auth can-i --list \
  --as=system:serviceaccount:production:ci-robot
# Resources  Verbs
# deployments  get list patch update
# pods  get list

# Check a specific action
kubectl auth can-i delete secrets \
  --as=system:serviceaccount:production:ci-robot
# no

Bind Role to ServiceAccount

Binds the ci-deployer Role to the ci-robot ServiceAccount using a namespaced RoleBinding in the production namespace.

Example · yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: ci-robot-binding
  namespace: production
subjects:
  - kind: ServiceAccount
    name: ci-robot
    namespace: production
roleRef:
  kind: Role
  name: ci-deployer
  apiGroup: rbac.authorization.k8s.io

Discussion

  • Be the first to comment on this lesson.