
AWS IRSA: Fine-Grained Pod Identity for EKS
An examination of AWS IRSA for EKS pod identity, detailing how to secure Kubernetes workloads with IAM roles.
AWS IAM Roles for Service Accounts: The Mechanism of Fine-Grained Pod Identity
This article is Part 3 of the AWS IAM & Cloud Security Series.
The fundamental security flaw in early Amazon EKS architectures was the conflation of compute identity with workload identity. When you attach an IAM role to an EC2 node, every process running on that node inherits the role's permissions. If a container within a pod is compromised, the attacker gains the full breadth of the node's access, including the ability to spin up new instances or read secrets from the metadata service. This "blast radius" is unacceptable for multi-tenant clusters. AWS IRSA (IAM Roles for Service Accounts) solves this by decoupling the identity of the Kubernetes pod from the identity of the underlying node, using OpenID Connect (OIDC) to create a dynamic trust boundary.
The mechanism relies on three distinct components: an OIDC identity provider configured in AWS IAM, a Kubernetes Service Account annotated with a specific role ARN, and an IAM role that trusts the OIDC provider. When a pod starts, the kubelet injects a short-lived JSON Web Token (JWT) into the pod's filesystem. This token contains claims about the pod's namespace and service account name. The application inside the pod uses this token to request temporary credentials from the AWS Security Token Service (STS) via the AssumeRoleWithWebIdentity API call. The IAM role's trust policy acts as the gatekeeper, validating the token's signature and its specific claims before issuing temporary access keys.
The Trust Boundary Problem
To understand the necessity of IRSA, one must examine the failure mode of the "node-wide" IAM permissions model. In this legacy approach, an IAM role is attached to the EC2 instance profile of the worker nodes. Consequently, any pod scheduled on that node inherits the permissions of that role. This creates a massive trust boundary where a single compromised container can pivot laterally to access the entire cluster's infrastructure resources.
Furthermore, the aws-iam-authenticator pattern, often used for user authentication to the API server, is strictly for Kubernetes RBAC authentication. It authenticates the user to the control plane but never provided a mechanism for the pod itself to assume an IAM role for AWS API calls. It does not provide a mechanism for the pod itself to assume an IAM role with specific, least-privilege permissions for downstream AWS service interactions. Without IRSA, the only way to restrict pod access is to rely on the coarse-grained permissions of the node, which is fundamentally insecure for multi-tenant environments.
The OIDC Trust Mechanism
AWS IRSA introduces a dynamic trust relationship by leveraging OpenID Connect (OIDC). The first step is configuring an OIDC identity provider within the AWS IAM console. This provider points to the public URL of your EKS cluster's OIDC issuer, typically formatted as https://oidc.eks.<region>.amazonaws.com/id/<cluster-id>. This step establishes the trust anchor; the IAM role's trust policy references this OIDC provider, and the OIDC provider trusts the EKS cluster. This ensures that the IAM role can verify tokens signed by the cluster's Kubernetes API server. Without this provider, the IAM role has no way to verify the source of the token.
When a pod starts, the kubelet injects a short-lived JSON Web Token (JWT) into the pod's filesystem at /var/run/secrets/eks.amazonaws.com/serviceaccount/token. This token contains claims about the pod's namespace and service account name. The application inside the pod uses this token to request temporary credentials from the AWS Security Token Service (STS) via the sts:AssumeRoleWithWebIdentity API call. The IAM role's trust policy acts as the gatekeeper, validating the token's signature and its specific claims before issuing temporary access keys.
The Trust Policy Structure
The trust policy is the critical mechanism that enforces least privilege. It must be a JSON document that allows the sts:AssumeRoleWithWebIdentity action but restricts it to specific conditions. The Condition block is where the logic resides. It checks two primary claims within the incoming JWT: aud (audience) and sub (subject).
The aud claim ensures the token was issued by your specific EKS cluster's OIDC provider. The sub claim is a string formatted as system:serviceaccount:<namespace>:<service-account-name>. By specifying these conditions, the trust policy ensures that only pods running in the exact namespace with the exact Service Account name can assume the role. A pod in the dev namespace cannot assume a role intended for the production namespace, even if they share the same IAM role ARN in the annotation.
A common mistake is omitting the StringEquals condition for the aud claim, which could allow tokens from any other cluster sharing the same OIDC issuer domain to assume the role. The sub claim must also be precise; wildcards like system:serviceaccount:*:log-* are valid but reduce isolation guarantees. For production workloads, the specific namespace and service account name should be hardcoded in the condition.
Operational Verification
Consider a concrete scenario. You have a cluster named secure-cluster in the us-west-2 region. You create an IAM role named s3-reader-role with a policy allowing s3:GetObject on a bucket logs-bucket. You configure the trust policy for this role to allow sts:AssumeRoleWithWebIdentity from the OIDC provider of secure-cluster, restricted to the sub condition system:serviceaccount:logging:log-collector.
In your Kubernetes cluster, you create a Service Account named log-collector in the logging namespace and annotate it with the ARN of s3-reader-role. When a pod using this Service Account starts, the AWS SDK (configured with the AWS_WEB_IDENTITY_TOKEN_FILE environment variable) reads the token from /var/run/secrets/eks.amazonaws.com/serviceaccount/token. The SDK then sends this token to the STS endpoint.
# Example of the token file path injected by the EKS pod identity
export AWS_WEB_IDENTITY_TOKEN_FILE=/var/run/secrets/eks.amazonaws.com/serviceaccount/token
export AWS_ROLE_ARN=arn:aws:iam::<account-id>:role/s3-reader-roleThe STS service verifies the token signature using the public key from the OIDC provider. It then evaluates the trust policy conditions. If the token's sub claim matches system:serviceaccount:logging:log-collector and the aud matches the cluster's issuer, STS assumes the role and returns temporary credentials (Access Key ID, Secret Access Key, and Session Token). These credentials are valid for a default of one hour and are used by the application to make API calls to S3. If the token is expired, malformed, or belongs to a different namespace, the request is denied.
Common Pitfalls
When implementing IRSA, several configuration errors frequently lead to security gaps or operational failures.
- Missing
audCondition: Failing to include theaud(audience) claim in the trust policy's condition block is a critical error. Without it, any cluster configured with the same OIDC provider domain could potentially assume the role, breaking the isolation between clusters. - Overly Broad
subWildcards: Using wildcards in thesubclaim (e.g.,system:serviceaccount:logging:*) reduces the security posture. While convenient, it allows any service account in the namespace to assume the role. Production workloads should specify the exact service account name. - Token Rotation and Cluster Updates: If an EKS cluster is recreated or the OIDC provider configuration is rotated, the trust relationship breaks. The IAM role's trust policy must be updated to reflect the new cluster ID or OIDC URL, otherwise, all pods relying on IRSA will fail to retrieve credentials.
Practical Takeaways
To effectively secure EKS workloads with IRSA, focus on these core principles:
- Decouple Identity: Always separate the identity of the pod from the identity of the node to minimize the blast radius of a compromise.
- Least Privilege via Trust Policy: Enforce strict constraints in the IAM trust policy using both
audandsubconditions to ensure only the intended workload can assume the role. - Short-Lived Credentials: Rely on the temporary nature of the credentials provided by STS; do not attempt to cache or extend their validity manually.
FAQ
Q: Can I use IRSA with self-managed nodes? A: Yes, IRSA works with both managed and self-managed node groups, provided the EKS cluster is configured with an OIDC provider. The node type does not affect the mechanism, as the identity is derived from the pod, not the node.
Q: Does IRSA replace aws-iam-authenticator?
A: No. aws-iam-authenticator is used for authenticating human users to the Kubernetes API server for RBAC. IRSA is used for authenticating workloads (pods) to AWS services. They serve orthogonal purposes and are often used together.
Q: How do I troubleshoot IRSA credential retrieval failures?
A: Check the pod logs for AccessDenied errors. Verify the AWS_ROLE_ARN and AWS_WEB_IDENTITY_TOKEN_FILE environment variables are set correctly. Then, inspect the IAM role's trust policy to ensure the sub and aud claims match the pod's namespace and service account exactly.
Conclusion
This mechanism eliminates the need for long-term static credentials in the pod environment. IRSA allows the application code itself to authenticate to AWS services directly. This is a significant shift in the data flow: the identity is now derived from the Kubernetes control plane's internal state rather than the node's hardware identity.
While this approach is resilient, it introduces a dependency on the Kubernetes Service Account controller and the availability of the OIDC provider. If the OIDC provider configuration is rotated or the cluster ID changes, the trust relationship breaks until updated. Additionally, the application code must be aware of the IRSA environment variables or the token file location. The AWS SDK for most languages handles this automatically if the environment variables are set, but custom implementations must handle the token retrieval and STS assumption logic manually.
The tradeoff here is operational complexity for security granularity. Managing the trust policies and annotations adds steps to the deployment pipeline, but the reduction in attack surface is substantial. You no longer need to worry about a compromised pod in one namespace escalating privileges to another, because the trust policy physically prevents the token from being accepted by the IAM role for the wrong identity. This mechanism effectively turns the Kubernetes Service Account into a secure, short-lived credential source for AWS APIs, aligning cloud-native security practices with the principle of least privilege.
In summary, AWS IRSA transforms how EKS workloads interact with AWS. It moves the identity boundary from the node to the pod, leveraging the OIDC protocol to validate Kubernetes-native tokens against IAM trust policies. By strictly controlling the aud and sub claims in the IAM role's trust policy, you ensure that only authorized workloads in specific namespaces can assume specific roles. This eliminates the risk of credential leakage and provides a scalable, fine-grained security model for cloud-native applications.
Related posts
Least Privilege in Practice
A practical guide to implementing least privilege in AWS using IAM Access Analyzer, policy generation, and condition keys for secure access management.
Implementing Dynamic Authorization with Cedar Policy Language (AWS)
A technical overview of implementing dynamic authorization using Cedar policy language and AWS Verified Permissions with ABAC.
IAM for Data Lakes: Securing Big Data
An examination of identity and access management strategies for data lakes, covering Apache Ranger, Lake Formation, and column-level security.