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.
What you'll learn
- ✓The difference between identity and permissions
- ✓When to use a role vs a user
- ✓Trust policies vs permission policies
- ✓How AssumeRole and instance profiles work
- ✓Patterns for cross-account access
Prerequisites
- •Basic familiarity with AWS console or CLI
What and Why
IAM has four core concepts: users, roles, policies, and trust relationships. They are simple individually but easy to confuse. The most common mistake — long-lived access keys handed to applications — is also the most expensive when it leaks. Roles fix that by issuing short-lived credentials on demand.
This is the cheatsheet I wish I had on day one.
Mental Model
- A policy is a JSON document that grants or denies actions on resources. Policies are attached to identities.
- A user is a long-lived identity with credentials (password or access keys).
- A role is an identity that has no permanent credentials. Something assumes the role and gets temporary credentials for it.
- A trust policy says who is allowed to assume a role.
- A permission policy says what the assuming principal can do once they have assumed it.
[EC2 instance / Lambda / User]
|
sts:AssumeRole (allowed by trust policy)
|
v
[IAM Role]
|
uses permission policies attached to role
|
v
[API calls]
|
v
[AWS resources] Hands-on Example
A permission policy that allows read on one S3 bucket:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:ListBucket"],
"Resource": [
"arn:aws:s3:::reports-prod",
"arn:aws:s3:::reports-prod/*"
]
}]
}
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"
}]
}
Create the role and attach the policy:
aws iam create-role --role-name reports-reader \
--assume-role-policy-document file://trust.json
aws iam put-role-policy --role-name reports-reader \
--policy-name s3-read --policy-document file://perm.json
Attach via an instance profile so the EC2 instance picks it up:
aws iam create-instance-profile --instance-profile-name reports-reader
aws iam add-role-to-instance-profile \
--instance-profile-name reports-reader \
--role-name reports-reader
The instance now gets temporary credentials from the metadata service. No access keys to rotate, no secrets to leak.
For a cross-account role, the trust policy lists another account ID:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::111122223333:root" },
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": { "sts:ExternalId": "shared-secret-value" }
}
}]
}
The external ID prevents the confused-deputy problem when third parties assume roles into your account.
Common Pitfalls
Long-lived access keys in code. Never. Use roles via instance profile, IRSA for EKS, or OIDC federation from GitHub Actions.
Wildcard everything. "Action": "*", "Resource": "*" is admin. Audit for wildcards in production policies regularly.
Confusing identity-based and resource-based policies. S3 buckets, KMS keys, SNS topics, and a few others have their own resource policies. Both must allow the action; either denying wins.
Forgetting explicit denies. A Deny always overrides an Allow. Useful for guardrails (no one, not even admins, may delete production CloudTrail), but devastating when a typo hits a wide pattern.
Inline vs managed policies. Inline policies are tied to one identity and hard to audit. Use customer-managed policies you can attach to many roles.
Practical Tips
For workloads on EKS, use IRSA (IAM Roles for Service Accounts). Pods assume roles via OIDC federation, with per-pod scoping. No more node-wide instance profile that every pod inherits.
For CI/CD, configure OIDC federation from GitHub Actions, GitLab, or your CI of choice:
permissions:
id-token: write
contents: read
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-deploy
aws-region: us-east-1
No long-lived secrets in the runner.
Use IAM Access Analyzer to find unused permissions and overbroad trust policies. It generates least-privilege policy suggestions from real CloudTrail usage.
Tag roles by team and purpose. When a permission seems suspicious, tags shorten the “whose is this?” investigation from hours to seconds.
Memorize the policy evaluation order: explicit deny > organization SCP > resource policy > identity policy > permissions boundary > session policy. When access is unexpectedly blocked, walk it from the top.
Wrap-up
Users are identities, roles are assumable identities, policies are what each one can do, and trust policies decide who can assume what. Prefer roles over users for everything programmatic, federate CI through OIDC, and avoid wildcards in production. Master sts:AssumeRole and the policy evaluation order, and IAM stops feeling like a maze and starts feeling like the security backbone it is designed to be.
Related articles
- 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.
- 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.
- AWS AWS IAM Basics: Users, Roles, and Policies
Learn AWS IAM fundamentals: root vs IAM users, JSON policies, roles for services, least privilege, and common pitfalls that lead to security incidents.
- Kubernetes Kubernetes RBAC Cheatsheet: Roles, Bindings, and Service Accounts
A concise tour of Kubernetes RBAC: roles vs cluster roles, bindings, service accounts, and patterns that scale without becoming a permissions zoo.