
From Local AWS Keys to EKS Pod Identity: Decoupling Cloud Credentials for SREs
Stop injecting long-lived AWS credentials into Kubernetes workloads. Discover how to transition from local static keys to Amazon EKS Pod Identity, decouple cloud credentials, and embrace an identity-driven, least-privilege architecture for your SRE operations.
When we run applications locally, credentials often begin innocently.
Maybe you export a profile:
1
export AWS_PROFILE=devMaybe your application reads credentials from:
1
~/.aws/credentialsOr perhaps a Kubernetes workload receives them through a Secret:
1
2
3
4
5
6
7
8
9
10
11
12
env:
- name: AWS_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: aws-credentials
key: access-key
- name: AWS_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: aws-credentials
key: secret-keyFor a local K3s cluster or development environment, this may appear convenient.
But once that workload moves to Amazon EKS, the operational question changes.
How does this pod access AWS without storing AWS credentials inside the cluster?
This is where Amazon EKS Pod Identity changes the model.
Instead of distributing credentials to containers, we associate a Kubernetes ServiceAccount with an IAM role. EKS then provides temporary credentials to workloads using that ServiceAccount. AWS describes Pod Identity as a way to give applications IAM permissions similarly to how EC2 instance profiles provide permissions to EC2 instances.
For SREs, the important shift is simple:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
OLD
Application
|
v
Kubernetes Secret
|
v
AWS Access Key + Secret Key
|
v
AWS API
NEW
Application Pod
|
v
Kubernetes ServiceAccount
|
v
EKS Pod Identity Association
|
v
IAM Role
|
v
Temporary AWS Credentials
|
v
AWS APIWe are no longer managing AWS credentials.
We are managing identity relationships.
The Local Mental Model
Imagine a simple application that reads objects from an S3 bucket.
Locally, the workflow may look like this:
1
2
3
4
aws configure --profile demo
export AWS_PROFILE=demo
python app.pyThe AWS SDK discovers the local credentials and uses them.
If we containerize the application, someone may be tempted to pass credentials into the container:
1
2
3
4
docker run \
-e AWS_ACCESS_KEY_ID="$AWS_ACCESS_KEY_ID" \
-e AWS_SECRET_ACCESS_KEY="$AWS_SECRET_ACCESS_KEY" \
my-appAnd when that application reaches Kubernetes, the same mental model often becomes:
1
2
3
4
5
6
7
8
9
10
AWS credentials
|
v
Kubernetes Secret
|
v
Environment Variables
|
v
ApplicationTechnically, this can work.
Operationally, however, we have created several new responsibilities:
- Who rotates the access key?
- Where is the credential stored?
- Which workloads can read the Secret?
- What happens when the credential leaks?
- How do we audit which pod used it?
- How do we remove the key without downtime?
The cloud-native solution is not to build better mechanisms for distributing static credentials.
It is to stop distributing them.
The EKS Model: Identity Instead of Credentials
With EKS Pod Identity, the relationship becomes:
1
2
3
4
5
6
7
8
9
10
11
12
Namespace
|
+-- ServiceAccount: s3-reader
|
v
Pod Identity Association
|
v
IAM Role
|
v
S3 APIA Pod Identity association maps:
1
2
3
4
EKS Cluster
+ Namespace
+ Kubernetes ServiceAccount
+ IAM RoleAWS stores that association through the EKS API. Unlike IAM Roles for Service Accounts (IRSA), Pod Identity does not require an IAM role annotation on the Kubernetes ServiceAccount.
That distinction is important when troubleshooting.
Running:
1
kubectl describe serviceaccount s3-readerand not seeing an IAM annotation does not mean Pod Identity is broken.
The association exists outside the Kubernetes object.
Step 1: Define the Variables
For this example, assume we already have an EKS cluster.
1
2
3
4
5
export CLUSTER_NAME=my-eks-cluster
export AWS_REGION=us-east-1
export NAMESPACE=demo
export SERVICE_ACCOUNT=s3-reader
export ROLE_NAME=eks-s3-readerGet the AWS account ID:
1
2
3
export ACCOUNT_ID=$(aws sts get-caller-identity \
--query Account \
--output text)Verify access to the cluster:
1
kubectl get nodesBefore touching IAM, make sure Kubernetes access actually works.
Step 2: Install the EKS Pod Identity Agent
Standard EKS clusters use the EKS Pod Identity Agent on worker nodes.
Install it as an EKS add-on:
1
2
3
4
aws eks create-addon \
--cluster-name "$CLUSTER_NAME" \
--addon-name eks-pod-identity-agent \
--region "$AWS_REGION"AWS documents the agent as a DaemonSet that runs on cluster nodes and supplies Pod Identity credentials to workloads on those nodes.
Check that the agent is running:
1
2
kubectl get pods -n kube-system \
| grep eks-pod-identity-agentYou should see Pod Identity Agent pods in the
Running state.Using EKS Auto Mode?
There is an important exception.
EKS Auto Mode integrates Pod Identity functionality, so you don't separately install the Pod Identity Agent add-on for an Auto Mode cluster.
Step 3: Create the Application Permission Policy
Our example application should only read objects from one S3 bucket.
Create:
1
permissions.json1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ListApplicationBucket",
"Effect": "Allow",
"Action": [
"s3:ListBucket"
],
"Resource": "arn:aws:s3:::my-application-bucket"
},
{
"Sid": "ReadApplicationObjects",
"Effect": "Allow",
"Action": [
"s3:GetObject"
],
"Resource": "arn:aws:s3:::my-application-bucket/*"
}
]
}Create the IAM policy:
1
2
3
aws iam create-policy \
--policy-name EksApplicationS3ReadOnly \
--policy-document file://permissions.jsonOur goal is intentionally not:
1
2
3
4
{
"Action": "s3:*",
"Resource": "*"
}Moving credentials out of Kubernetes is only half the job.
The IAM permissions still need to follow least privilege.
Step 4: Create the Pod Identity IAM Role
Now comes the architectural difference.
Instead of trusting a human IAM user or an EC2 instance, the role trusts the EKS Pod Identity service principal:
1
pods.eks.amazonaws.comCreate:
1
trust-policy.json1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowEksAuthToAssumeRoleForPodIdentity",
"Effect": "Allow",
"Principal": {
"Service": "pods.eks.amazonaws.com"
},
"Action": [
"sts:AssumeRole",
"sts:TagSession"
]
}
]
}AWS requires the Pod Identity trust relationship to permit both
sts:AssumeRole and sts:TagSession.Create the role:
1
2
3
aws iam create-role \
--role-name "$ROLE_NAME" \
--assume-role-policy-document file://trust-policy.jsonAttach our application policy:
1
2
3
4
aws iam attach-role-policy \
--role-name "$ROLE_NAME" \
--policy-arn \
"arn:aws:iam::$ACCOUNT_ID:policy/EksApplicationS3ReadOnly"At this point we have:
1
2
3
4
5
IAM Role
|
+-- Trusts EKS Pod Identity
|
+-- Can read selected S3 resourcesBut Kubernetes still knows nothing about it.
That comes next.
Step 5: Create the Kubernetes ServiceAccount
Create the namespace:
1
kubectl create namespace "$NAMESPACE"Create the ServiceAccount:
1
2
kubectl create serviceaccount "$SERVICE_ACCOUNT" \
--namespace "$NAMESPACE"Verify it:
1
2
3
kubectl get serviceaccount \
"$SERVICE_ACCOUNT" \
-n "$NAMESPACE"Notice what we did not do:
1
kubectl annotate serviceaccount ...Pod Identity doesn't require the IAM role ARN to be stored as a ServiceAccount annotation.
That is one of the biggest differences for engineers familiar with IRSA.
Step 6: Create the Pod Identity Association
Now connect the Kubernetes identity to the AWS identity.
1
2
3
4
5
6
aws eks create-pod-identity-association \
--cluster-name "$CLUSTER_NAME" \
--namespace "$NAMESPACE" \
--service-account "$SERVICE_ACCOUNT" \
--role-arn \
"arn:aws:iam::$ACCOUNT_ID:role/$ROLE_NAME"Conceptually:
1
2
3
4
5
6
7
demo/s3-reader
|
v
EKS Pod Identity Association
|
v
arn:aws:iam::<account>:role/eks-s3-readerList the associations:
1
2
aws eks list-pod-identity-associations \
--cluster-name "$CLUSTER_NAME"The Pod Identity association is managed through EKS rather than being stored as metadata on the ServiceAccount.
This separation becomes extremely useful when infrastructure and Kubernetes responsibilities belong to different teams.
Step 7: Attach the ServiceAccount to the Workload
Now deploy an application using our ServiceAccount.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
apiVersion: apps/v1
kind: Deployment
metadata:
name: aws-client
namespace: demo
spec:
replicas: 1
selector:
matchLabels:
app: aws-client
template:
metadata:
labels:
app: aws-client
spec:
serviceAccountName: s3-reader
containers:
- name: aws-client
image: public.ecr.aws/aws-cli/aws-cli:2
command:
- /bin/sh
- -c
args:
- |
while true; do
aws sts get-caller-identity
sleep 300
doneApply it:
1
kubectl apply -f deployment.yamlGet the pod:
1
kubectl get pods -n demoThen execute:
1
2
3
4
kubectl exec \
-n demo \
deployment/aws-client \
-- aws sts get-caller-identityThe application never received:
1
2
AWS_ACCESS_KEY_ID=<hardcoded-key>
AWS_SECRET_ACCESS_KEY=<hardcoded-secret>Instead, the supported AWS SDK/CLI credential provider chain can discover the temporary credentials supplied through EKS Pod Identity.
That is the paradigm shift.
What Actually Happened?
From the application's perspective, surprisingly little changed.
It still does something like:
1
2
3
import boto3
s3 = boto3.client("s3")There is no credential-handling logic in the application.
No:
1
2
3
4
5
boto3.client(
"s3",
aws_access_key_id="...",
aws_secret_access_key="..."
)The AWS SDK follows its credential provider chain.
EKS injects the information required by supported SDK versions to retrieve Pod Identity credentials.
This gives us a useful separation:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Application Team
|
| writes normal AWS SDK code
v
AWS SDK
|
| discovers workload credentials
v
Pod Identity
|
| maps Kubernetes identity
v
IAM
|
| defines authorization
v
AWS ServiceThe application deals with business logic.
The platform deals with identity.
IAM deals with authorization.
That separation is exactly what we want.
Pod Identity vs the Node IAM Role
This distinction matters enormously.
Without workload-level identity, engineers sometimes give the EKS worker node role every permission applications require.
Eventually, it looks something like:
1
2
3
4
5
6
7
8
9
Worker Node Role
|
+-- ECR access
+-- S3 access
+-- DynamoDB access
+-- SQS access
+-- Secrets Manager access
+-- CloudWatch access
+-- ...Now every workload on the node potentially exists within a much larger credential boundary.
Pod Identity lets us move toward:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
payment-service
|
+-- payment-service-role
|
+-- DynamoDB payment table
report-service
|
+-- report-service-role
|
+-- S3 reports bucket
notification-service
|
+-- notification-service-role
|
+-- SQS notification queueAWS describes credential isolation as one of Pod Identity's benefits, particularly when access to the EC2 Instance Metadata Service is restricted so containers cannot simply retrieve the underlying node credentials.
That last part is important.
Pod Identity should not become an excuse to ignore node-level credential isolation.
Pod Identity vs IRSA
If you have operated EKS before, you are probably thinking:
Isn't this what IRSA already does?
Yes — IAM Roles for Service Accounts also provides fine-grained IAM permissions to Kubernetes workloads.
IRSA remains supported.
But its architecture involves an IAM OIDC identity provider associated with the EKS cluster.
A simplified IRSA model looks like:
1
2
3
4
5
6
7
8
9
10
11
ServiceAccount
|
| IAM role annotation
v
Cluster OIDC Provider
|
v
IAM Trust Policy
|
v
IAM RolePod Identity looks more like:
1
2
3
4
5
6
7
8
9
10
ServiceAccount
|
v
EKS Pod Identity Association
|
v
EKS Auth
|
v
IAM RoleAWS notes that Pod Identity does not require configuring an OIDC provider and makes IAM roles easier to reuse across clusters because the trust principal is the EKS Pod Identity service rather than a cluster-specific OIDC provider.
For SREs managing many clusters, that operational difference becomes significant.
Troubleshooting: "My Pod Still Can't Access AWS"
Identity problems often appear as application problems.
Here are the first places I check.
1. Is the ServiceAccount correct?
1
2
3
kubectl get pod \
-n demo \
-o jsonpath='{.items[0].spec.serviceAccountName}'Expected:
1
s3-readerIf the workload is using:
1
defaultyour association will not help it.
2. Does the Pod Identity association exist?
1
2
aws eks list-pod-identity-associations \
--cluster-name "$CLUSTER_NAME"Remember:
1
kubectl describe serviceaccountdoes not show the association.
It lives in EKS.
3. Is the Pod Identity Agent running?
1
2
3
kubectl get pods \
-n kube-system \
| grep eks-pod-identity-agentFor standard EKS worker-node configurations using Pod Identity, the agent needs to be available on the node.
4. Does the IAM trust policy allow Pod Identity?
Check:
1
2
3
aws iam get-role \
--role-name "$ROLE_NAME" \
--query Role.AssumeRolePolicyDocumentLook for:
1
2
3
"Principal": {
"Service": "pods.eks.amazonaws.com"
}and:
1
2
3
4
"Action": [
"sts:AssumeRole",
"sts:TagSession"
]Both operations are part of the documented Pod Identity trust relationship.
5. Does the role actually have application permissions?
1
2
aws iam list-attached-role-policies \
--role-name "$ROLE_NAME"Authentication can succeed while authorization fails.
These are separate problems.
1
2
3
4
5
Authentication:
Who is this workload?
Authorization:
What may this workload do?Pod Identity solves the first.
Your IAM policies still determine the second.
6. Is the application using a compatible AWS SDK?
The application should use a supported AWS SDK version and its normal/default credential provider chain. AWS specifically calls this out as part of the Pod Identity setup.
If someone wrote custom credential-loading logic ten years ago, Pod Identity cannot magically override that application design.
A 2026 Improvement: Session Policies
There is another useful development for larger EKS environments.
In March 2026, AWS added session policies for EKS Pod Identity.
Session policies allow teams to further restrict the permissions available through a Pod Identity association without necessarily creating another IAM role for every small permission variation.
Think about a platform with many workloads:
1
2
3
4
5
6
7
8
9
10
11
Shared IAM Role
|
+-- Base permissions
|
+-- Pod Identity Association A
| |
| +-- narrower session permissions
|
+-- Pod Identity Association B
|
+-- different narrower permissionsFor large organizations, this can help reduce role proliferation while preserving fine-grained authorization boundaries.
I would still start with straightforward dedicated roles when learning the model.
But for platform teams operating hundreds or thousands of workload identities, session policies are worth understanding.
The SRE Takeaway
The interesting part of EKS Pod Identity is not the AWS CLI command.
It is the change in responsibility.
Locally, we often think:
1
"My application needs AWS credentials."On EKS, a better question is:
1
"What AWS identity should this workload have?"That leads to a completely different architecture.
Instead of:
1
2
3
4
5
6
7
8
9
Create access key
↓
Store secret
↓
Distribute secret
↓
Rotate secret
↓
Hope nobody copied secretwe get:
1
2
3
4
5
6
7
8
9
Create IAM role
↓
Define least-privilege permissions
↓
Associate role with ServiceAccount
↓
Run workload
↓
Receive temporary credentialsFor an SRE, that means fewer long-lived credentials to rotate, fewer secrets to distribute, and clearer workload-level permission boundaries.
Final Mapping
The local-to-EKS transition now looks like this:
| Local / Simple Environment | Amazon EKS |
|---|---|
| ~/.aws/credentials | EKS Pod Identity |
| Environment variables containing AWS keys | Temporary workload credentials |
| Shared AWS user | IAM role |
| Kubernetes Secret containing keys | Pod Identity association |
| Application manages credentials | AWS SDK credential chain |
| One credential used by many apps | IAM permissions per ServiceAccount |
| Manual credential rotation | Temporary credential delivery |
The larger lesson is the same one we encountered when moving from local infrastructure to managed AWS services:
Do not simply recreate the local implementation in the cloud.
Map the responsibility to the managed primitive designed for it.
With ECR, we stopped treating the local Docker daemon as our image registry.
With ALB, we stopped treating a manually maintained HAProxy configuration as our cloud routing layer.
With EKS, we stopped operating the Kubernetes control plane ourselves.
And with EKS Pod Identity, we stop treating static credentials as application configuration.
We move identity out of the container and into the platform.
That is a much better place for an SRE to manage it.
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article