IAM Roles: Why I Never Embed AWS Credentials
How IAM roles provide temporary credentials that eliminate the risk of long-lived access keys in your code.
I've seen AWS access keys committed to Git repos, hardcoded in Lambda functions, and sitting in EC2 environment variables for years. Each one is a breach waiting to happen. Attackers scan for exposed keys constantly.
IAM roles solve this. No static credentials to leak. Temporary tokens that expire automatically. This should be your default for anything running in AWS.
The Problem with Access Keys
Access keys are long-lived. Once created, they work forever until you rotate or delete them.
Common places I've found exposed keys:
- GitHub repos (including "private" ones that got leaked)
- Docker images
- CloudFormation templates
- EC2 user data scripts
- Local developer machines (in
~/.aws/credentials)
When keys leak, attackers can use them until someone notices and rotates them. That window can be hours, days, or months.
How IAM Roles Work
Instead of static keys, IAM roles provide temporary credentials through AWS Security Token Service (STS). These credentials:
- Expire automatically (default 1 hour, configurable)
- Are rotated by AWS before expiration
- Never need to be stored in code or config files
When an EC2 instance or Lambda function assumes a role, AWS injects temporary credentials into the runtime. Your code uses the AWS SDK, which automatically picks up these credentials. You never see or handle the keys.
Where to Use Roles
EC2 instances: Attach an instance profile (which wraps a role) to your EC2 instances. The AWS SDK automatically uses these credentials.
# This just works - no credentials needed
import boto3
s3 = boto3.client('s3')
s3.list_buckets()
Lambda functions: Every Lambda has an execution role. Define what the function can access in that role's policy.
ECS/EKS tasks: Task roles give each container its own permissions without sharing node-level access.
Cross-account access: Roles enable secure access between AWS accounts without sharing credentials.
Least Privilege
Roles are only as secure as their policies. I see roles with "Action": "*" and "Resource": "*" - essentially admin access.
Instead, scope permissions tightly:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::my-bucket/uploads/*"
}
]
}
This role can only read and write to a specific prefix in one bucket. If compromised, the blast radius is limited.
When IAM Users Make Sense
IAM users aren't obsolete, but they have specific use cases:
Human access to console: People need usernames and passwords for console login. Enable MFA.
CI/CD pipelines outside AWS: GitHub Actions or Jenkins running outside AWS need access keys. Rotate them regularly and use OIDC federation where possible.
Third-party integrations: Some tools require access keys. Scope permissions minimally and monitor usage.
Even for these cases, prefer temporary credentials through AssumeRole when possible.
Auditing Existing Keys
Find old access keys in your account:
aws iam generate-credential-report
aws iam get-credential-report --output text --query Content | base64 -d
Look for:
- Keys older than 90 days
- Keys that haven't been used recently (might be forgotten)
- Keys on service accounts that could use roles instead
Key Takeaways
- IAM roles provide temporary credentials that expire automatically - no keys to leak
- EC2, Lambda, ECS, and EKS should always use roles, never embedded credentials
- Scope role permissions tightly - least privilege limits breach impact
- Reserve IAM users for human console access and external CI/CD
- Audit existing access keys regularly and migrate to roles where possible
Written by Bar Tsveker
Senior CloudOps Engineer specializing in AWS, Terraform, and infrastructure automation.
Thanks for reading! Have questions or feedback?