k8wiz
All graphs

Kubernetes graph

RBAC: how a ServiceAccount gets its permissions

A CI deployer's permissions traced through RBAC: ServiceAccount, projected token, Role and RoleBinding, a ClusterRole reused in one namespace, and a ClusterRoleBinding for cluster-wide reads.

RBAC permissions are always additive. A subject starts with nothing, and each binding adds the rules of one role. Nothing can deny. To answer "can this ServiceAccount do X?", you find every binding that names it and check their roles.

Where the binding lives decides the scope, not the role. A RoleBinding grants its role's rules in its own namespace only, even when the role is a ClusterRole. Only a ClusterRoleBinding grants across the whole cluster.

Interactive graph

Click a resource to see what it does, or follow the setup step by step.

Created by a controllerYou write itComes with an add-onreferencestraffic

How it works

All 10 resources and what they do

Pod ci-runner-8x2kqv1
A CI job Pod that runs kubectl apply against the apps namespace. serviceAccountName: deployer decides which identity it gets.
ServiceAccount deployerv1
The identity. Its full username is system:serviceaccount:ci:deployer, and bindings in any namespace can name it. On its own it can do almost nothing.
Projected token kube-api-access-…
The kubelet asks the API server for a short-lived token for deployer and mounts it at /var/run/secrets/kubernetes.io/serviceaccount/token. It is bound to this Pod, expires after an hour by default and is refreshed before that. No Secret is involved.
API server kube-apiserver
Authenticates the token, then asks the RBAC authorizer whether system:serviceaccount:ci:deployer may do this verb on this resource in this namespace. If no binding allows it, the answer is 403 Forbidden.
Role deployerrbac.authorization.k8s.io/v1
Rules for deploying in apps: manage Deployments, Services, ConfigMaps and Ingresses. It leaves out Secrets on purpose, because get on Secrets would expose every credential in the namespace.
RoleBinding deployerrbac.authorization.k8s.io/v1
Gives the deployer Role to the ServiceAccount from the ci namespace. The binding lives in apps, so that's where the rules apply.
ClusterRole viewrbac.authorization.k8s.io/v1
Built into every cluster. Read access to most namespaced objects, but not Secrets. It's an aggregated role, so CRDs can add their own rules to it with the label rbac.authorization.k8s.io/aggregate-to-view: "true".
RoleBinding deployer-viewrbac.authorization.k8s.io/v1
Reuses the view ClusterRole, but as a RoleBinding in monitoring, so the deployer can read there and nowhere else. This is how you avoid writing the same Role in every namespace.
ClusterRole node-readerrbac.authorization.k8s.io/v1
Read access to Nodes. Nodes are cluster-scoped, so only a ClusterRole can grant it, and only a ClusterRoleBinding makes it take effect.
ClusterRoleBinding deployer-node-readerrbac.authorization.k8s.io/v1
Grants node-reader across the whole cluster. The CI job uses it to wait for capacity before a rollout.

Common mistakes

  • kubectl auth can-i create deployments -n apps --as=system:serviceaccount:ci:deployer tells you whether a request would pass, and kubectl auth can-i --list with the same flags shows everything the account can do.
  • roleRef can't be changed after a binding is created. To point a binding at another role, delete it and create it again.
  • list and watch on Secrets return their contents, not just their names, so they give the same access as get.
  • Being allowed to create Pods in a namespace means being able to run as any ServiceAccount there, and so to use its permissions. Treat create pods as access to every ServiceAccount in the namespace.
  • Set automountServiceAccountToken: false on Pods that never call the API. A token that isn't mounted can't be stolen.

The manifests you write

Everything above that is marked "You write it", in one file. The rest comes from add-ons and controllers.

apiVersion: v1
kind: ServiceAccount
metadata:
  name: deployer
  namespace: ci
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: deployer
  namespace: apps
rules:
  - apiGroups: ["apps"]
    resources: ["deployments"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]
  - apiGroups: [""]
    resources: ["services", "configmaps"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]
  - apiGroups: ["networking.k8s.io"]
    resources: ["ingresses"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: deployer
  namespace: apps
subjects:
  - kind: ServiceAccount
    name: deployer
    namespace: ci
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: deployer
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: deployer-view
  namespace: monitoring
subjects:
  - kind: ServiceAccount
    name: deployer
    namespace: ci
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: view
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: node-reader
rules:
  - apiGroups: [""]
    resources: ["nodes"]
    verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: deployer-node-reader
subjects:
  - kind: ServiceAccount
    name: deployer
    namespace: ci
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: node-reader

Keep going

More graphs