Definition
- IAM Role – An identity with no permanent credentials that is assumed through STS to receive temporary, auto-rotating credentials, preferred for all programmatic access.
- IAM User access key – A permanent two-part secret (key ID and secret key) that does not expire on its own and must be manually rotated.
- Instance Profile – A wrapper that attaches a Role to an EC2 instance so the SDK auto-fetches temporary credentials from the metadata endpoint.
- STS (Security Token Service) – The AWS service that issues temporary credentials when a Role is assumed.
- Cross-account access – A trust policy on a Role in one account allows a principal from another account to assume it, avoiding the need to copy access keys.
An IAM User's access key is like giving a contractor a permanent master key to National Bank of India that never stops working – if he loses it, a thief can enter the vault forever. An IAM Role is like a temporary visitor badge printed at the front desk that stops working after one hour. If the badge is dropped in the car park, it is already useless. So for machines and contractors we always hand out the expiring badge, never the permanent master key.
Understanding the Concepts
What is an IAM Role and why does it exist?
An IAM Role is an identity you assume temporarily to get short-lived credentials from STS. Unlike an IAM User, a Role has no password and no permanent access key. It exists because permanent credentials are dangerous: they do not expire, they can leak, and if they do, an attacker has durable access until a human manually rotates them. A Role eliminates that risk by issuing credentials that expire automatically.
import software.amazon.awssdk.services.sts.StsClient;
import software.amazon.awssdk.services.sts.model.GetCallerIdentityRequest;
public class CheckRoleIdentity {
public static void main(String[] args) {
StsClient sts = StsClient.create();
var identity = sts.getCallerIdentity(
GetCallerIdentityRequest.builder().build());
System.out.println("Account: " + identity.account());
System.out.println("ARN: " + identity.arn());
// If running on EC2 with a Role, this shows the assumed role ARN
}
}
How does an EC2 instance assume a Role?
When the settlement service runs on EC2, you attach an instance profile that wraps a Role. The AWS SDK's default credential provider chain automatically fetches temporary credentials from the instance metadata endpoint (169.254.169.254), uses them, and refreshes them before they expire. You write zero credential-handling code.
The metadata endpoint is available only from inside the instance, and IMDSv2 requires a session token to access it, adding another layer of protection.
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.regions.Region;
public class SettlementService {
public static void main(String[] args) {
// On EC2 this picks up the Role's temporary credentials automatically
// No access key in code, no secret in config files
S3Client s3 = S3Client.builder()
.region(Region.AP_SOUTH_1)
.credentialsProvider(DefaultCredentialsProvider.create())
.build();
var objects = s3.listObjectsV2(b -> b.bucket("nbi-customer-statements"));
objects.contents().forEach(obj ->
System.out.println("Statement: " + obj.key()));
}
}
How does cross-account access work with Roles?
National Bank of India has a production account and an analytics account. The analytics account must read production's settlement data. Instead of copying production access keys into the analytics account, you create a Role in production whose trust policy allows the analytics account to assume it. The analytics job calls assumeRole and gets temporary production credentials scoped to exactly what it needs.
import software.amazon.awssdk.services.sts.StsClient;
import software.amazon.awssdk.services.sts.model.AssumeRoleRequest;
import software.amazon.awssdk.auth.credentials.AwsSessionCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.regions.Region;
public class CrossAccountAnalytics {
public static void main(String[] args) {
StsClient sts = StsClient.create();
var assumed = sts.assumeRole(AssumeRoleRequest.builder()
.roleArn("arn:aws:iam::111111111111:role/ProdStatementReaderRole")
.roleSessionName("analytics-daily-report")
.build());
var tempCreds = assumed.credentials();
// Use temporary production credentials from analytics account
S3Client prodS3 = S3Client.builder()
.region(Region.AP_SOUTH_1)
.credentialsProvider(StaticCredentialsProvider.create(
AwsSessionCredentials.create(
tempCreds.accessKeyId(),
tempCreds.secretAccessKey(),
tempCreds.sessionToken())))
.build();
System.out.println("Reading production statements with temp creds");
}
}
How does Lambda use Roles?
A Lambda function has an execution role. When the function is invoked, Lambda assumes that role automatically and makes the temporary credentials available through environment variables that the SDK reads. You never see the credentials in code.
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import software.amazon.awssdk.services.s3.S3Client;
import java.util.Map;
public class StatementProcessor implements RequestHandler<Map<String, Object>, String> {
// Lambda assumes the execution role automatically
private final S3Client s3 = S3Client.create();
@Override
public String handleRequest(Map<String, Object> event, Context context) {
// Temporary credentials from the execution role – no keys anywhere
s3.getObject(b -> b.bucket("nbi-customer-statements").key("stmt-2024.pdf"));
return "processed";
}
}
Why must application code never embed long-lived keys?
An access key is a two-part secret (key ID and secret key) that does not expire on its own. If it lands in a Git commit, a Docker image layer, a log line, or a stack trace, it is compromised forever until someone notices and rotates it. Attackers scan public repositories for exactly these strings within seconds of a push. Temporary credentials from STS cut the blast radius to the token's lifetime, usually one hour.
public class WhyNoEmbeddedKeys {
// WRONG: permanent secret in code – breach waiting to happen
// If this file is committed to Git, the key is exposed forever
static final String ACCESS_KEY = "AKIAIOSFODNN7EXAMPLE";
static final String SECRET_KEY = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY";
// CORRECT: no secrets in code at all
// On EC2: instance profile provides temporary credentials
// On Lambda: execution role provides temporary credentials
// Locally: aws sso login or named profile
public static void main(String[] args) {
S3Client s3 = S3Client.create(); // SDK finds creds automatically
}
}
What is federation and how does it use Roles?
Federation means users log in through an external identity provider such as Active Directory, Okta, or Google Workspace, and then assume an IAM Role. They never get a permanent IAM User at all. AWS IAM Identity Center (formerly SSO) manages this flow, mapping identity-provider groups to IAM Roles so that when Rajesh logs in through the bank's Active Directory, he automatically assumes the Developer role with the right permissions.
Rajesh → Bank Active Directory → IAM Identity Center → Assume DeveloperRole
(authentication) (federation) (temporary credentials)
What are the rules and constraints?
A Role's trust policy determines who can assume it. The trust policy names the trusted principal, which can be an AWS service (EC2, Lambda), an AWS account, a federated identity provider, or a specific IAM User or Role.
Session duration is configurable between 1 and 12 hours, but role chaining (when one Role assumes another Role) caps the session at 1 hour regardless of the configured maximum.
A Role can have up to 10 managed policies attached and one inline policy. The total size of all policies on a Role cannot exceed 10,240 characters.
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Action": "sts:AssumeRole"
}]
}
What are common mistakes?
The first mistake is embedding access keys in source code or environment variables committed to Git. The fix is to use Roles for all machine access.
The second mistake is giving a Role broader permissions than the workload needs. Even though the credentials are temporary, an overly permissive Role still lets a compromised service do too much during the token lifetime.
The third mistake is not using IMDSv2 on EC2. IMDSv1 allows any process on the instance to read credentials with a simple HTTP GET. IMDSv2 requires a session token, which blocks common SSRF attacks.
public class RoleMistakes {
// WRONG: Role with admin access
// { "Effect": "Allow", "Action": "*", "Resource": "*" }
// Even temporary credentials with full admin are dangerous
// CORRECT: Role scoped to exactly what the service needs
// { "Effect": "Allow", "Action": ["s3:GetObject"],
// "Resource": "arn:aws:s3:::nbi-customer-statements/*" }
}
What should a senior engineer know?
A senior engineer enforces IMDSv2 on all instances, uses permission boundaries to cap what any Role in a development account can do, and monitors with IAM Access Analyzer for unused permissions. They know that role chaining caps sessions at one hour, that aws sso login is preferred over long-lived keys even for local development, and that credential reports and Access Analyzer findings should be reviewed regularly.
They also know that STS endpoints are regional and recommend using the regional STS endpoint for lower latency and higher availability rather than the global endpoint.
# Enforce IMDSv2 on launch – blocks SSRF credential theft
aws ec2 run-instances \
--metadata-options HttpTokens=required,HttpEndpoint=enabled \
--image-id ami-0abcd1234 \
--instance-type m6i.large
Visual Overview
┌────────────────── Credential Flow Comparison ─────────────────┐ │ │ │ IAM User (permanent keys) │ │ ┌──────────────┐ │ │ │ Access Key ID │──→ Embedded in code ──→ Git commit │ │ │ Secret Key │ Docker layer Log file │ │ └──────────────┘ NEVER EXPIRES ──→ Breach until rotated │ │ │ │ IAM Role (temporary credentials) │ │ ┌──────────────┐ ┌─────────┐ ┌────────────────┐ │ │ │ EC2 Instance │──→│ Instance │──→│ STS issues │ │ │ │ (Settlement) │ │ Profile │ │ temp creds │ │ │ └──────────────┘ └─────────┘ │ (1 hour TTL) │ │ │ └───────┬────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────────────────────────────────────┐ │ │ │ SDK auto-fetches from 169.254.169.254 │ │ │ │ SDK auto-refreshes before expiry │ │ │ │ No secrets in code, config, or Git │ │ │ │ Leaked token is useless after 1 hour │ │ │ └──────────────────────────────────────────────────┘ │ │ │ │ Cross-Account Access │ │ ┌───────────┐ assumeRole ┌────────────┐ │ │ │ Analytics │──────────────→│ Production │ │ │ │ Account │ (STS) │ Account │ │ │ │ (222222) │←──temp creds──│ Role in │ │ │ └───────────┘ │ (111111) │ │ │ └────────────┘ │ └───────────────────────────────────────────────────────────────┘
Key Takeaway
I reach for an IAM Role instead of an IAM User whenever the identity is a machine, needs cross-account access, or should only have temporary access. The three classic cases are compute resources like EC2 and Lambda that call AWS services, cross-account access where a principal in one account assumes a Role in another, and federated users who log in through an identity provider. In all of these, STS issues temporary credentials when the Role is assumed, and those credentials expire, usually after an hour.
The reason this matters, especially in banking, comes down to blast radius. An IAM User access key is a permanent two-part secret that never expires on its own. The moment it lands in a Git commit, a Docker image layer, a log line, or a stack trace, it is compromised forever until a human notices and rotates it, and attackers scan public repositories for these strings within seconds. Temporary credentials from a Role cap the damage to the token's short lifetime, so even a leaked token is useless within the hour.
At National Bank of India our settlement service runs on EC2 with an instance profile that wraps a Role. The AWS SDK's default credential provider automatically fetches the temporary credentials from the instance metadata, uses them, and refreshes them before they expire, so we write no credential-handling code at all and there is simply no secret to leak. For reporting, a separate analytics account assumes a production Role whose trust policy names the analytics account, so analytics reads production settlement data with short-lived scoped credentials instead of us copying production keys across accounts.
So the rule I state plainly in an interview is that application code must never embed long-lived access keys, because a secret that never expires is a breach waiting to happen. For services we use Roles through instance profiles or execution roles, for cross-account we use trust policies and assume-role, and even for local development we use SSO or named profiles rather than keys in the code.
Remember one sentence: Use Roles for machines and cross-account access because temporary credentials that expire in an hour are infinitely safer than permanent keys that never expire.
---