AWS for DevOps — IAM Roles
2.2 — IAM Roles
An IAM role is an identity that services, applications, or users can assume to get temporary credentials. Unlike an IAM user, a role does not have permanent passwords or access keys.
Why Roles Matter for DevOps
In a DevOps workflow, services talk to other services constantly:
- An EC2 instance needs to pull images from ECR
- A Lambda function needs to read from DynamoDB
- CodeBuild needs to push artefacts to S3
- ECS tasks need to write logs to CloudWatch
You should never hard-code access keys inside your code or containers. Instead, you assign a role, and AWS provides temporary credentials that rotate automatically.
┌─────────────┐ assumes ┌──────────────────┐
│ EC2 Instance│ ──────────────► │ EC2-S3-ReadRole │
└─────────────┘ │ │
│ Policy: │
│ s3:GetObject on │
│ my-deploy-bucket │
└──────────────────┘
│
Temporary credentials
refreshed automatically
Anatomy of a Role
Every role has two components:
| Component | Purpose | Example |
|---|---|---|
| Trust policy | Defines who can assume the role | "Principal": {"Service": "ec2.amazonaws.com"} |
| Permission policy | Defines what the role can do | S3 read, CloudWatch write, etc. |
Trust Policy Example
A trust policy is a JSON document that says “I trust this entity to assume me”:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
This says: only the EC2 service can assume this role.
Creating a Role (CLI)
# Step 1 — Create the role with a trust policy
$ aws iam create-role \
--role-name EC2-S3-ReadRole \
--assume-role-policy-document file://trust-policy.json
{
"Role": {
"RoleName": "EC2-S3-ReadRole",
"Arn": "arn:aws:iam::123456789012:role/EC2-S3-ReadRole"
}
}
# Step 2 — Attach a permission policy
$ aws iam attach-role-policy \
--role-name EC2-S3-ReadRole \
--policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
# Step 3 — Create an instance profile (required for EC2)
$ aws iam create-instance-profile \
--instance-profile-name EC2-S3-ReadProfile
# Step 4 — Add the role to the instance profile
$ aws iam add-role-to-instance-profile \
--instance-profile-name EC2-S3-ReadProfile \
--role-name EC2-S3-ReadRole
Attaching a Role to an EC2 Instance
# At launch
$ aws ec2 run-instances \
--image-id ami-0abcdef1234567890 \
--instance-type t3.micro \
--iam-instance-profile Name=EC2-S3-ReadProfile
# To an existing instance
$ aws ec2 associate-iam-instance-profile \
--instance-id i-0abc123def456 \
--iam-instance-profile Name=EC2-S3-ReadProfile
Once the role is attached, any code running on the instance can access S3 without any access keys:
# This works automatically — no credentials configured
$ aws s3 ls s3://my-deploy-bucket/
2026-03-30 10:00:00 1234567 app-v1.2.3.tar.gz
Common Role Types for DevOps
| Service | Role Trust Principal | Typical Permissions |
|---|---|---|
| EC2 | ec2.amazonaws.com |
S3 read, CloudWatch logs |
| Lambda | lambda.amazonaws.com |
DynamoDB, S3, SQS |
| ECS Task | ecs-tasks.amazonaws.com |
ECR pull, CloudWatch logs |
| CodeBuild | codebuild.amazonaws.com |
S3, ECR, CloudWatch |
| CodePipeline | codepipeline.amazonaws.com |
CodeBuild, S3, CodeDeploy |
| CloudFormation | cloudformation.amazonaws.com |
Depends on resources it creates |
Assuming a Role (Cross-Account)
DevOps teams often manage multiple AWS accounts. A role in Account B can trust a user in Account A:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111111111111:root"
},
"Action": "sts:AssumeRole"
}
]
}
Then the user assumes it:
$ aws sts assume-role \
--role-arn arn:aws:iam::222222222222:role/CrossAccountDeploy \
--role-session-name deploy-session
{
"Credentials": {
"AccessKeyId": "ASIA...",
"SecretAccessKey": "...",
"SessionToken": "...",
"Expiration": "2026-03-30T06:00:00Z"
}
}
The credentials are temporary (default 1 hour, max 12 hours).
Roles vs Users — When to Use Which
| Scenario | Use |
|---|---|
| Human logs into Console | IAM user (in a group) |
| Application on EC2 | IAM role |
| Lambda function | IAM role (auto-assigned) |
| CI/CD pipeline | IAM role |
| Cross-account access | IAM role (assumed via STS) |
| External identity (SAML/OIDC) | IAM role (federated) |
Common Mistakes
| Mistake | Why It Hurts | Fix |
|---|---|---|
| Hard-coding access keys in code | Keys get committed to Git | Use roles instead |
Giving a role AdministratorAccess |
Blast radius is the entire account | Use least-privilege policies |
| Forgetting instance profiles for EC2 | Role exists but EC2 can’t use it | Always create the instance profile |
| Not using session names | Can’t trace who assumed the role | Always set --role-session-name |
Quick Reference
| Task | Command |
|---|---|
| Create role | aws iam create-role --role-name NAME --assume-role-policy-document file://trust.json |
| Attach policy to role | aws iam attach-role-policy --role-name NAME --policy-arn ARN |
| Create instance profile | aws iam create-instance-profile --instance-profile-name NAME |
| Add role to profile | aws iam add-role-to-instance-profile --instance-profile-name NAME --role-name NAME |
| Assume a role | aws sts assume-role --role-arn ARN --role-session-name SESSION |
| List roles | aws iam list-roles --query 'Roles[*].RoleName' |
Next → IAM Policies