TT Lab
Get started
Learn Learning paths Courses

Kubernetes Operations

ServiceAccount — A Workload's Identity

Continue in TT Lab

Summary

People prove their identity with an external authentication system, but Pods prove it with a ServiceAccount. That proof is a token, and tokens these days expire.

Why this matters

Programs running inside the cluster also call the API. Operators, controllers, monitoring agents, and CI runners all do. You cannot issue them human accounts, so Kubernetes provides ServiceAccount, an identity dedicated to workloads. A Pod gets its SA's token mounted as a file, and the apiserver identifies the subject from that token. The next decision is made by the RBAC you learned in the previous module. The structure in which authentication (who are you) and authorization (what can you do) are separate becomes clear here.

The problem was the old way. Before 1.24, creating an SA automatically produced one Secret holding a token, and that token had no expiry. That meant credentials that stay valid forever once leaked were rolling around, dozens per cluster. Whether printed in a log or left in an image layer, there was no way to track the copies.

How it works

Since 1.24 the default has changed. Creating an SA no longer creates a Secret automatically, and the Pod gets a projected token with a fixed lifetime mounted. The kubelet renews it on its own before it expires, so the application only has to reread the file.

When you get a token by hand, you use the TokenRequest API.

kubectl create token payments -n sa-lab --duration=3600s --audience=vault

If you open this token's payload, you see three things. sub is the subject (system:serviceaccount:<네임스페이스>:<이름>, namespace and name), aud is where this token will be used, and exp and iat are the lifetime. The reason aud matters is that it prevents replay attacks. If a token issued to give to Vault also works on the apiserver, then when one place is breached, the other opens too. A token with its audience stamped in is valid only at that audience.

Written directly in a Pod spec, it looks like this.

volumes:
  - name: vault-token
    projected:
      sources:
        - serviceAccountToken:
            audience: vault
            expirationSeconds: 3600
            path: vault-token

This structure is the basis of cloud workload identity. If you present this short-lived token that the Pod received to a cloud STS, it exchanges it for temporary credentials. The static keys you would have to store disappear entirely.

There is also control in the opposite direction. You do not give a token at all to a Pod that does not need one.

spec:
  automountServiceAccountToken: false

This field can be set on the Pod and also on the ServiceAccount. If you set it on the account, it applies by default to every Pod that uses that account. A workload like a web server that never calls the API has no reason to hold a token, and there is one less thing for an attacker to pick up when a container is breached.

The legacy way can still be created. On a Secret of the kubernetes.io/service-account-token type, if you attach the kubernetes.io/service-account.name annotation, a token with no expiry is created. It is the last resort when a tool outside the cluster does not support TokenRequest, but having no expiry means a person is 100% responsible for the revocation procedure, so it is not recommended.

What it looks like in the field

First, abuse of the default service account. A Pod that does not specify an SA uses the namespace's default. As a result, all the Pods in a namespace share the same identity, and the moment you grant even a little permission, that permission spreads to all of them. Giving each Pod its own dedicated SA is the basic rule.

Second, permissions are still decided by RBAC. Creating an SA does not give it any permissions. Conversely, a single binding put in for convenience can give a workload excessive permission. The habit of asking with --as is valid here too.

Third, the token file path. The default mount path is /var/run/secrets/kubernetes.io/serviceaccount. It is also the path an attacker checks first in container compromise analyses. Remember that a sidecar or a debug container can see the same thing.

What to do in the next lab

You create a dedicated SA, attach it to a Pod, and check the automatically mounted token volume. You apply the two ways of turning off the token on both the Pod and the account, and decode the payload of a token obtained with TokenRequest to read the subject, audience, and lifetime yourself. You write a projected token with a specified audience in a spec, create a legacy long-lived token Secret and sort out its risks, and finally build a usage audit report.