3.1 IAM Roles: Temporary Credentials via STS AssumeRole
Right, let’s talk about the single most important security feature in AWS: temporary credentials. You’re about to learn why hardcoding an IAM user’s access key into a .env file is the cloud equivalent of taping your house key to the front door with a note that says, “PLEASE STEAL MY BIKE.” We’re moving past that. We’re using IAM Roles and the Security Token Service (STS), and we’re doing it properly.
The core idea is beautifully simple: instead of giving something (an EC2 instance, a Lambda function, you) long-term credentials that last forever and are a massive liability, you give it the permission to ask for short-term, temporary credentials. These credentials expire, usually in an hour, which drastically limits the blast radius if they’re leaked. This whole “asking for” process is done via the AssumeRole API action.
Think of it like a valet key for your car. You don’t hand the valet your entire keychain with your house key, mailbox key, and that weird key you don’t remember the purpose of. You hand them a single, restricted key that only starts the car and expires after a set time. STS is your automated valet key dispenser.
The Core Mechanics of AssumeRole
Here’s the dance. Three parties are involved:
- The Trusting Entity: The IAM Role itself. It defines who can assume it and what permissions they get once they do.
- The Trusted Entity: The thing that wants to assume the role (e.g., an IAM user, an EC2 instance, a Lambda function).
- STS: The service that performs the swap, validating the request and issuing the temporary credentials.
The role has two critical policies attached to it. The trust policy (the “who can ask”) is JSON that lives in the “Trust relationships” tab of the role. It specifies which AWS principals (users, other roles, services) are allowed to call AssumeRole for this role. The permissions policy (the “what they can do”) is the standard IAM policy that defines the actual permissions—like s3:GetObject or ec2:StartInstances—that the temporary credentials will grant.
Here’s a realistic trust policy for a role meant to be assumed by an EC2 instance (via an Instance Profile, but we’ll get to that).
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
And here’s a simple permissions policy for that same role.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-secure-bucket/*"
}
]
}
Actually Assuming a Role in Code
Enough theory. Let’s get our hands dirty. You’ll need the AWS SDK for this. Here’s how you assume a role from, say, a script running on your laptop (where you have AWS credentials configured in ~/.aws/credentials).
import boto3
from botocore.exc import ClientError
def assume_role(role_arn, session_name):
"""
Assumes a role and returns the temporary credentials.
"""
# Create an STS client using your current credentials
sts_client = boto3.client('sts')
try:
# The big moment: the AssumeRole call
assumed_role = sts_client.assume_role(
RoleArn=role_arn,
RoleSessionName=session_name, # This identifies this specific session for auditing
DurationSeconds=3600 # You can request up to 1 hour (or more with other settings)
)
# Extract the temporary credentials from the response
credentials = assumed_role['Credentials']
# Now, create a *new* client for a service (like S3) using the temporary creds
s3_client = boto3.client(
's3',
aws_access_key_id=credentials['AccessKeyId'],
aws_secret_access_key=credentials['SecretAccessKey'],
aws_session_token=credentials['SessionToken'] # This is critical and often forgotten!
)
# Use the new client. This call will only work if the role has s3:ListBuckets permission.
response = s3_client.list_buckets()
print("Buckets:", response['Buckets'])
return credentials
except ClientError as e:
print(f"Failed to assume role: {e}")
return None
# Call the function
temp_creds = assume_role('arn:aws:iam::123456789012:role/my-cool-role', 'my-script-session')
The most common pitfall here, bar none, is forgetting the aws_session_token. Temporary credentials are a trio: AccessKeyId, SecretAccessKey, and SessionToken. If you miss the token, your API calls will be rejected with a cryptic “InvalidToken” or “AccessDenied” error. I’ve lost hours of my life to this. Don’t be me.
The Critical Role of Role Chaining
Here’s a fun twist. You can assume a role using another set of temporary credentials. This is called role chaining. Why would you do this? Maybe you have a central security account with a role that has permission to assume different roles in other application accounts. It’s a cornerstone of multi-account strategies.
The crucial thing to know about chaining is this: the maximum session duration you can request is capped by the original role’s maximum session duration setting. You can’t just keep chaining roles to get a 12-hour session from a role that only allows 1 hour. AWS is smarter than that.
Where This Happens Automagically
You won’t always be writing this code yourself. AWS does it for you in key places:
- EC2 Instance Profiles: This is just a fancy name for a wrapper around an IAM Role that lets you attach it to an EC2 instance. The magic
169.254.169.254metadata service on the instance provides these temporary credentials automatically, and the SDKs know to look for them. This is why you should never, ever run an EC2 instance without one. - Lambda: You assign an execution role to your Lambda function. The service assumes that role on your function’s behalf before invoking it.
- ECS Tasks: Similar deal. You assign a task role, and the containers within the task get those temporary credentials.
The beauty of this system is its consistency. Whether it’s your laptop, a server, or a serverless function, the pattern is the same: define a role, define who can assume it, then let the thing assume it to get short-lived, scoped permissions. It’s the bedrock of everything secure in AWS. Now go use it. And for the love of all that is holy, remember the session token.