IAM for EKS: Cluster Role, Node Role, Pod Identity, and Cluster Access

By Pravin Mishra

We've covered EKS architecture, cluster creation, and node groups. Now it's time for IAM — and I want to be upfront about something: IAM on EKS confuses a lot of people. Not because it's difficult, but because there are several separate access paths, and it's easy to mix them up. By the end of this post you'll know exactly which IAM role is for which purpose — cluster access, node permissions, or pod-level AWS access — and why conflating them is the single most common IAM mistake on EKS.

Why EKS needs IAM at all

EKS doesn't live in isolation — it's an AWS service that constantly talks to other AWS services. When a controller provisions a load balancer for one of your Services, it calls AWS APIs. When a worker node starts up, it calls EKS APIs to join the cluster. When your application needs to read a secret from Secrets Manager, it calls the Secrets Manager API. Every one of those calls needs an identity: AWS has to verify who's asking and whether they're allowed to do it. IAM is how AWS provides that identity and permission — and on EKS, there isn't just one identity in play. There are four, and they solve four different problems.

The cluster role — IAM for the control plane

When AWS creates and manages your EKS control plane, it needs permission to perform AWS operations on your behalf — managing network interfaces, security groups, and the other AWS resources normal cluster operation depends on. You create an IAM role, EKS assumes it, and that's the cluster IAM role. The required policy is AmazonEKSClusterPolicy, an AWS-managed policy that grants exactly the permissions EKS needs for cluster operations.

eksctl creates this role automatically when you create a cluster; with Terraform, you define it yourself and attach it during creation. Without it, EKS can't create or manage the control plane at all. It's worth being precise about scope here: the cluster role is assumed only by the EKS control plane service — your applications and worker nodes never use it. It's also a different thing entirely from Kubernetes RBAC: IAM controls access to AWS resources, RBAC controls access to Kubernetes resources inside the cluster. See Amazon EKS cluster IAM role for the full policy detail.

The node role — IAM for EC2 worker nodes

Your worker nodes need their own AWS identity too. When a node starts, it calls the EKS API to register with the cluster. The container runtime pulls images from ECR when pods are scheduled. The VPC CNI plugin needs AWS permissions to manage pod networking — either through the node role or, preferably, through its own dedicated role. We covered exactly which policies this role needs in the node groups post, so I won't repeat the full breakdown here — the short version is that nodes need permission to join the cluster and pull images, and the CNI needs networking permissions to manage ENIs and pod IPs.

eksctl creates this role automatically; with Terraform you define it explicitly. Without it, your nodes can't join the cluster and your pods can't start. So at this point we have two roles: the cluster role lets EKS operate the control plane, the node role lets EC2 instances join the cluster, pull images, and run node-level components. The obvious next question is what covers the application itself.

Why the node role isn't for application permissions

Say the application needs to read a secret from Secrets Manager, or write an object to S3. It isn't the control plane, and it isn't the worker node — it's a pod running inside the cluster. So how should it get AWS permissions?

The wrong answer is attaching application permissions to the node role. If the node role can read Secrets Manager or write to S3, every workload scheduled on that node potentially has a path to those same permissions through the node's credentials. Now think about blast radius: if one pod is compromised — an application vulnerability, a malicious dependency, a configuration mistake — an attacker may be able to use whatever AWS permissions are reachable from that node, and those aren't limited to the one application that got compromised. They can affect every other workload sharing the node.

The production rule: don't use the node role for application permissions. The node role belongs to the node. Application pods get their own scoped AWS identity, following least privilege. There are two common ways to provide that identity on EKS — IRSA and EKS Pod Identity.

IRSA — the older approach, still common in existing clusters

IRSA — IAM Roles for Service Accounts — was introduced in 2019 and is still widely used in existing EKS clusters, so it's worth understanding even if you wouldn't reach for it on a new one. IRSA works through OIDC federation: your EKS cluster has an OpenID Connect provider URL, and AWS trusts tokens issued by it. When a pod runs under a specific Kubernetes Service Account, Kubernetes provides that pod a web identity token, and the pod exchanges the token for temporary AWS credentials through STS.

The detail that matters operationally: the IRSA trust policy contains your cluster's specific OIDC provider information, which ties the IAM role's trust relationship to that one EKS cluster. Move the workload to another cluster, and you normally have to update the trust relationship to point at the new cluster's OIDC provider. That per-cluster coupling is one of the reasons AWS introduced Pod Identity. See IAM roles for service accounts for the full OIDC mechanics.

EKS Pod Identity — the default for new clusters

Pod Identity became generally available in late 2023, and for a new EKS cluster, it's usually the first option to consider when applications need AWS access. The difference from IRSA is structural: Pod Identity doesn't use OIDC federation at all. Instead, the EKS Pod Identity Agent — a DaemonSet running on every node — receives a pod's credential request and hands back temporary AWS credentials based on a Pod Identity association you create in EKS.

The IAM role's trust policy doesn't reference your cluster's OIDC URL. Instead, it uses a fixed AWS service principal:

{
  "Statement": [{
    "Effect": "Allow",
    "Principal": {
      "Service": "pods.eks.amazonaws.com"
    },
    "Action": ["sts:AssumeRole", "sts:TagSession"]
  }]
}

