20.5 Service Accounts: Pod Identity
Right, let’s talk about the unsung heroes and potential villains of your cluster: Service Accounts. You’ve been using them this whole time, probably without even knowing it. Every time you create a Pod, if you don’t explicitly tell it otherwise, the Kubernetes API server automatically mounts a magical token for the default service account in that Pod. It’s like the hotel key card you get at check-in without asking. This is both incredibly convenient and a security nightmare waiting to happen, which is a classic Kubernetes trope.
Why? Because that default service account token has essentially no permissions. It can authenticate to the API server, but it’s like showing up to a VIP party with a ticket that says “guest”—you get past the bouncer but no one will talk to you. However, if you start handing out roles to the default service account (a common rookie mistake), you’ve just given every Pod in that namespace a universal keycard to whatever permissions you granted. Don’t do that. We’ll get to the proper way.
The ServiceAccount and Token Secret Symbiosis
A ServiceAccount is just an object that exists in your namespace. It’s an identity. But an identity needs credentials, right? That’s where a special type of Secret comes in. When you create a ServiceAccount (or Kubernetes creates the default one), a controller can automatically create a Secret that contains a signed JSON Web Token (JWT). This JWT is the bearer token that your Pod uses to prove who it is to the API server.
You can see this in action. Create a simple Pod and exec into it:
kubectl create deployment nginx --image=nginx
kubectl exec -it deploy/nginx -- cat /var/run/secrets/kubernetes.io/serviceaccount/token
You’ll see a long, encoded string. That’s the token. The Pod automatically gets this mounted because of the…wait for it…automountServiceAccountToken feature, which is usually true. Now, here’s the first designer quirk: the Secret that holds this token is not owned by the ServiceAccount. If you delete the ServiceAccount, the Secret just hangs around, orphaned. It’s a bit messy.
Creating and Using a Dedicated ServiceAccount
The first rule of sane Kubernetes security is to never use the default service account for anything. You create a dedicated ServiceAccount for each specific application or purpose. It’s a one-liner:
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-app-service-account
namespace: my-namespace
Then, you assign permissions to this service account using RBAC RoleBindings (or ClusterRoleBindings, but try to use Roles first).
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: my-namespace
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
namespace: my-namespace
name: read-pods
subjects:
- kind: ServiceAccount
name: my-app-service-account
namespace: my-namespace
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
Finally, you tell your Pod to use this new, properly permissioned identity by setting serviceAccountName in its spec.
apiVersion: v1
kind: Pod
metadata:
name: my-app-pod
namespace: my-namespace
spec:
containers:
- name: app
image: my-app:latest
serviceAccountName: my-app-service-account # This is the crucial line
Now, your Pod has the “pod-reader” role and nothing more. Principle of least privilege achieved.
The Modern, Secure Way: TokenRequest API and Projected Volumes
Remember that auto-created Secret I mentioned? The old way of using those static tokens is… not great. Those tokens don’t expire, and if they leak, you have a permanent problem on your hands. The security model was frankly a bit absurd.
Kubernetes 1.20+ started pushing everyone towards a far better system: the TokenRequest API. Instead of relying on a static Secret, the kubelet dynamically requests a temporary, time-bound token for the Pod’s specific ServiceAccount. This token is valid only for that Pod, has an expiration time (typically an hour), and is tied to the specific Pod’s identity. This is a massive win for security.
You’ll see this in action when you describe a modern Pod. The mounted token isn’t from a Secret anymore; it’s a “projected” volume.
kubectl describe pod my-app-pod | grep -A5 Mounts
… Tokens: my-app-service-account-token-abcde …
The beauty is this happens automatically. When you set `serviceAccountName`, the API server and kubelet handle the rest, using the projected volume method by default in newer clusters. The old, long-lived Secret-based tokens are legacy. You should disable their auto-creation if you can (`automountServiceAccountToken: false` on the ServiceAccount) and fully embrace the temporary token model.
### The Major Pitfall: The Legacy Secret Trap
The biggest pitfall is getting stuck in the old paradigm. If you see a Pod that has a volume mount pointing to a Secret named something like `my-sa-token-xyz`, that's the old, dangerous kind. Migrate away from it. Use dedicated ServiceAccounts, rely on the projected tokens, and regularly audit your clusters for any Pods still using the `default` service account—they should have a very good reason for doing so. This isn't just best practice; it's how you avoid being the headline in the next Kubernetes security breach report.