What Is a Kubernetes Service Account Token and How Does It Work?
Kubernetes service account tokens explained — what they are, how pods use them for API access, the difference between legacy tokens and projected tokens, and how IRSA works on AWS EKS.
If you have ever run kubectl exec into a pod and found a file at /var/run/secrets/kubernetes.io/serviceaccount/token, you have seen a Kubernetes service account token without necessarily knowing what it was or why it was there.
Service account tokens are how pods authenticate to the Kubernetes API server — and increasingly, to external services like AWS via IRSA. Understanding how they work is essential for both security and debugging.
What Is a Service Account?
A Service Account is a Kubernetes object that gives a pod an identity. Think of it as the cluster's equivalent of a user account, but for workloads rather than humans.
Every namespace has a default service account. If you do not specify a service account for your pod, it gets the default one automatically.
# See service accounts in a namespace
kubectl get serviceaccounts -n production
# Describe a specific one
kubectl describe serviceaccount my-app -n productionPods use service accounts to:
- Authenticate to the Kubernetes API server (to read ConfigMaps, create Events, etc.)
- Get AWS credentials via IRSA (on EKS)
- Get GCP credentials via Workload Identity (on GKE)
How Tokens Work
When a pod runs with a service account, Kubernetes automatically mounts a token into the pod at a well-known path:
# Inside a pod
cat /var/run/secrets/kubernetes.io/serviceaccount/tokenThis token is a JWT (JSON Web Token) signed by the Kubernetes API server. Applications can send this token in an HTTP Authorization header when calling the Kubernetes API, and the API server validates the signature to confirm the pod's identity.
# From inside a pod, calling the Kubernetes API directly
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
curl -H "Authorization: Bearer $TOKEN" \
--cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
https://kubernetes.default.svc/api/v1/namespaces/default/configmapsLegacy Tokens vs Projected Tokens
There are two types of service account tokens, and the difference matters for security.
Legacy tokens (Kubernetes < 1.24): Long-lived tokens stored as Secrets. They never expire. If a pod is compromised and the token is extracted, the attacker has indefinite access.
# Old style — auto-created Secret for service account
apiVersion: v1
kind: Secret
type: kubernetes.io/service-account-token
metadata:
name: my-app-token
annotations:
kubernetes.io/service-account.name: my-appProjected tokens (Kubernetes 1.21+, default since 1.24): Short-lived tokens automatically rotated by the kubelet. They have an expiration time (default 1 hour), are audience-bound, and are tied to a specific pod's lifetime. Much more secure.
You can see the projected token configuration in a pod spec:
volumes:
- name: kube-api-access
projected:
sources:
- serviceAccountToken:
expirationSeconds: 3607
path: token
- configMap:
name: kube-root-ca.crt
items:
- key: ca.crt
path: ca.crt
- downwardAPI:
items:
- path: namespace
fieldRef:
fieldPath: metadata.namespaceThe kubelet automatically refreshes this token before it expires, so your application always has a valid token without any action required.
RBAC: What the Token Actually Allows
Having a service account token only proves identity — it does not grant any permissions. Permissions come from RBAC (Role-Based Access Control) bindings.
By default, the default service account has almost no permissions. To give your pod permission to read ConfigMaps:
# 1. Create a Role with specific permissions
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: configmap-reader
namespace: production
rules:
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list", "watch"]
---
# 2. Bind the Role to a ServiceAccount
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-configmaps
namespace: production
subjects:
- kind: ServiceAccount
name: my-app
namespace: production
roleRef:
kind: Role
name: configmap-reader
apiGroup: rbac.authorization.k8s.io
---
# 3. Use the ServiceAccount in your Pod
apiVersion: v1
kind: Pod
spec:
serviceAccountName: my-app
containers:
- name: app
image: myapp:latestIRSA: Using Service Account Tokens for AWS Access
IRSA (IAM Roles for Service Accounts) is the most important use of service account tokens beyond the Kubernetes API. It lets pods get AWS credentials by exchanging a Kubernetes token for AWS credentials — no AWS access keys in Secrets needed.
How it works:
- EKS acts as an OIDC identity provider
- Kubernetes issues tokens with a specific audience for AWS
- AWS IAM validates the token against the OIDC provider
- AWS returns temporary credentials for the configured IAM role
Setting it up:
# 1. Create an IAM role with a trust policy for the service account
aws iam create-role \
--role-name my-app-role \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789:oidc-provider/oidc.eks.us-east-1.amazonaws.com/id/EXAMPLE"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.us-east-1.amazonaws.com/id/EXAMPLE:sub":
"system:serviceaccount:production:my-app"
}
}
}]
}'
# 2. Annotate the service account with the role ARN
kubectl annotate serviceaccount my-app \
-n production \
eks.amazonaws.com/role-arn=arn:aws:iam::123456789:role/my-app-roleOnce annotated, pods using my-app service account automatically get temporary AWS credentials via the projected token mechanism. The AWS SDK picks them up automatically — no code changes needed.
Debugging Service Account Issues
Pod cannot access Kubernetes API:
# Check if service account has the right role binding
kubectl auth can-i list pods \
--as=system:serviceaccount:production:my-app \
-n production
# Check what the service account can do
kubectl auth can-i --list \
--as=system:serviceaccount:production:my-app \
-n productionPod cannot access AWS (IRSA not working):
# Check the service account annotation
kubectl describe serviceaccount my-app -n production | grep eks.amazonaws.com
# Check if the projected token has the right audience
# Inside the pod:
cat /var/run/secrets/eks.amazonaws.com/serviceaccount/token | \
python3 -c "import sys,json,base64; t=sys.stdin.read().split('.')[1]; print(json.loads(base64.b64decode(t+'==')))"
# Look for "aud": ["sts.amazonaws.com"]Disable automounting if the pod does not need API access:
spec:
automountServiceAccountToken: false # More secure for pods that don't need it
containers:
- name: app
image: myapp:latestThis is a security best practice for pods that have no reason to call the Kubernetes API — reduces the blast radius if the pod is compromised.
More Kubernetes security? Read our RBAC Kubernetes explained guide and Kubernetes pod security admission explained.
Today I Fixed
Short real fixes from production — posted daily
Stay ahead of the curve
Get the latest DevOps, Kubernetes, AWS, and AI/ML guides delivered straight to your inbox. No spam — just practical engineering content.
Related Articles
How to Set Up HashiCorp Vault for Secrets Management from Scratch (2026)
HashiCorp Vault is the industry standard for secrets management. This step-by-step guide shows you how to install Vault, configure it, and integrate it with Kubernetes.
What is a Service Mesh? Explained Simply (No Jargon)
Service mesh sounds complicated but the concept is simple. Here's what it actually does, why teams use it, and whether you need one — explained without the buzzwords.
What is an Admission Webhook in Kubernetes Explained
Admission webhooks intercept every Kubernetes API request before it's persisted. Learn how mutating and validating webhooks work, with real examples from OPA, Istio, and custom webhooks.