RBAC is specifically about calls to the API server -- it has nothing to say about whether one Pod can open a network connection straight to another. That's a separate, equally wide-open-by-default problem; see NetworkPolicy.
Role and ClusterRole: the permissions themselves
A Role is a set of permissions scoped to one namespace. A ClusterRole is the same shape, but either cluster-wide (for cluster-scoped resources like Nodes, or for granting the same permissions across every namespace at once) or reusable -- defined once, bound into as many individual namespaces as you want.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-reader
namespace: staging
rules:
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]Neither a Role nor a ClusterRole does anything by itself -- it's just a named list of allowed verbs on a resource. Nothing is granted to anyone until it's bound.
RoleBinding and ClusterRoleBinding: granting it to someone
A RoleBinding grants a Role (or a ClusterRole, used this way to reuse one definition across namespaces) to a subject -- a User, Group, or ServiceAccount -- within one namespace. A ClusterRoleBinding grants a ClusterRole across the whole cluster, with no namespace boundary.
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods
namespace: staging
subjects:
- kind: ServiceAccount
name: ci-deployer
namespace: staging
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.ioThe same ClusterRole bound with a RoleBinding (namespace-scoped) versus a ClusterRoleBinding (cluster-wide) is the single most common RBAC mistake worth knowing about: it's the difference between "can read pods in staging" and "can read pods everywhere, including kube-system."
ServiceAccount: identity for a Pod
A User authenticates from outside the cluster (your own kubeconfig, SSO, a cloud IAM identity). A ServiceAccount is the equivalent for something inside the cluster -- a Pod calling back into the API server to list other Pods, read a Secret, or watch for changes. Every Pod runs as some ServiceAccount whether you set one or not; leaving it unset means the namespace's default one, which usually has no permissions bound to it but still gets a token mounted into the Pod for no reason.
apiVersion: v1
kind: ServiceAccount
metadata:
name: ci-deployer
namespace: stagingReference it explicitly, and skip the token mount entirely if this Pod never actually calls the API server:
spec:
serviceAccountName: ci-deployer
automountServiceAccountToken: false # only if this Pod never calls the API serverPutting it together
The Role, the RoleBinding, and the ServiceAccount above are a complete, working example: a ServiceAccount named ci-deployer that can read Pods and their logs in the staging namespace, and nothing else -- not Secrets, not Deployments, not any other namespace. A Pod that mounts this ServiceAccount gets exactly that, no more:
apiVersion: v1
kind: Pod
metadata:
name: log-shipper
namespace: staging
spec:
serviceAccountName: ci-deployer
containers:
- name: shipper
image: docktionary/log-shipper:1.0Checking what a Role actually grants
Reading a pile of rules and figuring out whether they add up to what you intended is slow and error-prone. Ask the API server directly instead:
# Can I do this?
kubectl auth can-i get pods --namespace staging
# Can this ServiceAccount do this? (--as uses impersonation, which
# requires its own RBAC permission to use at all)
kubectl auth can-i list secrets \
--as=system:serviceaccount:staging:ci-deployer \
--namespace staging
# Everything a ServiceAccount is bound to, read back out
kubectl auth can-i --list \
--as=system:serviceaccount:staging:ci-deployer \
--namespace staging
# Find every RoleBinding/ClusterRoleBinding mentioning a name
kubectl get rolebindings,clusterrolebindings --all-namespaces \
-o json | jq '.items[] | select(.subjects[]?.name=="ci-deployer")'Where this goes wrong in practice
- Wildcards.
resources: ["*"],verbs: ["*"], or both together on a Role someone meant to scope down -- functionally cluster-admin, written to look narrower than it is. - ClusterRoleBinding by default. Reaching for a ClusterRoleBinding because it's the first result that comes to mind, when every actual use case was namespace-scoped and a RoleBinding (referencing a shared ClusterRole, if the rules need to be reused) would have contained the blast radius.
- Binding to cluster-admin directly. The built-in
cluster-adminClusterRole exists for actual cluster administrators -- binding an application's ServiceAccount to it to unblock a permissions error is solving "I don't know which permission I'm missing" with "grant all of them," instead of usingkubectl auth can-iabove to actually find the one rule that's missing. - Unused token mounts. Every Pod gets a ServiceAccount token mounted by default, readable by anything that can exec into it, even when the Pod never calls the API server at all.
automountServiceAccountToken: falseremoves a credential that was never needed in the first place.