Back to blog

Security

Assume IAM Roles on EKS, the right way

How to set up and use IAM Roles for Service Accounts in EKS to securely manage pod-level IAM roles

May 20, 2024 Platform Engineering 3 min read

Before IAM Roles for Service Accounts (IRSA), the simplest way to let workloads in EKS access AWS APIs was to attach permissions to the worker nodes. The problem is obvious: every pod on that node can inherit more access than it needs.

Tools such as Kiam and kube2iam improved that model, but they added more moving parts and more operational burden. IRSA gave EKS a native way to map IAM roles directly to Kubernetes service accounts. AWS now also offers EKS Pod Identity for some newer patterns, but IRSA remains widely used and is still a strong fit for many clusters. Sources: IAM roles for service accounts, associate service account role.

Initial OIDC Setup

IRSA relies on an OpenID Connect (OIDC) provider for the cluster. EKS exposes an OIDC issuer URL for each cluster, and IAM needs a matching identity provider before service accounts can assume roles.

If you are using the AWS EKS OpenTofu or Terraform module, enabling IRSA is straightforward:

module "eks" {
  source      = "terraform-aws-modules/eks/aws"
  enable_irsa = true
}

Once that is in place, pods in the cluster can assume IAM roles through annotated service accounts rather than inheriting the node role.

Creating IAM Roles for EKS

To let workloads assume a role, add the cluster OIDC provider as a trusted federated principal in the IAM role. One simple route is the iam-assumable-role-with-oidc module:

module "assumable_role" {
  source  = "terraform-aws-modules/iam/aws//modules/iam-assumable-role-with-oidc"
  version = "5.39.0"

  create_role = true
  role_name   = "assumable-role"

  provider_url = replace(module.eks.cluster_oidc_issuer_url, "https://", "")
  role_policy_arns = [
    aws_iam_policy.example.arn,
  ]
}

This creates a role that can be assumed through the cluster OIDC provider.

Associating IAM Roles with Service Accounts

To connect the IAM role to a workload, annotate the service account with the role ARN:

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

Any pod using that service account can then obtain AWS credentials for the linked role.

Restricting Access to Specific Service Accounts

If you do nothing else, the trust relationship can be broader than it needs to be. In practice, you should scope it down to specific service accounts wherever possible.

With the Terraform module above, one option is oidc_fully_qualified_subjects:

module "assumable_role" {
  source                        = "terraform-aws-modules/iam/aws//modules/iam-assumable-role-with-oidc"
  create_role                   = true
  oidc_fully_qualified_subjects = ["system:serviceaccount:default:app"]
}

That limits role assumption to the named service account rather than every service account in the cluster.

Verifying IRSA

Once the role and annotation are in place, check that the pod is using the intended service account and that the application can call AWS with the expected permissions. In practice that usually means:

  • confirming the service account annotation is present
  • confirming the pod is running under that service account
  • testing an AWS SDK or CLI call from inside the pod

EKS injects the required environment variables and web identity token file for supported SDKs. Source: Use IRSA with the AWS SDK.

Conclusion

IRSA remains one of the cleanest ways to manage pod-level AWS permissions in EKS. It removes the need to over-permission node roles, avoids extra third-party components, and gives you a more precise least-privilege model for workloads running in the cluster.