Free — no signup required

IRSA: IAM Roles for Service Accounts

3 min read

The Security Gap

A pod running in EKS often needs to call AWS APIs — reading from S3, writing to DynamoDB, publishing to SNS. The question is: how does the pod prove its identity to AWS?

Bad approach 1 — Node IAM Role: Attach an IAM policy to the EC2 node's instance role. Every pod on that node inherits those permissions. If your node runs both a payment processor and a public-facing web server, both pods can access your payment data. This violates the Principle of Least Privilege.

Bad approach 2 — Hardcoded credentials: Store AWS Access Keys in environment variables or Kubernetes Secrets. Keys get committed to Git, leaked in logs, or forgotten and never rotated. This is a well-documented source of AWS account compromises.

The Solution: IRSA (IAM Roles for Service Accounts)

IRSA uses the OpenID Connect (OIDC) protocol to create a cryptographically verifiable link between a Kubernetes identity (a Service Account) and an AWS identity (an IAM Role). Each pod gets its own temporary, short-lived credentials — not shared with any other pod.

The IRSA Flow, Step by Step

Pod starts
   kubelet injects a signed OIDC token into the pod's filesystem
   Pod's SDK reads the token automatically
  → SDK calls AWS STS: "I have this token, give me credentials for Role X"
   STS verifies the token signature against the cluster's OIDC provider
  → STS returns temporary credentials (valid 1 hour, auto-refreshed)
  → Pod calls S3/DynamoDB/etc. with those credentials

Setting Up IRSA: The Three Components

1. Associate an OIDC Provider with your cluster (one-time setup): a single eksctl call registers the cluster as a trusted identity provider with IAM — nothing to configure beyond the cluster name and an approval flag.

2. Create an IAM Role with a trust policy scoped to a specific Service Account:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {
      "Federated": "arn:aws:iam::123456789012:oidc-provider/oidc.eks.us-east-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE"
    },
    "Action": "sts:AssumeRoleWithWebIdentity",
    "Condition": {
      "StringEquals": {
        "oidc.eks.us-east-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE:sub": "system:serviceaccount:default:report-sa",
        "oidc.eks.us-east-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE:aud": "sts.amazonaws.com"
      }
    }
  }]
}

The sub condition is critical — it pins the trust to a specific namespace and service account name. Without it, any service account in the cluster could assume the role.

3. Annotate the Kubernetes Service Account:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: report-sa
  namespace: default
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/FinanceReportRole

Result: Only pods using serviceAccountName: report-sa in the default namespace receive credentials for FinanceReportRole. Every other pod on the same node gets nothing.

Senior Insight: IRSA tokens are projected service account tokens with a 24-hour expiry by default, but the AWS SDK refreshes them automatically well before expiry. The token is mounted at /var/run/secrets/eks.amazonaws.com/serviceaccount/token — distinct from the standard Kubernetes service account token. If you see ExpiredTokenException errors in pods, the most common cause is a clock skew between the node and AWS STS, not an actual expiry issue.

This is one of 18 chapters

Get every chapter — Kubernetes, Terraform, SRE, distributed systems, and more — with fast daily review built in.

See pricing