Two details in that policy are worth calling out. First, pods.eks.amazonaws.com is a fixed service principal, not a cluster-specific OIDC URL — the same trust policy works across different clusters, with no per-cluster update required. Second, both sts:AssumeRole and sts:TagSession are required — Pod Identity uses session tags when it assumes the role, and if sts:TagSession is missing, the credential flow can fail silently from the application's point of view: the pod still starts, but it gets AWS credential errors at runtime. This is a common, easy-to-miss Pod Identity misconfiguration in practice.

From there, you create an association connecting a specific Kubernetes Service Account in a specific namespace to an IAM role — notably, the association targets the Service Account, not a Pod directly, so any pod that uses that Service Account automatically gets the role's temporary credentials:

aws eks create-pod-identity-association \
  --cluster-name book-review \
  --namespace default \
  --service-account book-review-app \
  --role-arn arn:aws:iam::123456789012:role/book-review-secrets-reader \
  --region eu-west-1

That's the entire setup: no IRSA annotation on the Service Account, no cluster-specific OIDC trust relationship to maintain. The namespace and Service Account together determine which workloads receive the role, and the Pod Identity Agent handles credential delivery automatically. One requirement: the eks-pod-identity-agent add-on has to be installed on the cluster — which is exactly why the cluster creation post included it in the add-ons list from the start.

So, which to use? For a new cluster, use Pod Identity — simpler trust policies, easier management, no OIDC provider relationship to maintain. For a cluster already running IRSA, there's no urgent need to migrate — it still works, and you'll keep seeing it in production environments for a while, so understanding both is worth the time. This course's labs use Pod Identity, since it's what you'd normally reach for on a new EKS environment today. See EKS Pod Identity and creating a Pod Identity association for the full reference.

Cluster access — who can actually run kubectl?

We've now covered IAM roles for the control plane, worker nodes, and application pods. None of that answers a simple question: how does a human engineer, or a CI/CD pipeline, get permission to actually run kubectl against the cluster? This is the one that catches people off guard most often.

The old answer was the aws-auth ConfigMap — a Kubernetes ConfigMap inside the cluster mapping IAM users and roles to Kubernetes users and groups. It worked, but carried real operational risk: a mistake while editing it — bad YAML indentation, a wrong IAM ARN — could lock administrators out of the cluster, and recovery wasn't always straightforward.

The newer approach is EKS access entries, managed through the EKS API instead of a hand-edited ConfigMap — giving you an AWS-managed recovery path and removing most of that lockout risk. Access entries require the cluster's authentication mode to be set to API or API_AND_CONFIG_MAP. The pattern is: create an access entry for an IAM role, then associate an EKS access policy with it.

aws eks create-access-entry \
  --cluster-name book-review \
  --principal-arn arn:aws:iam::123456789012:role/github-actions-deploy \
  --region eu-west-1

aws eks associate-access-policy \
  --cluster-name book-review \
  --principal-arn arn:aws:iam::123456789012:role/github-actions-deploy \
  --access-scope type=cluster \
  --policy-arn arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy \
  --region eu-west-1

That IAM role can now authenticate to the cluster and run kubectl operations according to whichever access policy you attached. AWS provides several managed access policies: AmazonEKSClusterAdminPolicy for full administrative access, AmazonEKSAdminPolicy for administrative access scoped to a namespace, and AmazonEKSViewPolicy for read-only access.

This is exactly where a GitHub Actions deployment pipeline fits in: the CI/CD role needs Kubernetes access to run something like helm upgrade, and an access entry is how it gets that access — the role's trust policy uses GitHub OIDC federation so GitHub Actions can assume the AWS role without any long-lived AWS credentials stored anywhere, and the access entry then grants that role Kubernetes permissions once it's in AWS. We'll build the full pipeline in a later post; for now, the mental model to keep is: the IAM role gets a principal into AWS, and the EKS access entry is what gives that principal Kubernetes permissions once it's there. See EKS access entries and the access policy list for the full reference.

The four IAM concerns, side by side

The common mistake is treating the node role as if it covers everything. It doesn't — the node role is for the node, Pod Identity or IRSA is for your applications, and access entries are for humans and automation. Here's the full picture in one place:

Mechanism What it is Who it applies to Why it exists
Cluster role IAM role for the EKS service The EKS control plane EKS needs to manage AWS resources required for the cluster
Node role IAM role for EC2 worker nodes All nodes in the group Nodes need to register with the cluster, pull images, and run node-level components
Pod Identity / IRSA IAM role assigned per workload Individual pods Pods need AWS access without inheriting node-level permissions
Access entries EKS API-managed cluster access IAM users/roles — engineers and CI/CD Controlled authentication and authorization for the Kubernetes API server

In production, every workload that calls an AWS service should have its own scoped pod-level permissions through Pod Identity or IRSA — the node role should carry only the minimum nodes need to operate, and nothing more.

Where each role gets created in practice

In a basic eksctl lab flow, the cluster role and node role are created automatically. Pod Identity associations and access entries can also be managed through eksctl or your IaC tool of choice, but they're deliberate, explicit steps you configure yourself — eksctl won't infer who should have pod-level AWS access or kubectl access on your behalf, and that's exactly the point: those two are workload- and human-specific decisions, not generic cluster bootstrapping.

Four identities, four different jobs: the cluster role runs the control plane, the node role runs the nodes, Pod Identity (or IRSA) runs your applications, and access entries decide who can reach the Kubernetes API at all. Getting comfortable telling these apart — and resisting the shortcut of loading permissions onto whichever role is already in front of you — is exactly the kind of production judgment DMI's graded weekly loop is built to develop.

Want the fundamentals these decisions build on? Start with DMI Self-Paced →