Amazon EKS joins two identity systems, AWS IAM and Kubernetes RBAC, and most EKS compromises exploit the seams between them: a node role every pod can borrow, a cluster creator with permanent cluster-admin, or an aws-auth ConfigMap nobody reviews. This guide covers the EKS-specific controls that close those seams: access entries and access policies in place of aws-auth, EKS Pod Identity and IAM roles for service accounts (IRSA), instance metadata (IMDS) restrictions, the node and add-on roles, and the logs that show who did what. It assumes you already know the Kubernetes basics of service account tokens, RBAC and federation, which are covered in the Kubernetes NHI Security Guide and the Workload Identity Federation Guide. It is written for platform engineers, cloud security teams and auditors who run or assess production EKS clusters.
Key takeaways
- Move to access entries and set the mode to API. AWS describes
aws-authas deprecated; access entries are managed through the EKS API, show up in CloudTrail and cannot lock you out with a YAML typo. The mode change is one-way, so plan it. - Remove the cluster creator's standing admin. Create clusters with
bootstrapClusterCreatorAdminPermissions=falseor disassociateAmazonEKSClusterAdminPolicyafterwards. InCONFIG_MAPmode that access cannot be revoked at all. - Prefer EKS Pod Identity, keep IRSA where it is the only option. Pod Identity needs no per-cluster OIDC provider, adds session tags for ABAC and separates association management (EKS) from permissions (IAM). IRSA remains necessary on Fargate, EKS Anywhere and self-managed clusters.
- Stop pods inheriting the node role. Require IMDSv2 with a hop limit of 1 on node launch templates and move
aws-nodeoff the node role, otherwise every pod on a node holds the node's AWS permissions. - Turn on control plane logging. All five log types are off by default. Without
auditandauthenticatorlogs you cannot map a Kubernetes action back to the IAM session that performed it.
How identity and access work in EKS
Every request to the EKS API server is authenticated by AWS IAM and then authorised by Kubernetes RBAC (or by an EKS access policy, which EKS evaluates alongside RBAC). On the workload side, pods obtain AWS credentials through one of three paths, and the path that wins is the first one the AWS SDK default credential chain finds. Getting EKS identity right means controlling all four planes in the table below.
| Plane | Who or what | EKS mechanism | Main risk |
|---|---|---|---|
| Human and pipeline access to the Kubernetes API | IAM roles assumed by engineers (often via IAM Identity Center) and CI/CD roles | Access entries with access policies or Kubernetes groups; legacy aws-auth ConfigMap | Over-broad mappings to system:masters; unreviewed ConfigMap edits |
| Cluster management in AWS | IAM principals calling eks:* | IAM policies on the EKS service API | Anyone who can call CreateAccessEntry or AssociateAccessPolicy can grant themselves cluster-admin |
| Workload access to AWS | Pods via service accounts | EKS Pod Identity associations or IRSA | Shared roles, wildcard trust, fallback to node role |
| Node and add-on access to AWS | Kubelet, VPC CNI (aws-node), controllers | Node IAM role (instance profile); add-on roles | Node role readable by every pod through IMDS |
Two consequences matter for hardening. First, IAM permissions on the EKS API are a privilege escalation path into Kubernetes: eks:CreateAccessEntry and eks:AssociateAccessPolicy are, in effect, cluster-admin grants and belong in the same review set as iam:PassRole. Second, Kubernetes permissions are a path into AWS: anyone who can create a pod in a namespace can run it under any service account in that namespace, and therefore use whatever IAM role that service account is associated with. The Kubernetes side of that problem is covered in the Kubernetes RBAC Guide.
Admin roles and privileged access
Access entries, access policies and authentication modes
An access entry ties one IAM principal to a cluster. You then either associate one or more AWS-managed access policies (with a cluster or namespace scope) or map the entry to Kubernetes groups and grant those groups permissions with RBAC. The cluster's authentication mode decides which sources EKS reads:
CONFIG_MAP: only theaws-authConfigMap. Legacy; the creator's admin cannot be removed.API_AND_CONFIG_MAP: both. The migration state. Only the creator's entry is created automatically; every otheraws-authmapping must be recreated as an access entry by hand.API: access entries only. The target state recommended in the EKS Best Practices Guide.
The switch is one-way: AWS states that you cannot change back to a mode that removes the EKS API and access entries. Older Kubernetes versions need a minimum platform version (for example eks.6 on 1.28 and eks.1 on 1.29) before access entries can be enabled.
Access policy (arn:aws:eks::aws:cluster-access-policy/...) | Effect | Hardening note |
|---|---|---|
AmazonEKSClusterAdminPolicy | All verbs on all resources, cluster-wide | Break-glass and platform automation only; never a standing human grant |
AmazonEKSAdminPolicy | Namespace admin: workloads, Secrets, Roles and RoleBindings, ServiceAccount impersonation | Can grant itself more within the namespace; scope to named namespaces |
AmazonEKSEditPolicy | Create and change most namespaced resources; reads Secrets; no RBAC permissions | Still able to run pods as any service account in scope, so it inherits their IAM roles |
AmazonEKSViewPolicy | Read most namespaced resources, excluding Secrets | Safe default for developers and support |
AmazonEKSAdminViewPolicy | Read all resources including Secrets | Treat as sensitive despite the "view" name |
AmazonEKSSecretReaderPolicy | Get, list and watch on Secrets | Equivalent to credential access; restrict and monitor |
Removing standing cluster-admin
- New clusters: create with
--access-config authenticationMode=API,bootstrapClusterCreatorAdminPermissions=falseand add an explicit admin entry for a platform role instead. - Existing clusters: after confirming another admin path works, run
aws eks disassociate-access-policyforAmazonEKSClusterAdminPolicyon the creator principal, or delete that access entry. - Break-glass: keep one dedicated IAM role with the cluster admin policy, assumable only through a controlled process (approval, MFA, session recording via CloudTrail), and alert on every assumption.
- Clusters stuck in
CONFIG_MAPmode: the creator keepssystem:masterspermanently, so the best practice guidance is to create clusters from a dedicated automation role nobody else can assume and audit it.
Human access should use IAM roles federated from your identity provider rather than IAM users, so that joiner, mover and leaver changes happen in one place. If you must keep aws-auth during migration, prefer mapRoles over mapUsers, edit it with tooling rather than by hand, and include {{SessionName}} in the mapped username so the Kubernetes audit log records the individual rather than just the shared role. For the general IAM role design see the AWS IAM Policies and Roles Guide; for just-in-time patterns see the Kubernetes RBAC Guide.
Integrations, API tokens and service accounts
EKS Pod Identity versus IRSA
Both mechanisms map a Kubernetes service account to an IAM role and deliver short-lived credentials. The federation mechanics behind IRSA are explained in the Workload Identity Federation Guide; the table focuses on what differs for hardening.
| Attribute | EKS Pod Identity | IRSA |
|---|---|---|
| Trust anchor | Service principal pods.eks.amazonaws.com, same for every cluster | Per-cluster IAM OIDC identity provider |
| Who binds role to service account | EKS association (eks:CreatePodIdentityAssociation); permissions stay in IAM | Role trust policy sub condition plus service account annotation |
| Scope of a binding | One role to one service account in one namespace of one cluster; up to 5,000 associations per cluster | Whatever the trust policy sub and aud conditions allow |
| Session tags | Six transitive tags: eks-cluster-arn, eks-cluster-name, kubernetes-namespace, kubernetes-service-account, kubernetes-pod-name, kubernetes-pod-uid | None |
| Credential delivery | Pod Identity Agent DaemonSet on 169.254.170.23 / [fd00:ec2::23], ports 80 and 2703 | SDK calls sts:AssumeRoleWithWebIdentity using the projected token |
| Where supported | EKS on Linux EC2 nodes; not Fargate, Windows nodes, Outposts, EKS Anywhere or self-managed Kubernetes | Any cluster with a public OIDC issuer, including Fargate |
| Cross-account | Role must be in the cluster's account; chain into the target account with role assumption | Trust policy in the target account can name the cluster's OIDC provider |
Decision guidance: use Pod Identity by default on EC2 Linux nodes. It lets one role serve many clusters without editing trust policies, and its session tags make attribute-based access control possible: a single role can read only S3 objects tagged with the caller's own eks-cluster-name or kubernetes-namespace by comparing against ${aws:PrincipalTag/...}. Keep IRSA for Fargate, Windows and non-EKS clusters, or where a cross-account trust must be expressed in the target account. Small estates with a handful of clusters gain less from ABAC and can run one role per application; large multi-tenant estates benefit most from tag-based policies because they avoid thousands of near-identical roles.
Hardening the role side
- Pod Identity trust policy: allow
sts:AssumeRoleandsts:TagSessionforpods.eks.amazonaws.com, and add"aws:SourceOrgId": "${aws:ResourceOrgId}"(or a source account or cluster ARN condition) to prevent confused deputy use from clusters outside your organisation. - IRSA trust policy: always pin both
oidc.eks.<region>.amazonaws.com/id/<ID>:audtosts.amazonaws.comand:subtosystem:serviceaccount:<namespace>:<name>withStringEquals. A missingsubcondition lets every service account in the cluster assume the role. - One role per application: the exception is a deliberately designed ABAC role using Pod Identity session tags.
- Permissions boundaries: attach a boundary to every workload role so that a role edited by a delegated team cannot exceed a defined maximum.
- Session tag size: if a workload hits
PackedPolicyTooLarge, session tags can be disabled per association, but any ABAC policy relying on them then stops matching. Record that as an exception. - Role-tag collisions: EKS-supplied session tags override IAM role tags with the same key, which is useful: a role tagged with a false
eks-cluster-namecannot spoof it.
The node role and IMDS
Every EC2 node carries an instance profile. A pod that does not use Pod Identity or IRSA, or whose SDK falls through the credential chain, can query the instance metadata service and receive the node role's credentials (MITRE ATT&CK T1552.005, Cloud Instance Metadata API). The node role typically carries AmazonEKS_CNI_Policy and AmazonEC2ContainerRegistryReadOnly, so every pod can manipulate network interfaces and pull any image in the account's registries.
- Launch template: set
http_tokens = "required"(IMDSv2) andhttp_put_response_hop_limit = 1. The extra network hop from a pod's namespace then prevents the response reaching the pod. EKS-provisioned defaults use a hop limit of 2, so this must be set explicitly. - Do not disable IMDS entirely: node components such as the termination handler depend on it. If a specific application needs IMDS, keep IMDSv2 and accept hop limit 2 only on the node group that runs it.
- Host-network pods: pods with
hostNetwork: truealways reach IMDS regardless of the hop limit. Block them with admission policy outside system namespaces; see the Kubernetes Admission Control Guide. - Shrink the node role: move the
aws-nodeDaemonSet to Pod Identity (supported from VPC CNI v1.15.5) or IRSA and removeAmazonEKS_CNI_Policyfrom the node role.
Remaining static credentials
Access keys stored in Kubernetes Secrets or environment variables bypass every control above. The Tesla Kubernetes cryptojacking incident is the standard example: an unauthenticated console led to a pod holding AWS keys, and the keys led to the AWS account. Inventory such keys with IAM credential reports, migrate the consumers, and see the Kubernetes Secrets Security Guide. External systems that need Kubernetes API access (for example a CI runner outside AWS) should assume an IAM role mapped through an access entry rather than hold a long-lived service account token Secret.
Logging and detection
Control plane logging is off by default and is enabled per log type. Logs land in the CloudWatch log group /aws/eks/<cluster-name>/cluster, one stream per component.
| Source | What to watch for | Why it matters |
|---|---|---|
audit log (kube-apiserver-audit-*) | New ClusterRoleBindings to cluster-admin, create on serviceaccounts/token, reads of Secrets, pods/exec in sensitive namespaces, anonymous requests | Kubernetes-side privilege escalation (T1098.006, Additional Container Cluster Roles) and discovery (T1613) |
authenticator log (authenticator-*) | Which IAM ARN and session mapped to which Kubernetes username | Joins audit events to the IAM session and therefore the human |
| CloudTrail (EKS API) | CreateAccessEntry, AssociateAccessPolicy, UpdateClusterConfig (authentication mode, endpoint, logging), CreatePodIdentityAssociation | AWS-side grants of cluster access and changes that weaken logging |
| CloudTrail (STS) | AssumeRoleWithWebIdentity from IRSA roles, AssumeRole by pods.eks.amazonaws.com with session tags, node role credentials used from outside the VPC | Workload credential use and theft (T1528, T1078.004) |
| GuardDuty EKS Protection | Policy:Kubernetes/AnonymousAccessGranted, CredentialAccess:Kubernetes/SuccessfulAnonymousAccess, Policy:Kubernetes/AdminAccessToDefaultServiceAccount, PrivilegeEscalation:Kubernetes/AnomalousBehavior.RoleBindingCreated, CredentialAccess:Kubernetes/AnomalousBehavior.SecretsAccessed | Managed detections over the audit stream |
Metrics that show the programme is working:
- Percentage of clusters in
APIauthentication mode (target 100%). - Number of principals with
AmazonEKSClusterAdminPolicyorsystem:mastersper cluster (target: break-glass role plus platform automation only). - Percentage of workloads calling AWS that use Pod Identity or IRSA, against those using static keys or the node role (target: no static keys).
- Percentage of node groups with IMDSv2 required and hop limit 1.
- Percentage of clusters with
auditandauthenticatorlogging enabled. - Time from an unexpected cluster-admin grant (CloudTrail or audit) to alert.
Hardening baseline
Roll changes out cluster by cluster, starting with a non-production cluster that mirrors production add-ons.
- Observe: enable all five control plane log types, export
aws-auth, list access entries (aws eks list-access-entries), Pod Identity associations and IRSA roles, and record which pods currently obtain credentials from IMDS (CloudTrail calls made with the node role from pods). - Recreate access: switch to
API_AND_CONFIG_MAP, recreate eachaws-authmapping as an access entry with the narrowest access policy and namespace scope, then compare authenticator logs to confirm users now resolve through entries. Rollback at this stage is simple: the ConfigMap still works. - Cut over: switch to
API. This cannot be reverted, so do it only once a break-glass entry is tested and every mapping has a matching entry. - Remove creator admin and restrict IAM permissions on
eks:CreateAccessEntry,eks:AssociateAccessPolicyandeks:UpdateClusterConfigto the platform pipeline role, ideally with an SCP. - Migrate workloads to Pod Identity (or IRSA where required) one namespace at a time. Confirm in CloudTrail that the new role is in use before removing old keys.
- Close IMDS: roll new launch templates with hop limit 1 through a canary node group; roll back by reverting the template version if a workload breaks.
- Restrict the endpoint: enable private endpoint access and either disable public access or limit it to known CIDR ranges.
- Enforce: disable
automountServiceAccountTokenon default service accounts, removesystem:unauthenticatedfrom all bindings exceptsystem:public-info-viewer(older clusters may still bind it tosystem:discoveryandsystem:basic-user), and blockhostNetworkpods through admission control.
Framework mapping
| Control area | NIST SP 800-53 Rev. 5 | NIST CSF 2.0 | CIS Controls v8.1 | ISO/IEC 27001:2022 Annex A |
|---|---|---|---|---|
| Access entries, removing creator admin | AC-2, AC-6 | PR.AA-05 | Control 5, Control 6 | A.5.18, A.8.2 |
| Pod Identity, IRSA, no static keys | IA-5, IA-9 | PR.AA-01 | Control 6 | A.5.17 |
| IMDS restriction, node role reduction | AC-6 | PR.AA-05 | Control 6 | A.5.15 |
| Control plane logs, CloudTrail, GuardDuty | AU-2, AU-6 | DE.CM-03 | Control 8 | A.8.15 |
Common misconfigurations
- IRSA trust without a
subcondition. Copying a trust policy that checks onlyaudlets any service account in the cluster assume the role, including ones created by a tenant with namespace-level rights. - "Edit" treated as low risk.
AmazonEKSEditPolicycannot touch RBAC, but it can create a pod using any service account in the namespace and so inherit every IAM role associated with those accounts. Review namespace edit grants together with the roles mapped in that namespace. - Half-finished migration. Clusters left in
API_AND_CONFIG_MAPkeep a second, unaudited path: a lateraws-authedit grants access that never appears in access entry reviews. - Hop limit left at 2. Pod Identity is deployed but IMDS is still reachable, so any pod without an association silently falls back to the node role, which often has broad ECR and EC2 network rights.
- Logging off or partial. Only
apilogs enabled, so there is no audit trail of RBAC changes and no authenticator mapping from Kubernetes user to IAM session. - Static keys left in Secrets after migration. Workloads move to Pod Identity but old access keys remain active in IAM and in Secrets, which is exactly the exposure seen in the Tesla incident. Deactivate and delete keys once CloudTrail shows no further use.
Practitioner checklist
- Every cluster is in
APIauthentication mode andaws-authhas been deleted or emptied. - The cluster creator has no standing access entry; one tested break-glass role holds
AmazonEKSClusterAdminPolicy. - Human access uses federated IAM roles mapped to namespace-scoped access policies, not IAM users.
- IAM rights to
eks:CreateAccessEntry,eks:AssociateAccessPolicyandeks:UpdateClusterConfigare limited to the platform pipeline and alerted on. - Workloads use Pod Identity (trust policy with
aws:SourceOrgId) or IRSA (trust pinned toaudand exactsub), with permissions boundaries. - Node launch templates require IMDSv2 with hop limit 1;
hostNetworkpods are blocked outside system namespaces. aws-noderuns on its own role andAmazonEKS_CNI_Policyis off the node role.- No AWS access keys exist in Secrets, ConfigMaps or environment variables.
auditandauthenticatorcontrol plane logs, CloudTrail and GuardDuty EKS Protection are enabled and routed to the SOC.- The API endpoint is private or restricted to known CIDR ranges.
Standards and references
- AWS: Amazon EKS Best Practices Guide, Identity and Access Management
- AWS: Grant IAM users access to Kubernetes with EKS access entries
- AWS: Review access policy permissions
- AWS: Learn how EKS Pod Identity grants pods access to AWS services
- AWS: Grant pods access to AWS resources based on tags
- AWS: Send control plane logs to CloudWatch Logs
- AWS: GuardDuty EKS Protection finding types
- MITRE ATT&CK: Cloud Instance Metadata API
Related NHI Mgmt Group resources: Kubernetes NHI Security Guide · Workload Identity Federation Guide · GKE Identity Hardening Guide · AKS Identity Hardening Guide · AWS IAM Policies and Roles Guide