Happy Dussehra! May good always win. Happy Diwali! Wishing you a bright year ahead. Merry Christmas! Warm wishes from Vizag. Happy New Year! Here’s to a great year ahead.

Archive · Kubernetes

RBAC and Service Accounts

Control who and what can do which actions in a Kubernetes cluster: Roles, bindings, service accounts, token behaviour in 1.22, and an audit routine.

4 min read

  • Kubernetes 1.22
  • rbac.authorization.k8s.io/v1
  • ServiceAccount
  • kubectl auth can-i

Anyone who can reach the Kubernetes API with valid credentials can do whatever RBAC (role-based access control) allows. Defaults matter: in a cluster where everything runs as cluster-admin, one leaked token is a full compromise. RBAC lets you grant exactly the verbs on exactly the resources each person or workload needs.

Versions in use on this date. Kubernetes 1.22, where bound service account tokens are stable: pods now receive a time-limited, audience-bound token that is refreshed by the kubelet, instead of a never-expiring Secret-based token. Only rbac.authorization.k8s.io/v1 exists; the v1beta1 version was removed in 1.22.

The model

flowchart TB
  subgraph WHO[Subjects - who]
    U[User from OIDC or certificate]
    G[Group]
    SA[ServiceAccount in a namespace<br/>used by pods]
  end
  subgraph WHAT[Permissions - what]
    R[Role<br/>namespaced rules]
    CR[ClusterRole<br/>cluster-wide or reusable rules]
  end
  RB[RoleBinding<br/>grants within one namespace] --> R
  RB --> CR
  CRB[ClusterRoleBinding<br/>grants in all namespaces] --> CR
  U --> RB
  G --> RB
  SA --> RB
  SA --> CRB
  REQ[API request] --> AUTHN[Authentication] --> AUTHZ{RBAC allows verb on resource?}
  AUTHZ -->|yes| ADM[Admission controllers] --> STORE[etcd]
  AUTHZ -->|no| DENY[403 Forbidden]

RBAC is additive: there are no deny rules. A subject can do the union of everything granted to it.

A namespaced example: a read-only role for developers

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata: { name: dev-readonly, namespace: shop }
rules:
  - apiGroups: [""]
    resources: ["pods", "pods/log", "services", "configmaps", "events"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["apps"]
    resources: ["deployments", "replicasets"]
    verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata: { name: dev-readonly-binding, namespace: shop }
subjects:
  - kind: Group
    name: shop-developers
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: dev-readonly
  apiGroup: rbac.authorization.k8s.io

Notice: no access to secrets. A role that can list or get Secrets can read every credential in the namespace, so treat that permission as highly sensitive.

Verify before and after:

kubectl auth can-i list pods -n shop --as=jane --as-group=shop-developers       # yes
kubectl auth can-i get secrets -n shop --as=jane --as-group=shop-developers     # no
kubectl auth can-i --list -n shop --as=system:serviceaccount:shop:web

Service accounts for workloads

Every pod runs as a ServiceAccount (the namespace’s default one unless set). Create one per application and grant it only what the application calls from the API; most apps need nothing at all.

apiVersion: v1
kind: ServiceAccount
metadata: { name: web, namespace: shop }
automountServiceAccountToken: false      # the app never talks to the Kubernetes API
---
apiVersion: apps/v1
kind: Deployment
metadata: { name: web, namespace: shop }
spec:
  template:
    spec:
      serviceAccountName: web

Practices:

  • Disable token mounting (automountServiceAccountToken: false) for pods that do not call the API. If a pod is compromised, an attacker with a mounted token can try the API.
  • Do not use the default service account for anything with permissions.
  • Controllers (operators, CI agents) get a narrow Role or ClusterRole built for them; read the vendor’s documented rules rather than binding cluster-admin.
  • Cloud IAM integration: on EKS use IAM Roles for Service Accounts to give a service account AWS permissions without keys; on GKE, Workload Identity.
# inspect a service account and the token a pod actually receives
kubectl -n shop get sa web -o yaml
kubectl -n shop exec deploy/web -- cat /var/run/secrets/kubernetes.io/serviceaccount/token | cut -c1-20

In 1.22 the mounted token is a projected volume with an expiry (default one hour, refreshed automatically) and is invalidated when the pod is deleted. Long-lived token Secrets are still created for service accounts in this release, so audit and delete those you do not need.

Humans

Authentication is external to RBAC: certificates, OIDC (recommended), or the cloud provider’s IAM (on EKS the aws-auth ConfigMap maps IAM roles to Kubernetes groups). Bind groups, not individual users, so access follows your identity provider.

Role of person Typical grant
Developer edit or custom read/exec in their own namespaces
On-call view cluster-wide plus edit in production namespaces
Platform admin cluster-admin, held by few, with MFA, preferably time-bound
CI/CD A service account limited to deploy verbs in specific namespaces

The built-in ClusterRoles view, edit, admin and cluster-admin are good starting points; bind them with a RoleBinding to scope them to a namespace.

Dangerous permissions

Watch for: * verbs or resources, secrets read, pods/exec, create on pods (can mount host paths unless policy forbids), escalate, bind and impersonate verbs, nodes/proxy, and write access to clusterrolebindings. Any of these can become cluster-admin indirectly.

Audit routine

kubectl get clusterrolebindings -o json | jq -r '.items[] | select(.roleRef.name=="cluster-admin") | .metadata.name + " -> " + ([.subjects[]? | .kind + ":" + .name] | join(","))'
kubectl get rolebindings,clusterrolebindings -A -o wide | grep -v "system:"
kubectl get sa -A | grep -v default
kubectl auth can-i '*' '*' --as=system:serviceaccount:kube-system:default

Tools such as kubectl-who-can, rbac-lookup, rakkess and kubeaudit summarise access. Review quarterly, remove stale bindings, and keep all RBAC in Git. Pair RBAC with admission policy (Pod Security, OPA Gatekeeper or Kyverno) and network policies, because RBAC controls API access, not what a running container can do.