CKA ServiceAccounts and RBAC Binding Meets CKS Bound Projected Tokens, Audience Scoping, and the TokenRequest API
The CKA exam asks whether you can give a pod an identity and grant it the right to act. The CKS exam asks whether that identity is short-lived, scoped to one audience, and unable to be replayed. Workload identity is where the two exams meet, spanning the CKA Workloads and RBAC objectives and the CKS Cluster Hardening and Minimize-Microservice-Vulnerabilities domains.
This is the exam-prep face of today's Ops lesson. The Ops lesson argued that a fleet must prove identity on the credential's clock and authorization on the request's clock, and must deny by default. Kubernetes implements exactly that, and the exam tests the pieces: the ServiceAccount is the identity, the bound projected token is the short-lived proof the kubelet re-mints, RBAC is the policy, and the API server is the enforcement point that evaluates every call. The Dev lesson made the deny-by-default floor a compile error in the browser; here it is a 403 from the API server.
A ServiceAccount is a namespaced identity for a workload. Every pod runs as one, the default ServiceAccount in its namespace if you assign none, which is the first CKS finding on most clusters, because default accumulates grants no one audits. The CKS instinct is one ServiceAccount per workload, named, with only the permissions that workload needs.
The pod proves it holds that identity with a token the API server trusts. Historically that token was a long-lived Secret auto-mounted into the pod, valid forever, readable by anyone who read the pod. Kubernetes replaced this with bound service-account tokens issued through the TokenRequest API. Kubernetes Up & Running 3e covers this under Authenticating to the Kubernetes API (p. 220): the token is a signed JWT the API server validates against its own signing key, and the modern token is bound, tied to a specific pod object, given an expiry, and scoped to an audience.
CKA gets the ServiceAccount created and granted. Creating it is one object; granting it is the RBAC pair the exam drills. Nigel Poulton's Authorization (RBAC) sections (The Kubernetes Book, pp. 180–184) frame RBAC as two halves: a Role names permissions, and a RoleBinding attaches those permissions to a subject. Neither half does anything alone.
apiVersion: v1
kind: ServiceAccount
metadata: { name: agent-sa, namespace: agents }
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata: { name: read-configmaps, namespace: agents }
rules:
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata: { name: agent-reads-config, namespace: agents }
subjects:
- kind: ServiceAccount
name: agent-sa
namespace: agents
roleRef:
kind: Role
name: read-configmaps
apiGroup: rbac.authorization.k8s.io
The Role grants get and list on ConfigMaps and nothing else. The RoleBinding attaches it to agent-sa. A pod running as agent-sa may now read ConfigMaps in the agents namespace and may do nothing else, because Kubernetes RBAC is additive and deny-by-default: with no rule granting a verb, the API server denies. This is the Ops lesson's floor written as cluster policy. The distinction the exam wants: a Role and RoleBinding are namespaced, a ClusterRole and ClusterRoleBinding are cluster-wide, and mixing them (a ClusterRole referenced by a namespaced RoleBinding) grants the ClusterRole's permissions only within that one namespace, a common and testable pattern.
CKS makes the identity safe. Three CKS moves harden the ServiceAccount token, and all three are directly testable.
A pod that never calls the API server should carry no token. Set automountServiceAccountToken: false on the ServiceAccount or the pod; a stolen pod then yields no API credential. Least privilege applied to identity itself.
When the pod needs a credential for an external consumer, mount a projected token scoped to that consumer with an audience and an expirationSeconds, not the general API-server token.
Poulton's Threat model (p207): assume the pod is breached and ask what the attacker reaches. A bound, scoped, short token contains the breach to what the workload legitimately touches.
volumes:
- name: vault-token
projected:
sources:
- serviceAccountToken:
path: token
audience: vault
expirationSeconds: 3600
The audience: vault field means the token is only accepted by a verifier that checks for the vault audience; presented to any other service, it fails validation, so a leaked token cannot be replayed sideways. The expirationSeconds: 3600 makes it a lease the kubelet renews under the running pod before it expires. Together they are the Ops lesson's scoped, short-lived credential, and they are the CKS answer to the confused-deputy replay: a token that names its audience cannot be carried to a different deputy.
Put the pieces in one flow, the cluster-side mirror of the Ops worked example. A research agent pod runs as agent-sa, which is bound to read-configmaps. The agent's code tries to write a Secret in the agents namespace.
The pod presents its bound token. The API server authenticates it: the JWT signature verifies, the token has not expired, and the named pod still exists, so the request's identity is system:serviceaccount:agents:agent-sa. Authentication succeeds. Now authorization runs. The API server evaluates the request create secrets in agents against every Role and ClusterRole bound to that subject. read-configmaps grants get, list on ConfigMaps. Nothing grants create on Secrets. RBAC is deny-by-default, so the API server returns 403 Forbidden, and the audit policy from the 06-24 lesson records the denied attempt with the subject, verb, and resource.
Read the sequence against the Ops lesson. Authentication ran on the token's clock; a token minutes from expiry would have failed here regardless of permissions. Authorization ran on the request's content; same identity, different verb-and-resource, different answer. The API server is the policy decision point; the RBAC objects are the policy; the 403 is deny-by-default holding the line the agent tried to cross. The cluster does per-request continuous authorization by construction, and the exam is testing whether you know why the write failed and where you would grant it if it should succeed.
The Ops lesson named the pattern in the abstract: identity on the credential's clock, authorization on the request's clock, deny by default. This lesson is that pattern as Kubernetes primitives — bound projected tokens for the first clock, RBAC evaluation on every API call for the second, additive-with-no-default-grant for the floor. The Dev lesson enforced the same floor at compile time in a browser; the cluster enforces it at request time in the API server. A read token that cannot reach a write path is a TypeScript type error in the client and a 403 in the cluster. Same rule, three seats.
A ServiceAccount agent-sa has a Role granting get on pods, but calls to the API return 403. The Role exists and is correct. What object is almost certainly missing, and what does it do?
A projected ServiceAccount token minted with audience: vault is stolen and replayed against the Kubernetes API server directly. Does the replay succeed? Explain what the audience claim enforces and where it is checked.
A pod runs a batch job that never contacts the API server, yet a breach of the pod exposes a usable API token. Name the single field that removes the token, and on which two objects it can be set.
You bind a ClusterRole using a namespaced RoleBinding. Across how many namespaces do the granted permissions apply, and why is this a useful pattern?
Contrast a legacy auto-mounted Secret token with a bound projected token on three axes — expiry, pod-binding, and audience. For each axis, state the blast-radius consequence of the legacy behavior.
A ServiceAccount is a name; a Role is a grant; a RoleBinding ties them; a bound projected token is the short, scoped proof the kubelet renews; and the API server denies every verb no rule allows. Those five facts are most of what CKA and CKS ask about workload identity, and they are the same five moves the Ops lesson made in the abstract and the Dev lesson made in the type system.
In the exam and in the cluster, give each workload its own ServiceAccount, grant it only the verbs it uses, turn off the token where it is not needed, and bind the token you do mount to an audience and an expiry. The pod that carries a short, audience-scoped token and a Role that grants nothing extra is the pod whose breach the API server contains to one namespace and one hour.