AWS for DevOps — IAM Roles

3 minute read

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

Updated: