Service Accounts
TL;DR
- Every pod runs as a ServiceAccount (defaults to
defaultif not specified) - Kubernetes 1.22+ uses short-lived, auto-rotating projected tokens instead of long-lived Secrets
- ServiceAccount identity string in RBAC:
system:serviceaccount:<namespace>:<name> - Cloud IAM integration (IRSA, Workload Identity) eliminates static credential Secrets
- Set
automountServiceAccountToken: falseon pods that don’t need API access - Always create dedicated ServiceAccounts per workload; never rely on the
defaultaccount
Core Concepts
ServiceAccounts as Pod Identity
A ServiceAccount is the workload equivalent of a human user. Every pod runs as a ServiceAccount (explicitly or implicitly using default), and that identity is used for RBAC authorization when the pod calls the Kubernetes API.
ServiceAccounts are namespaced: every namespace automatically gets a default ServiceAccount created. Pods in a namespace that don’t specify serviceAccountName use the default account.
Token Delivery: Projected Volumes vs. Legacy Secrets
Modern (Kubernetes 1.22+): Projected Volumes
Service account tokens are delivered as short-lived, auto-rotating, audience-bound JWTs via the projected volume mechanism:
volumes:
- name: token-vol
projected:
sources:
- serviceAccountToken:
path: token
expirationSeconds: 3600 # Auto-rotates hourly
audience: vault # Scoped to specific consumer
Legacy: Static Secrets
Older kubernetes.io/service-account-token type Secrets still exist but are discouraged: they contain long-lived tokens that never expire and represent a large blast radius if leaked.
Projected tokens are inherently safer: automatic rotation shrinks the exploitation window, and audience-scoping prevents token replay across different services.
RBAC Identity Representation
A ServiceAccount’s identity in RBAC and audit logs is a standardized string:
system:serviceaccount:<namespace>:<serviceaccount-name>
Example: system:serviceaccount:payments:payments-api-sa
This exact format is used in RoleBinding subjects and with kubectl auth can-i --as=.
Cloud Workload Identity
Cloud-managed clusters (EKS, GKE, AKS) allow ServiceAccounts to federate with cloud IAM, eliminating static credential Secrets:
AWS IRSA (IAM Roles for Service Accounts) — Annotate the ServiceAccount with an IAM role ARN; EKS injects an OIDC-based token that AWS SDKs exchange for temporary credentials.
GCP Workload Identity — Bind a Kubernetes ServiceAccount to a Google Cloud service account via OIDC federation.
Azure Workload Identity Federation — Similar OIDC-based federation model.
This approach provides temporary, credential-less access without storing long-lived keys in Kubernetes Secrets.
Token Mounting and Security
By default, every pod mounts its ServiceAccount token at /var/run/secrets/kubernetes.io/serviceaccount/token. This token is valid for API server access.
Security best practice: Set automountServiceAccountToken: false on pods (and ServiceAccounts) that have no legitimate need to call the Kubernetes API. If a pod is later compromised, the attacker doesn’t automatically gain API access.
Comparison Table
| Aspect | ServiceAccount | Token Type | Use Case |
|---|---|---|---|
| Identity Scope | Per-namespace | Kubernetes API | Workload identification |
| Token Lifetime | Indefinite (account exists) | Short-lived (1hr default) | Temporary API access |
| Token Lifetime (Legacy) | Indefinite (account exists) | Long-lived (never expires) | Deprecated in 1.22+ |
| Mounting | Explicit serviceAccountName | Automatic projection | Automatic pod token injection |
| Cloud Integration | Via IRSA/Workload Identity | OIDC federation | Cloud credential exchange |
| Best for | RBAC binding | API requests | Secure temporary access |
Command Reference
Creating ServiceAccounts
# Imperative creation
kubectl create serviceaccount payments-api-sa -n payments
# View ServiceAccounts in a namespace
kubectl get serviceaccounts -n payments
# Describe a ServiceAccount (shows mounted token Secrets, etc.)
kubectl describe serviceaccount payments-api-sa -n payments
Specifying a ServiceAccount in a Pod
apiVersion: v1
kind: Pod
metadata:
name: my-app
namespace: payments
spec:
serviceAccountName: payments-api-sa # Specify the SA
automountServiceAccountToken: false # Disable auto-mounting if not needed
containers:
- name: app
image: myorg/app:1.0
RBAC Binding for ServiceAccounts
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: payments-api-reader
namespace: payments
subjects:
- kind: ServiceAccount
name: payments-api-sa # Reference the SA
namespace: payments # Must match SA's namespace
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
Cloud Workload Identity Configuration
apiVersion: v1
kind: ServiceAccount
metadata:
name: payments-api-sa
namespace: payments
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/payments-api-role
Token Inspection and Testing
# Check which ServiceAccount a pod uses
kubectl get pod my-app -o jsonpath='{.spec.serviceAccountName}'
# Generate a short-lived token for testing (Kubernetes 1.24+)
kubectl create token payments-api-sa -n payments --duration=1h
# Inspect the token inside a running pod
kubectl exec my-app -- cat /var/run/secrets/kubernetes.io/serviceaccount/token
# Check what a ServiceAccount can do (dry run)
kubectl auth can-i --list --as=system:serviceaccount:payments:payments-api-sa -n payments
# Check specific permission
kubectl auth can-i create pods --as=system:serviceaccount:payments:payments-api-sa -n payments
Common Pitfalls
Pitfall — Implicit default ServiceAccount Usage
Pods run with the default ServiceAccount by accident when no ServiceAccount is explicitly specified, potentially with broad permissions.
- Why:
defaultexists in every namespace; omittingserviceAccountNamesilently uses it. - Fix: Always explicitly specify a dedicated ServiceAccount per workload. Minimize permissions on
defaultor forbid its use entirely.
Pitfall — RoleBinding in Wrong Namespace A RoleBinding is created for a ServiceAccount, but in a different namespace than the SA/pod.
- Why: RoleBindings are namespaced; a RoleBinding in namespace A doesn’t apply to a ServiceAccount in namespace B.
- Fix: Ensure RoleBinding namespace matches the ServiceAccount and pod namespaces.
Pitfall — Enabling automountServiceAccountToken on Non-API Pods Pods that don’t call the Kubernetes API automatically mount a token, giving attackers unnecessary API access if the pod is compromised.
- Why: Default behavior mounts tokens; no explicit configuration is needed to create the vulnerability.
- Fix: Always set
automountServiceAccountToken: falseat pod or ServiceAccount level if API access is not required.
Pitfall — Long-lived Token Secrets in Production
Using legacy kubernetes.io/service-account-token type Secrets instead of projected tokens.
- Why: Long-lived tokens never expire and represent a large blast radius if leaked.
- Fix: Use projected tokens (Kubernetes 1.22+) which auto-rotate and are audience-scoped.
Pitfall — Confusing ServiceAccount Namespace in RoleBinding Subject Referencing a ServiceAccount from a different namespace in a RoleBinding subject without specifying the namespace.
- Why: ServiceAccount subjects must include a namespace field; omitting it causes the binding to reference the wrong account.
- Fix: Always include
namespace: <sa-namespace>in ServiceAccount RoleBinding subjects.
Interview Questions
Q: Explain how ServiceAccounts provide identity to pods for API server access.
Q: What is the difference between projected tokens and legacy Secret-based tokens? Why the change?
Q: How would you prevent a pod from accessing the Kubernetes API even if it’s compromised?
Q: Walk through setting up a ServiceAccount with minimal RBAC permissions for a specific application.
Q: What is cloud workload identity (IRSA, Workload Identity)? What problem does it solve?
Q: A pod cannot list ConfigMaps even though the RoleBinding looks correct. What are three things to check?
Q: Design a ServiceAccount/RBAC strategy for a multi-tenant cluster where each team has its own namespace.
Q: Explain the identity string format for ServiceAccounts in audit logs and RBAC.