A lot of AWS security advice boils down to one sentence: don’t put access keys in your code, use a role. That’s easy to repeat and harder to explain, and interviewers often ask you to explain it. What is a role, if not a user? Where do the credentials come from if you never configure any? And what actually expires?
A role is an identity nobody logs in as
An IAM user is a long-lived identity with long-lived credentials: a password for the console and optionally access keys (AKIA...) for the API. Those keys work until someone deletes them, which in practice is often never.
An IAM role is also an identity with permissions, but it has no password and no permanent keys. Instead, a trusted principal assumes it and receives temporary credentials for a limited time. Every role carries two different policies, and mixing them up is the most common beginner mistake:
- The trust policy says who may assume the role. It’s a resource policy on the role itself, and its principal is a service (
ec2.amazonaws.com), an account, a specific role, or a federated identity provider. - The permission policies say what the role can do once assumed, like any identity policy.
Here’s a trust policy that lets Lambda assume a role, which is what makes it an “execution role”:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "Service": "lambda.amazonaws.com" },
"Action": "sts:AssumeRole"
}
]
}And a permission policy attached to the same role, letting the function read one table:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["dynamodb:GetItem", "dynamodb:Query"],
"Resource": "arn:aws:dynamodb:eu-west-1:111122223333:table/Orders"
}
]
}If you put dynamodb:GetItem in the trust policy, or lambda.amazonaws.com in the permission policy, nothing works, and the error messages don’t make the mix-up obvious. Keep them straight: trust is “who gets in”, permissions are “what they can touch”.
What AssumeRole returns
Assuming a role is an API call to AWS STS (Security Token Service). You can do it yourself with the CLI:
aws sts assume-role \
--role-arn arn:aws:iam::111122223333:role/orders-reader \
--role-session-name ana-debugging \
--duration-seconds 3600The response (illustrative values, shortened) has three secrets and an expiry:
{
"Credentials": {
"AccessKeyId": "ASIAEXAMPLE7QXZ4KJ2M",
"SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
"SessionToken": "IQoJb3JpZ2luX2VjEXAMPLE...",
"Expiration": "2026-10-07T15:42:10+00:00"
},
"AssumedRoleUser": {
"AssumedRoleId": "AROAEXAMPLEID:ana-debugging",
"Arn": "arn:aws:sts::111122223333:assumed-role/orders-reader/ana-debugging"
}
}A few things to notice. The access key starts with ASIA, which marks temporary credentials; long-term user keys start with AKIA. All three values are needed together: an ASIA key without its session token is rejected. The Arn is an assumed-role session, named after the session name you chose, so CloudTrail shows ana-debugging rather than just the role. And there’s an Expiration.
Session length defaults to one hour. You can request 15 minutes up to the role’s configured maximum session duration, which can be set as high as 12 hours. One limit trips people: if you use one role’s temporary credentials to assume another role (“role chaining”), the second session is capped at one hour regardless.
For the call to succeed, the trust policy must allow your principal. Across accounts, your own identity also needs an identity policy allowing sts:AssumeRole on that role ARN. Within one account, a trust policy that names your user or role ARN directly is enough on its own, while one that trusts the whole account (arn:aws:iam::111122223333:root) still needs that identity policy.
How services get credentials without you doing anything
You rarely call assume-role in application code, because AWS compute services do it for you and keep the credentials fresh.
EC2 uses an instance profile, a container that attaches a role to an instance. The instance metadata service (IMDS) at 169.254.169.254 serves temporary credentials for that role and rotates them before they expire. With IMDSv2, which you should require, the request needs a session token first:
TOKEN=$(curl -s -X PUT http://169.254.169.254/latest/api/token \
-H "X-aws-ec2-metadata-token-ttl-seconds: 300")
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
http://169.254.169.254/latest/meta-data/iam/security-credentials/orders-app-roleThat’s illustrative and only works from inside an instance. IMDSv2’s PUT-then-GET handshake exists because IMDSv1 could be read through server-side request forgery bugs: if an attacker could make your app fetch a URL, they could fetch the role’s credentials.
Lambda assumes the execution role when it creates an environment and exposes the credentials to your code as environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN).
ECS tasks get a task role via an endpoint the ECS agent provides. EKS pods use EKS Pod Identity or IAM roles for service accounts (IRSA), which exchange a Kubernetes service account token for role credentials.
In every case, the AWS SDK’s default credential provider chain finds them. It looks in a fixed order: environment variables, shared config and credentials files (~/.aws/), web identity tokens, the container credentials endpoint, then instance metadata. Your code just does new S3Client({}) or boto3.client("s3"). If you ever see a hard-coded key in source, that’s the thing to remove.
The order has a gotcha: environment variables win. A developer who exported an old AWS_ACCESS_KEY_ID in their shell profile will use that instead of the SSO profile they think they’re using. aws sts get-caller-identity settles it in one command.
Why temporary beats long-lived
The argument is about what happens when a secret leaks, because eventually one does: a key pasted into a GitHub repo, a laptop backup, a log line.
A leaked AKIA key works for anyone, from anywhere, until someone notices and deactivates it. Bots scan public repositories for AWS keys within minutes of a push.
A leaked ASIA set works only until its expiration, typically within the hour. It was issued for a specific role, so its blast radius is that role’s permissions. It’s traceable to a session name in CloudTrail. And there’s no long-term secret to rotate, because there isn’t one.
If you do need to cut off temporary credentials before they expire, you can’t delete them, but you can revoke them: the IAM console’s “Revoke active sessions” adds a policy to the role that denies any request from credentials issued before the current time (it uses the aws:TokenIssueTime condition key). New sessions still work.
For humans, the equivalent is signing in through IAM Identity Center (or another identity provider) and getting role sessions, rather than creating IAM users with access keys. aws configure sso then aws sso login gives your CLI short-lived role credentials.
Interview practice: the key in the CI pipeline
A team’s CI pipeline deploys to AWS using an IAM user’s access key stored as a CI secret. Security asks them to stop. The CI provider supports OpenID Connect (OIDC). What would you propose, and what does the trust policy need to say?
Answer
Replace the user with a role the pipeline assumes using AssumeRoleWithWebIdentity. Register the CI provider as an OIDC identity provider in IAM. On each run, the CI system issues a signed OIDC token for that job; the pipeline exchanges it with STS for temporary role credentials that last for the deployment and expire afterwards. No AWS secret is stored in CI at all.
The trust policy’s principal is the federated OIDC provider, the action is sts:AssumeRoleWithWebIdentity, and, importantly, it needs conditions on the token’s claims. At minimum, the audience (aud) should match the expected value, and the subject (sub) should be restricted to the specific repository and branch or environment, for example only the main branch of acme/orders-service. Without the subject condition, any repository on that CI provider could assume your role, which is a real misconfiguration that has been found in the wild.
Then give the role only the permissions the deployment needs, and delete the old IAM user’s access key once the new path works. CloudTrail will show each deployment as an assumed-role session, which is easier to audit than one shared key.
Keep going
- AWS interview questions
- AWS MCQs
- Cross-account IAM role assumption
- AWS IAM policies and permission decisions
- Temporary security credentials in IAM (AWS docs)