When a process running inside a Pod calls the Kubernetes API, what identity does it present and where does that credential come from? Why do security reviews object to leaving every workload on the namespace's default ServiceAccount?
answer
- ServiceAccount = namespaced Pod identity
- /var/run/secrets/kubernetes.io/serviceaccount/{token,ca.crt,namespace}
- username system:serviceaccount:<ns>:<name>
- default is unprivileged but shared
- one SA per Deployment; automount off elsewhere
basics
~20 sIt presents a ServiceAccount: a namespaced identity named by spec.serviceAccountName. The kubelet projects a signed JWT for it into the container at /var/run/secrets/kubernetes.io/serviceaccount/token. Leaving everything on default means all workloads share one identity, so any permission granted to it is granted to all of them and nothing is attributable.
solid answer
~50 sA Pod authenticates as a **ServiceAccount**, a namespaced object referenced by `spec.serviceAccountName`. Every namespace has one called `default`, and a Pod that does not name one gets it. The kubelet mounts a projected volume at `/var/run/secrets/kubernetes.io/serviceaccount` containing `token` (a signed JWT), `ca.crt`, and `namespace`; client libraries pick this up automatically for in-cluster config. The API server verifies the JWT's signature and maps it to the username `system:serviceaccount:<namespace>:<name>`, which is what RBAC binds against. Out of the box a ServiceAccount has essentially no permissions, so `default` is not dangerous by itself. The problem is that it is *shared*. The moment anyone grants `default` a permission for one workload, every Pod in the namespace gets it, and audit logs cannot tell which workload acted. Least privilege needs a distinct identity per workload, so the convention is one ServiceAccount per Deployment, with `automountServiceAccountToken: false` for the workloads that never call the API at all.
code
yaml · 18 linesapiVersion: v1
kind: ServiceAccount
metadata:
name: payments-api
namespace: payments
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: payments-api
namespace: payments
spec:
template:
spec:
serviceAccountName: payments-api
containers:
- name: app
image: payments-api:1.4go deeper
Name the object, the mount path, and the fact that a Pod without an explicit serviceAccountName gets the namespace's default one.
Add the username format RBAC binds to, how in-cluster config discovers the credential, and why one ServiceAccount per Deployment is the norm.
Argue it from blast radius and auditability, and mention hardening the default ServiceAccount with automountServiceAccountToken: false so forgetting fails closed.
Treat the ServiceAccount as the platform's workload identity primitive: the subject for RBAC, for mesh mTLS, and for federation to cloud IAM, and set the conventions that keep it one-per-workload across the fleet.
## Two kinds of identity Kubernetes distinguishes *users* (humans and external systems, authenticated by client certificates, OIDC, or a proxy, and deliberately not stored as objects in the cluster) from *ServiceAccounts* (in-cluster identities that are first-class namespaced API objects). A Pod always acts as a ServiceAccount, never as a user. A ServiceAccount is almost content-free: a name, a namespace, some annotations, and optionally a list of image-pull secrets. Its value is that it is a stable subject name that RBAC can bind to, and that the control plane will mint tokens for it. ## How the credential reaches the container When a Pod is admitted, a built-in admission controller sets `spec.serviceAccountName` to `default` if it is empty and validates that the named ServiceAccount exists (a Pod referencing a missing one will not start). Unless automounting is disabled, the kubelet then adds a projected volume mounted at `/var/run/secrets/kubernetes.io/serviceaccount` with three items: - `token` — a signed JWT asserting the ServiceAccount identity, - `ca.crt` — the CA bundle needed to trust the API server's serving certificate, - `namespace` — the Pod's namespace, so a client knows its own scope. Every official client library implements "in-cluster config": it looks for that directory plus the `KUBERNETES_SERVICE_HOST` and `KUBERNETES_SERVICE_PORT` environment variables the kubelet injects, and builds a working API client with no further configuration. On the receiving side the API server validates the token signature against the ServiceAccount signing key, and presents the request to the authorizer as user `system:serviceaccount:<namespace>:<name>`, a member of the groups `system:serviceaccounts` and `system:serviceaccounts:<namespace>`. That username string is exactly what a RoleBinding's subject refers to, which is why RBAC examples show `kind: ServiceAccount, name: api, namespace: payments`. ## Why one identity per workload A fresh ServiceAccount, including `default`, has no RBAC permissions beyond what every authenticated principal gets (the discovery endpoints and self-review APIs). So the objection is not "default is privileged", it is that default is *shared*, and shared identities decay in three ways. **Permissions accumulate to the wrong subject.** One workload in the namespace needs to read ConfigMaps, someone binds a Role to `default`, and now the other six Pods in that namespace can read ConfigMaps too. Nothing signals that this happened, and nobody can safely remove the binding later because it is not clear who depends on it. **Blast radius.** Compromise any container in the namespace and the attacker holds a token for the union of every permission the namespace's workloads ever needed, rather than the permissions of the one workload they broke into. **Attribution.** Audit entries record the ServiceAccount username. If every Pod is `system:serviceaccount:payments:default`, an audit log showing an unexpected Secret read tells you the namespace and nothing else. With one ServiceAccount per Deployment the same entry names the workload. The corresponding practice is small: create a ServiceAccount alongside each Deployment, set `serviceAccountName`, bind only the Roles that workload needs, and set `automountServiceAccountToken: false` on the ServiceAccount or the Pod for the large majority of applications that never call the Kubernetes API. Hardening baselines also recommend patching the `default` ServiceAccount in every namespace with `automountServiceAccountToken: false` so that forgetting to name one fails closed rather than silently shipping a credential. ## Things that commonly confuse people ServiceAccounts are namespaced, so `payments/api` and `search/api` are different identities, and a RoleBinding in one namespace can still name a ServiceAccount from another namespace as a subject (the subject carries its own namespace field). ServiceAccounts are also used for things other than API calls: the `imagePullSecrets` on a ServiceAccount are applied to Pods that use it, and the projected token is the credential of choice for federating to cloud IAM or authenticating to Vault. And a Pod's identity for *network* purposes (mTLS in a service mesh) is usually derived from the same ServiceAccount, which is another reason a distinct one per workload pays off. ## What to say Define ServiceAccount as the Pod's namespaced identity, describe the projected token path and the `system:serviceaccount:ns:name` username RBAC binds to, then explain that `default` is unprivileged but shared, which breaks least privilege, blast-radius containment, and attribution.
- Does the default ServiceAccount have any permissions out of the box?Essentially none beyond what every authenticated principal has: API discovery and the self-subject review endpoints. It is not privileged by default, so the risk is not the starting permissions but accumulation, because any Role someone binds to `default` for one workload applies to every Pod in that namespace that uses it.
- How does a client library inside a Pod know how to reach and authenticate to the API server without configuration?It uses in-cluster config: it reads the token and CA bundle from `/var/run/secrets/kubernetes.io/serviceaccount`, and takes the API server address from the `KUBERNETES_SERVICE_HOST` and `KUBERNETES_SERVICE_PORT` environment variables that the kubelet injects into every container. That is why `rest.InClusterConfig()` and its equivalents take no arguments.
The default ServiceAccount is a shared office badge left on a hook: harmless while it opens nothing, but every door anyone adds to it opens for everybody, and the door log only ever says 'the shared badge'.
saying these in an interview costs you the question
- Saying the default ServiceAccount is dangerous because it is privileged, rather than because it is shared
- Believing ServiceAccounts are cluster-scoped or interchangeable across namespaces
- Confusing ServiceAccounts with Kubernetes users, or expecting a User object to exist in the cluster
- Assuming a Pod without a ServiceAccount has no credential mounted, when the default is mounted unless automounting is disabled