AWS IAM Roles and Policies: A Practical Tutorial
Learn how AWS IAM roles, trust policies, and permissions policies work together. Build a small role from scratch and assume it from another account.
What you'll learn
- ✓The difference between users, roles, and policies
- ✓How a trust policy controls who can assume a role
- ✓How a permissions policy controls what the role can do
- ✓How to assume a role with the AWS CLI
- ✓How to avoid the most common IAM mistakes
Prerequisites
- •Comfortable with the Linux command line
IAM is the part of AWS that decides who can do what. Get it right and your infrastructure has clean, auditable boundaries. Get it wrong and you either lock yourself out of your own account or hand the keys to anyone with a stolen access key. The most important concept to internalize is the role — and the two policies attached to it.
Users versus roles
An IAM user is a long-lived identity with a password or access keys. A role is an identity that nobody owns directly; it is assumed temporarily by a user, a service, or another account. When the role is assumed, AWS issues short-lived credentials that expire in an hour or so by default.
Roles are the modern default. Almost every recommendation now boils down to “stop creating IAM users; use roles.” Users still exist for break-glass admin access and legacy integrations, but for applications, CI/CD, and cross-account access, roles win.
The two policies on every role
A role has two policy documents attached to it.
- A trust policy answers “who is allowed to assume this role?”
- One or more permissions policies answer “once assumed, what can the role do?”
Both are JSON, both use the same Statement syntax, and confusing them is the single most common IAM mistake.
A minimal trust policy
Here is a trust policy that lets an EC2 instance assume the role.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "Service": "ec2.amazonaws.com" },
"Action": "sts:AssumeRole"
}
]
}
Principal is the identity being authorized. sts:AssumeRole is the action being granted. For a role assumed by another account, the principal would be that account’s ARN. For a role assumed by GitHub Actions over OIDC, the principal is the GitHub OIDC provider.
A minimal permissions policy
Now the permissions policy — what the role can do once assumed.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::my-app-bucket/*"
}
]
}
This grants read and write on objects in one bucket, nothing else. Scope your permissions tightly — the wildcard * on actions or resources is rarely justified outside of an admin role.
Creating the role with the CLI
You can do all of this in the console, but the CLI is faster once you know the shape.
aws iam create-role \
--role-name app-s3-writer \
--assume-role-policy-document file://trust.json
aws iam put-role-policy \
--role-name app-s3-writer \
--policy-name s3-bucket-rw \
--policy-document file://permissions.json
Two files, two commands, one role. To attach it to an EC2 instance you wrap it in an instance profile and pass the profile name when launching the instance.
Assuming a role from the CLI
To assume a role manually, call sts assume-role and export the temporary credentials.
creds=$(aws sts assume-role \
--role-arn arn:aws:iam::123456789012:role/app-s3-writer \
--role-session-name local-test)
export AWS_ACCESS_KEY_ID=$(echo "$creds" | jq -r .Credentials.AccessKeyId)
export AWS_SECRET_ACCESS_KEY=$(echo "$creds" | jq -r .Credentials.SecretAccessKey)
export AWS_SESSION_TOKEN=$(echo "$creds" | jq -r .Credentials.SessionToken)
aws s3 ls s3://my-app-bucket
In practice you rarely script this by hand. The AWS CLI supports named profiles with role_arn and source_profile in ~/.aws/config, and most SDKs assume roles automatically when configured with the right environment variables.
Cross-account roles
A frequent pattern: a CI account assumes a role in a production account to deploy. The trust policy in the prod account names the CI account as principal, and often adds an ExternalId condition to defeat the confused deputy problem.
{
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::111111111111:root" },
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": { "sts:ExternalId": "shared-secret-string" }
}
}
The CI tool passes that ExternalId when assuming the role. Without it, the request fails.
Common mistakes
A few patterns will save you real pain:
- No wildcards on production roles.
"Action": "*"and"Resource": "*"belong in throwaway sandbox roles, not in anything customer-facing. - Do not embed AWS access keys in code or images. Use instance profiles, ECS task roles, or OIDC. Static keys are the leading cause of cloud breaches.
- Watch trust policy drift. When a role’s trust policy grows new principals over time, audit it. Old contractor accounts should not still be in there.
- Use IAM Access Analyzer. It will tell you which roles have never been used and which permissions are unused, which is half the work of tightening policies.
A useful mental model
Treat each role like a job description. Decide who is hired into the job (trust policy) and what tasks the job is allowed to perform (permissions policy). If a new task comes along, write a new role rather than expanding an existing one. That single discipline keeps an account audit-ready as it grows.
Related articles
- DevOps AWS S3 Bucket Policies Explained
How S3 bucket policies, IAM policies, and ACLs interact, how to write least-privilege bucket policies, and patterns for cross-account access without footguns.
- DevOps Terraform State Management Basics
Why Terraform state exists, how it maps configuration to real infrastructure, and how to set up remote backends, locking, and workspaces without losing your mind.
- AWS AWS IAM Policies vs Roles Cheatsheet
A quick-reference guide to the difference between IAM users, roles, policies, and trust relationships, with examples you can paste into your AWS account today.
- AWS AWS Secrets Manager Tutorial: Storing and Rotating Secrets
A practical guide to AWS Secrets Manager: creating secrets, retrieving them from apps, automatic rotation, IAM access control, and choosing it over SSM Parameter Store.