Free & open · no paywall, ever

IntegrAuth Academy

Identity, security and practical AI — free, from zero. Byte-sized lessons, real diagrams, hands-on labs, zero fluff. Two programs, each with its own exam and certificate.

20 tracks 241 lessons 242 hands-on labs 230+ diagrams 100% free

English is hard? Your browser can translate this page into Tamil, Telugu, Kannada, Malayalam or Hindi. Tech words stay in English so you learn the real names.

How?
  • Chrome on phone: ⋮ menu → Translate
  • Chrome on computer: right-click → Translate to …
  • Edge: the translate icon in the address bar
Identity & API Security Track 12 · Cloud & Workload Identity
0/166 lessons read
CLOUD & WORKLOAD · LESSON 7

Kubernetes identity — service accounts & RBAC

Kai's app now runs in a Kubernetes cluster, next to many other teams' apps. The cluster does not give the app a password. It gives a ServiceAccount and a token file. Your goal: set these up so a hacked pod stays a small problem.

Key points
  • Software in Kubernetes runs as a ServiceAccount. Give each app its own.
  • A bound token dies with its pod and expires in an hour by default.
  • Role bindings decide what the token may do. Bind in one namespace.
  • Whoever can create pods can use any ServiceAccount there.
  • Pin the cloud trust to one exact ServiceAccount.

Kubernetes in one paragraph

Kubernetes is open-source software that runs containers on a group of machines called nodes. The smallest unit is the pod: containers that start and stop together. Everything goes through one front door, the API server. Namespaces split a cluster into named sections, such as shop and pay.

ServiceAccounts: the caller for software

Software gets a ServiceAccount. It lives in one namespace. Its username looks like system:serviceaccount:shop:web: namespace shop, name web. A pod that names no ServiceAccount runs as the default one, so give every app its own (see non-human identities). A new ServiceAccount has no power until somebody grants it.

The token in every pod

Long ago, a ServiceAccount's credential was a token that never expired, kept in a Secret. Today the kubelet, the agent on each node, asks the API server's TokenRequest API for a token. It writes the token into the pod as a file. This is a bound token: a signed JWT with three limits.

  • Tied to the pod. Delete the pod, and its token stops working. Other pods of the same ServiceAccount keep working.
  • Time-limited. A token lasts one hour by default. Your app must re-read the file.
  • Audience-scoped. The aud claim names who the token is for. Another audience is refused.
Think of it like…

an exhibition hall. The office prints a day badge for one stall, with its name and today's date. It opens only that stall's back door, and when the stall closes, the badge is dead.

API serversigns tokenskubeletagent on the nodePod web-7fruns asshop:web1 · TokenRequest2 · signed JWT3 · write file+ refreshToken claims (simplified)sub: system:serviceaccount:shop:webaud: the API server · exp: about 1 hourbound to pod: web-7f (name + unique ID)Pod deleted → token revoked (≤ 60 s after its deletion timestamp)
Figure — A token the cluster makes, refreshes and revokes for you. The API server signs a token bound to the pod, with an audience and an expiry. Delete the pod, and the token stops working soon after. Nothing long-lived sits in a Secret.

RBAC: who may do what, and where

RBAC decides what a caller may do (see RBAC, ABAC & ReBAC). A rule names resources (pods, secrets) and verbs (get, list, create). Rules only add power; there are no deny rules. A Role lists rules for one namespace, and a RoleBinding gives it to a ServiceAccount.

The permissions that are secretly admin

Some grants look small and are not.

  • Reading Secrets. get, list and watch all reveal a Secret, so "read-only" is not safe. A legacy token inside is someone else's identity.
  • Creating pods. Anyone who can create pods in a namespace can run one as any ServiceAccount there. They can also mount any Secret there.
  • Exec into pods. pods/exec can read that container's token files.
  • Wildcards and cluster-admin. resources: ["*"] covers every resource type, now and later. Members of system:masters skip RBAC checks. Cluster-admin "just to make it work" is the Kubernetes admin:* from least privilege for machines.

Namespaces are the real trust boundary.

Pods that call the cloud: federation, not keys

Do not paste a cloud access key into a Kubernetes Secret: anyone who can read Secrets there would hold a long-lived key. Workload identity federation exists to kill that.

The fix is federation, with the cluster in the role of the CI system. The API server is an OIDC issuer and publishes its public keys. The pod gets a token for the cloud's token service. The cloud checks the signature, then its trust policy. The issuer must be this cluster. The subject must be exactly system:serviceaccount:shop:payments. The audience must be right. If all match, it returns short-lived credentials.

