Amazon EKS in Production with Terraform›05 · Identity
Learning Hub / Cloud — OpenStack, AWS & EKS / Amazon EKS in Production with Terraform

Lesson 05 of 18 · Operate

Identity

Two identity problems on EKS: who can access the cluster (access entries replacing the aws-auth ConfigMap), and how pods get AWS permissions (EKS Pod Identity, or IRSA), all as code, with least privilege.

Advanced
Key wordsaccess entriesaccess policiesauthentication modeaws-authEKS Pod IdentityIRSAOIDC providerleast privilege

Two different questions

  1. Who can talk to the Kubernetes API? People, CI and tools → access entries (+ Kubernetes RBAC).
  2. What AWS resources can a pod use? An app reading S3 or SQS → Pod Identity (or the older IRSA).

Two kinds of passes at a theme park. The staff pass decides who can enter the control room (cluster access). The ride wristband decides which rides each visitor can go on (pod permissions). Giving every visitor the manager's master key because it's easier would be a disaster, so each app gets its own wristband.

Cluster access with access entries

Set the cluster's authentication mode to API (or API_AND_CONFIG_MAP while migrating from aws-auth). Then grant access by IAM principal:

Access policy (examples) Grants
AmazonEKSClusterAdminPolicy Cluster-admin
AmazonEKSAdminPolicy Admin (namespace-scopable)
AmazonEKSEditPolicy / AmazonEKSViewPolicy Edit / view (namespace-scopable)

Or map the principal to Kubernetes groups in the entry and use your own RBAC (see Kubernetes Security, lesson 06). In Terraform, with the community EKS module:

access_entries = {
  platform_admins = {
    principal_arn = "arn:aws:iam::111122223333:role/aws-reserved/sso.amazonaws.com/AWSReservedSSO_PlatformAdmin_abc123"
    policy_associations = {
      admin = {
        policy_arn   = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy"
        access_scope = { type = "cluster" }
      }
    }
  }
  shop_developers = {
    principal_arn     = "arn:aws:iam::111122223333:role/aws-reserved/sso.amazonaws.com/AWSReservedSSO_ShopDev_def456"
    kubernetes_groups = ["shop-developers"]          # bound via RoleBindings in the shop namespace
  }
}

People log in through IAM Identity Center (SSO) into these roles; aws eks update-kubeconfig then uses their session. No shared kubeconfigs, no long-lived keys.

Pod permissions with EKS Pod Identity

  1. Install the EKS Pod Identity Agent add-on.
  2. Create an IAM role whose trust policy allows the Pod Identity service:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": { "Service": "pods.eks.amazonaws.com" },
      "Action": ["sts:AssumeRole", "sts:TagSession"]
    }
  ]
}
  1. Attach a least-privilege permissions policy (e.g. read one S3 prefix).
  2. Associate the role with a namespace + ServiceAccount:
resource "aws_eks_pod_identity_association" "orders" {
  cluster_name    = module.eks.cluster_name
  namespace       = "shop"
  service_account = "orders"
  role_arn        = aws_iam_role.orders.arn
}

Pods using serviceAccountName: orders now get that role's temporary credentials automatically; AWS SDKs pick them up with no code changes.

IRSA (IAM Roles for Service Accounts)

The older mechanism, still common: the cluster's OIDC issuer is registered as an IAM identity provider; the role's trust policy allows sts:AssumeRoleWithWebIdentity for a specific system:serviceaccount:<ns>:<name> subject; the ServiceAccount is annotated with eks.amazonaws.com/role-arn. It works on more environments (e.g. self-managed clusters), but needs per-cluster trust policies. Pod Identity is simpler for new EKS setups.

Lock down the node's credentials

Pods shouldn't borrow the node's instance role: require IMDSv2 with a hop limit of 1 (see AWS, lesson 03), and keep the node role minimal (ECR pull, CNI, SSM).

Try it: per-app AWS access (sandbox account)

  1. Switch a cluster to authentication mode API_AND_CONFIG_MAP (or create it with API) and grant a second IAM role view access scoped to namespace shop. Assume that role and test kubectl get pods -n shop (allowed) and -n kube-system (denied).
  2. Install the Pod Identity Agent add-on. Create a role that can only read s3://<bucket>/shop/*, and associate it with shop/orders.
  3. Run a pod with the amazon/aws-cli image and serviceAccountName: orders; run aws sts get-caller-identity and read an allowed and a denied S3 object.
  4. From a pod using the default ServiceAccount, confirm it gets no role (and can't reach the node's IMDS credentials).

Going deeper: identity hygiene

  • Manage access entries, roles and associations only in Terraform; alert on out-of-band changes (CloudTrail).
  • Use session tags (Pod Identity adds cluster, namespace and ServiceAccount tags) for attribute-based policies: one policy that scopes access by namespace tag.
  • Keep a break-glass role with a cluster-admin access entry, protected by MFA and alerting (see Kubernetes Security, lesson 05).
  • Review with aws eks list-access-entries and IAM Access Analyzer regularly.

Recap

  • Cluster access: access entries + access policies or Kubernetes groups/RBAC; people via SSO roles. Retire aws-auth edits.
  • Pod access to AWS: EKS Pod Identity (agent add-on, role trusting pods.eks.amazonaws.com, association per ServiceAccount), or IRSA.
  • Least privilege per app; block pods from the node's IMDS credentials.

This site is a public version of my personal engineering knowledge hub. It intentionally excludes confidential company information and internal operational details.