AWS
IAM
Terraform
Service Account
Cloud Infrastructure

How to assign AWS IAM Role to Service Account with Terraform?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

On Amazon EKS, the standard way to give a pod AWS permissions is to bind an IAM role to a Kubernetes service account through IRSA, short for IAM Roles for Service Accounts. With Terraform, you model the trust policy, the IAM permissions, and the annotated Kubernetes service account so the whole setup is repeatable and reviewable.

Understand the IRSA Flow

IRSA works by letting the pod present a projected Kubernetes service account token to AWS. AWS then checks whether the IAM role trust policy allows that token issuer and that exact service account identity to assume the role.

At a high level, you need four pieces:

  • An EKS cluster with an OIDC provider associated to it.
  • An IAM policy describing the AWS permissions.
  • An IAM role whose trust policy allows the target service account.
  • A Kubernetes service account annotated with the IAM role ARN.

Build the Trust Policy in Terraform

A common pattern is to read the cluster identity and construct the trust relationship from that data.

hcl
1data "aws_eks_cluster" "this" {
2  name = var.cluster_name
3}
4
5data "aws_eks_cluster_auth" "this" {
6  name = var.cluster_name
7}
8
9data "tls_certificate" "oidc" {
10  url = data.aws_eks_cluster.this.identity[0].oidc[0].issuer
11}
12
13resource "aws_iam_openid_connect_provider" "this" {
14  url             = data.aws_eks_cluster.this.identity[0].oidc[0].issuer
15  client_id_list  = ["sts.amazonaws.com"]
16  thumbprint_list = [data.tls_certificate.oidc.certificates[0].sha1_fingerprint]
17}

Then create the role trust document for one namespace and one service account:

hcl
1data "aws_iam_policy_document" "assume_role" {
2  statement {
3    actions = ["sts:AssumeRoleWithWebIdentity"]
4
5    principals {
6      type        = "Federated"
7      identifiers = [aws_iam_openid_connect_provider.this.arn]
8    }
9
10    condition {
11      test     = "StringEquals"
12      variable = "${replace(data.aws_eks_cluster.this.identity[0].oidc[0].issuer, "https://", "")}:sub"
13      values   = ["system:serviceaccount:${var.namespace}:${var.service_account_name}"]
14    }
15  }
16}

That sub condition is what ties the role to one exact service account identity.

Attach Permissions and Create the Role

Next, define the permissions the pod should receive:

hcl
1resource "aws_iam_policy" "app" {
2  name   = "app-irsa-policy"
3  policy = jsonencode({
4    Version = "2012-10-17"
5    Statement = [
6      {
7        Effect   = "Allow"
8        Action   = ["s3:GetObject", "s3:ListBucket"]
9        Resource = ["arn:aws:s3:::my-bucket", "arn:aws:s3:::my-bucket/*"]
10      }
11    ]
12  })
13}
14
15resource "aws_iam_role" "app" {
16  name               = "app-irsa-role"
17  assume_role_policy = data.aws_iam_policy_document.assume_role.json
18}
19
20resource "aws_iam_role_policy_attachment" "app" {
21  role       = aws_iam_role.app.name
22  policy_arn = aws_iam_policy.app.arn
23}

This keeps the trust relationship separate from the permission policy, which makes later review easier.

Annotate the Kubernetes Service Account

Finally, create the service account with the IAM role annotation:

hcl
1resource "kubernetes_service_account_v1" "app" {
2  metadata {
3    name      = var.service_account_name
4    namespace = var.namespace
5    annotations = {
6      "eks.amazonaws.com/role-arn" = aws_iam_role.app.arn
7    }
8  }
9}

Pods that run with this service account can now assume the attached IAM role.

Verify the Configuration

After applying Terraform, confirm the service account annotation and test from a pod using that service account.

bash
kubectl get serviceaccount app -n my-namespace -o yaml
kubectl describe serviceaccount app -n my-namespace

A useful runtime check from inside a pod is:

bash
aws sts get-caller-identity

If IRSA is working, the returned identity should reflect the IAM role associated with the service account.

Common Pitfalls

The most common mistake is creating the IAM role but forgetting the cluster OIDC provider. Without that federated identity provider, the trust chain is incomplete.

Another issue is getting the sub claim wrong. The namespace and service account name must match exactly, including case.

People also attach the role to the service account correctly but forget to make the workload use that service account. The pod spec must reference the intended account.

Finally, do not give the role broader permissions than the workload needs. IRSA works best when each service account has a narrowly scoped role.

Summary

  • IRSA links an IAM role to a Kubernetes service account in EKS.
  • Terraform should manage the OIDC provider, trust policy, IAM role, and service account annotation together.
  • The trust policy must match the exact system:serviceaccount identity.
  • Attach only the AWS permissions the workload actually needs.
  • Verify the role from inside a pod with aws sts get-caller-identity.

Course illustration
Course illustration

All Rights Reserved.