Pod · paymentstoken aud:cloud token serviceCloud token servicechecks the trust policyCloud databaseshort-lived creds onlyCluster issuer/openid/v1/jwksPod · webwrong subject1 · present token3 · creds, ~1h2 · fetch keysTrust policy (pinned)iss = this clustersub = system:serviceaccount:shop:paymentsaud = cloud token service
Figure — The cluster vouches, the cloud decides. The payments pod's token is checked against the cluster's public keys and a trust policy pinned to one exact ServiceAccount. A pod with any other ServiceAccount has the wrong subject and is refused.
Go deeper: legacy tokens and the version history
  • Bound to the pod. The pod's name and unique ID are inside the token. The API server stops accepting it within 60 seconds of the pod's deletion time. That kills one token. Re-creating the ServiceAccount kills every token for it.
  • Time-limited. The kubelet asks for a fresh token when the old one is 80% used.
  • Audience-scoped. By default the audience is the API server.

Before Kubernetes v1.22, pods got a token from a Secret. Since v1.22, pods get a bound token through the TokenRequest API. Since v1.24, the cluster no longer creates token Secrets by itself. Since v1.29, an auto-generated legacy token that stays unused for a year (the default) is marked invalid, and later deleted. You can still create a never-expiring token Secret by hand. The Kubernetes docs advise it only when the short-lived way truly will not work.

Legacy Secret tokenBound token (TokenRequest)
StoredIn a Secret object in the clusterDelivered to the pod's file by the kubelet
ExpiresNever: valid until the ServiceAccount is deletedTime-limited, refreshed automatically
Tied toNothing but the ServiceAccountOne pod (name and unique ID) and an audience
Who can read itAnyone who can read Secrets in that namespaceWhatever runs in that pod
How to kill itDelete the Secret or the ServiceAccountDelete the pod, or wait for it to expire

A pod that never talks to the API server should carry no API token. Set automountServiceAccountToken: false on the pod or on its ServiceAccount. The token file sits at /var/run/secrets/kubernetes.io/serviceaccount/token.

Go deeper: the four RBAC objects, and the built-in roles

A Role holds rules for one namespace. A RoleBinding gives a Role to a user, group or ServiceAccount in that namespace. Prefer RoleBindings. A ClusterRoleBinding gives power in every namespace, even ones made later.

A ClusterRole is a set of rules for cluster-wide things like nodes, or a reusable template. A RoleBinding can point at a ClusterRole and still grant it in one namespace only. A ClusterRoleBinding grants a ClusterRole in every namespace.

Kubernetes ships four user-facing roles: view, edit, admin and cluster-admin. The view role leaves out Secrets on purpose. The edit role can read them.

Watch more grants. bind and escalate get around the rule that you cannot grant powers you lack, and impersonate acts as someone else.

Watch two more. create on serviceaccounts/token makes tokens for existing accounts. A "read-only" wildcard still reads every Secret.

Go deeper: how the cloud checks a cluster's tokens

Also, do not let pods borrow the node's cloud identity from the machine's metadata service: then all pods share one identity.

Two lessons follow. First, pin the subject. A condition that matches system:serviceaccount:* trusts every pod in the cluster. Second, anyone who can create pods in shop can run one as payments and get its cloud role. Your Kubernetes RBAC is now part of your cloud IAM.

The API server publishes a discovery document at /.well-known/openid-configuration and its public keys at /openid/v1/jwks. The cloud fetches the keys and checks each token's signature with them.

Between services inside the cluster, SPIFFE fits the same way. The SPIRE Kubernetes attestor asks the kubelet which pod is calling, and issues an SVID for mutual TLS.

Go deeper: Zara's audit

Zara asks the cluster what each shop ServiceAccount can do. She uses kubectl auth can-i --list, which works if she may impersonate. Two answers worry her. The CI deployer holds cluster-admin through a ClusterRoleBinding, added "because a deploy failed once". A debugging helper can exec into any pod in shop, including payments, whose token opens the cloud database.

She rebinds the deployer to a Role in shop. She removes the helper's exec right. She moves payments into its own namespace. No code changed, but the blast radius did.

Go deeper: managed Kubernetes and client certificates

Kubernetes does not store people. It has no user objects. The API server trusts something outside to vouch for each person, usually your identity provider using OpenID Connect. To Kubernetes, a person is just a username plus groups.

Each major cloud packages the federation pattern under its own name (as of October 2026). Examples are IAM roles for service accounts and EKS Pod Identity on Amazon EKS, Workload Identity Federation for GKE, and Microsoft Entra Workload ID on AKS. The names differ. The idea is the same: a ServiceAccount maps to a cloud identity, with no stored key.

Client certificates can also log people in. But Kubernetes cannot revoke one certificate on its own. A leaked certificate works until it expires. So the project's hardening guide calls them a poor fit for everyday user logins.

🧪 Interactive lab — enable JavaScript to play with this one.

🧪 Try it free

Start a throwaway cluster on your laptop with kind (open source; it runs Kubernetes inside Docker). Create a ServiceAccount. Run kubectl create token <name> --duration 10m and decode the result with any JWT decoder. Look at its sub, aud and exp. Give the account the view role in one namespace with a RoleBinding. Then ask kubectl auth can-i --list -n <namespace> --as=system:serviceaccount:<namespace>:<name> what it may do. Look for Secrets in the answer. Last, hold your own cluster up against the Kubernetes RBAC good practices.