K8s in 30 seconds — A pod is your application running as one or more containers on a Kubernetes cluster. A workload is a higher-level object (like a Deployment or StatefulSet) that manages the lifecycle, scaling, and desired state of those pods. A namespace is an isolation boundary within the cluster that separates teams or environments. A ServiceAccount is an identity object assigned to pods: the identity that determines what tokens the pod receives.
Workload identity federation lets a pod authenticate to cloud services (AWS, GCP, Azure) using a short-lived Kubernetes ServiceAccount token instead of a static IAM key. If the federation is misconfigured (wrong audience, over-scoped IAM binding, or missing namespace constraint), pods outside the intended workload can assume its cloud role. This post documents a safe default and its trade-offs.
System description
In this pattern, a pod proves its identity to a cloud provider without static keys. Kubernetes issues a short-lived, signed token, the cloud verifies that token against the cluster's public keys, and, if the token matches a set of trust rules, exchanges it for temporary cloud credentials. The cluster acts as an OIDC issuer, publishing public keys (JWKS) that let the cloud verify your pod's tokens cryptographically, without a direct connection to the Kubernetes API.

Minimal system context
ServiceAccount (identity): One per workload. The
namespace/serviceaccountpair is the identity the cloud side binds to. Avoid sharing ServiceAccounts across different workloads to prevent one compromised app from hijacking the cloud access of anotherProjected token volume (credential): A kubelet-managed JWT mounted into the pod filesystem. The token is signed, auto-rotated, and scoped to a specific audience (the aud claim) defined in the pod spec
OIDC issuer (verification): The cluster exposes a
/.well-known/openid-configurationendpoint and a JWKS URI that cloud IAM uses to verify token signatures without calling the Kubernetes APICloud trust policy (authorization): An IAM policy that specifies which issuer, audience, namespace, and ServiceAccount name are allowed to assume a given cloud role
STS (token exchange): The cloud's Security Token Service (STS) that validates the token against the trust policy and returns temporary credentials
Golden path
Build this first. Then relax constraints only if you have a specific reason:
Dedicated ServiceAccount per workload → projected volume mounts short-lived JWT → explicit audience on token → namespace + SA pinning in trust policy → session duration matches token TTLDedicated ServiceAccount per workload: Never reuse the default account, which lacks the specific identity needed for cloud pinning
Projected volume mounts short-lived JWT: The kubelet handles the token lifecycle. No static Kubernetes Secrets involved
Explicit audience on token: Set the audience field within the projected volume configuration to the exact STS endpoint (e.g.,
sts.amazonaws.com)Namespace + SA pinning in trust policy: Verify both the namespace and ServiceAccount name in the cloud IAM policy
Session duration matches token TTL: Set the IAM role's max session duration to match the projected token's
expirationSeconds
Each step is a gate. If any gate fails (wrong audience, unrecognized issuer, or namespace mismatch), the exchange is denied and no cloud credentials are issued.
Security properties of projected ServiceAccount tokens
A projected ServiceAccount token binds:
The issuer (cluster OIDC URL)
The subject (
system:serviceaccount:<namespace>:<name>)The audience (explicit string, e.g.,
sts.amazonaws.com)A short expiration (default varies; typically 1h, configurable down to 10m)
It does not guarantee that:
The cloud IAM role is scoped to the minimum permissions the workload needs
Only the intended pod mounts the token (any pod using the same ServiceAccount gets it)
The OIDC issuer endpoint is access-controlled (public by default on most managed clusters)
While the endpoint is public, it only serves public keys (JWKS); the private keys never leave the cluster control plane
That's why the trust policy must bind to namespace + ServiceAccount name, and the IAM role must be scoped to the narrowest permission set the workload requires.
Threat model
Baseline assumptions
The Kubernetes cluster is managed (EKS, GKE, AKS) and the control plane is operated by the cloud provider
RBAC is enforced. Developers cannot create or modify ServiceAccounts in namespaces they don't own
If this assumption weakens which is common in orgs without admission control (the cluster's gatekeeper that validates or rejects API requests), every row in this table is affected, especially the cross-namespace confused deputy threat
Network policies (rules controlling pod-to-pod traffic) and pod security standards (restrictions on what pods can do, e.g., running as root) are in place. This model does not cover container escapes or node compromise
Standard infra controls such as TLS, etcd (the cluster's configuration database) encryption, audit logging, and admission control are assumed to be in place. This model focuses on the workload-to-cloud authentication path
A note on risk: you won’t fix everything
This table isn’t a checklist where every row must be fully eliminated. Focus on preventing the worst failures and limiting blast radius. In practice: ship prevention for the High rows first, then add monitoring and response for what you can’t realistically prevent.
Asset | Threat | Baseline Controls | Mitigation Options | Risk |
|---|---|---|---|---|
Cloud IAM role | Over-scoped binding: Trust policy uses wildcard or omits namespace condition, allowing any ServiceAccount in the cluster to assume the role | Trust policy exists | 1. Namespace + SA pinning: Always include both 2. Policy-as-code: Use OPA / Gatekeeper or cloud IAM policy linting in CI to reject wildcard conditions 3. Audit: Periodically enumerate trust policies and flag any that lack namespace constraints | High |
Cloud IAM role | Confused deputy (cross-namespace): Attacker creates a ServiceAccount with the same name in a different namespace and assumes the role | RBAC on SA creation | 1. Namespace binding: Include the namespace claim in the trust policy condition (not just the SA name) 2. Admission control: Restrict ServiceAccount creation in sensitive namespaces to specific principals 3. Drift Detection: Alert only on ServiceAccounts created manually (outside of CI/CD pipelines) in production namespaces | High |
Projected token | Token theft from pod filesystem: Attacker with exec access reads | Projected volumes | 1. Short TTL: Set 2. Network restriction: Cloud trust policy can include a source IP / VPC condition where supported 3. Detection: Alert on | Medium |
Token audience | Audience misconfiguration: Token is issued with a broad or default audience, making it valid for unintended cloud trust policies | Projected volumes (which require an audience field) | 1. Explicit pinning: Always set the 2. Validation: Admission webhook rejects pod specs where projected token audience is missing or set to a wildcard | Medium |
OIDC issuer | Issuer spoofing: Attacker with | Cloud trust policy specifies issuer URL | 1. Issuer inventory: Maintain an allowlist of known OIDC issuer URLs per cloud account 2. Policy review: Flag trust policies that reference unrecognized issuer URLs 3. Access control: Restrict who can create or modify identity provider configurations in cloud IAM | Medium |
IAM permissions | Blast radius: The cloud IAM role granted via federation has far more permissions than the workload needs | Role exists per workload | 1. Least privilege: Scope IAM policies to specific resources (ARNs, project IDs) and actions 2. Separate roles: One IAM role per workload, not per namespace or per cluster 3. Access Analyzer: Use cloud-native tools (IAM Access Analyzer, Policy Troubleshooter) to identify unused permissions | Low |
Credential refresh | Revocation gap: Cached cloud credentials persist after a pod's ServiceAccount binding is changed (e.g. during incident response), maintaining access until the session expires | Standard SDK behavior (auto-refresh based on TTL) | 1. Short session duration: Set the IAM role's max session duration to match the projected token TTL to shrink the revocation window 2. Pod restart: Terminate affected pods immediately after SA binding changes to force credential re-acquisition 3. Rotation testing: Include SA rebinding and credential expiry in integration tests | Low |
Workload availability | OIDC issuer drift: Cluster upgrade or recreation changes the OIDC issuer URL or rotates signing keys, breaking all cloud trust policies silently | Managed OIDC endpoint | 1. Pre-flight check: Verify OIDC issuer URL stability before cluster operations 2. Integration test: Run STS exchange test in CI after cluster changes 3. Monitoring: Alert on STS authentication failure rate spikes | Medium |
If you use static IAM keys instead
If you skip workload identity and store IAM access keys in Kubernetes Secrets:
Key rotation becomes a manual operational burden; missed rotations leave long-lived credentials active indefinitely
A single leaked Secret (etcd backup, misconfigured RBAC, log exposure) grants persistent cloud access with no expiry
There is no namespace or ServiceAccount binding; any pod that mounts the Secret gets the same permissions
Blast radius increases because static keys are typically broader-scoped and harder to audit than per-workload federated roles
Revocation requires generating new keys and redeploying every consumer, rather than simply updating a trust policy
Verification checklist
Identity binding
Each workload has a dedicated ServiceAccount (no reuse of the
defaultSA)Cloud trust policies include both
namespaceandserviceaccountconditions (not just SA name)Creating a ServiceAccount with the same name in a different namespace does not grant access to another workload's cloud role
Token configuration
Projected token
audienceis set to the exact STS endpoint string (e.g.,sts.amazonaws.com, not blank or*)Projected token
expirationSecondsis set explicitly and is less than or equal to 3600sPods do not mount the default ServiceAccount token (
automountServiceAccountToken: falsedisables the automatic token mount) when only the projected token is needed
IAM policy scoping
IAM Access Analyzer (or equivalent) reports no unused permissions for the workload's role
No trust policy uses wildcard conditions on namespace, ServiceAccount, or audience claims
IAM roles are not shared across workloads with different permission requirements
Credential lifecycle
The application SDK refreshes credentials before the projected token expires (observable in logs or metrics)
Changing a pod's ServiceAccount and restarting causes the old cloud role to become inaccessible within one token TTL
Deleting the trust policy immediately blocks new token exchanges (existing sessions drain within max session duration)
Detection
Cloud audit logs capture every
AssumeRoleWithWebIdentity(or equivalent) call with the source ServiceAccount identityAlerts fire on token exchange attempts from unexpected namespaces or ServiceAccount names
A query of all cloud trust policies returns only known, documented OIDC issuer URLs
STS authentication failure rate is monitored, and alerts fire on spikes correlated with cluster operations
Implementation & Review
The full threat model matrix, architectural diagrams, and a printable verification checklist for this pattern are available in the Secure Patterns repository. Use these artifacts to guide your design reviews and internal audits.
