IRSA: How to create an IAM role for a specific ServiceAccount

2 min read | by Jordi Prats

On AWS EKS you can associate an IAM role with a Kubernetes service account. The assume role policy is going to look like this:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::123456789123:oidc-provider/oidc.eks.us-west-2.amazonaws.com/id/A3E2AFA46A6F0C9B37B3F4A479A00C20"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "oidc.eks.us-west-2.amazonaws.com/id/A3E2AFA46A6F0C9B37B3F4A479A00C20:sub": "system:serviceaccount:demons:demosa"
        }
      }
    }
  ]
}

Let's take a look on how to create this role using Terraform

First we will have to make sure that we have created an IAM OIDC provider for our cluster. We'll need to construct the IRSA policy using the OIDC URL and the account ID.

We can the the OIDC URL using the following command:

$ aws eks describe-cluster --name prod --query "cluster.identity.oidc.issuer" --output text
https://oidc.eks.eu-west-1.amazonaws.com/id/A3E2AFA46A6F0C9B37B3F4A479A00C20

On terraform we can get the account ID using the aws_caller_identity datasource:

data "aws_caller_identity" "current" {}

So assuming that we have the OIDC URL, the namespace where the ServiceAccount is going to sit and it's name as an input variables:

variable "namespace" {
  description = "Kubernetes namespace for the service account"
  type        = string
}

variable "serviceaccount" {
  description = "ServiceAccount name"
  type        = string
}

variable "oidc_url" {
  description = "OpenID Connect provider URL"
}

We can create the policy as follows:

locals {
  oidc_provider = replace(var.oidc_url, "https://", "")
}

data "aws_iam_policy_document" "assume_role_policy" {
  statement {
    actions = ["sts:AssumeRoleWithWebIdentity"]
    effect  = "Allow"

    condition {
      test     = "StringEquals"
      variable = "${local.oidc_provider}:sub"
      values   = ["system:serviceaccount:${var.namespace}:${var.serviceaccount}"]
    }

    principals {
      identifiers = ["arn:aws:iam::${data.aws_caller_identity.current.id}:oidc-provider/${local.oidc_provider}"]
      type        = "Federated"
    }
  }
}

To finally create an IAM role with that assume_role_policy policy:

resource "aws_iam_role" "iam_role" {
  name                  = "${var.role_name}"
  assume_role_policy    = data.aws_iam_policy_document.assume_role_policy.json
  force_detach_policies = true
}

On the Kubernetes side, we will have to create an annotation for the ServiceAccount to actually using the IAM role using it's ARN, for example:

apiVersion: v1
kind: ServiceAccount
metadata:
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::123456789123:role/demoirsa
  name: demosa

Posted on 24/11/2021