A practical guide to identity, policies, roles, federation, MFA, and access security in AWS
Purpose: Use this document as a clear reference when designing permissions, creating roles, and debugging access in AWS.
1. What is IAM?
AWS Identity and Access Management (IAM) controls who can call AWS and what they are allowed to do. It is global (not per-region) and free to use.
Mental model: IAM answers three questions for every API call: 1) Who is asking? (user, role, or AWS service) 2) What do they want to do? (action, e.g. s3:GetObject) 3) On which resource? (e.g. arn:aws:s3:::my-bucket/*). If an explicit Deny matches anywhere, the request is denied. Otherwise an explicit Allow is needed. No allow = deny by default.
Term
Meaning
Principal
An identity that can make requests: IAM user, IAM role, or an AWS service.
Permission
One API action, such as ec2:StartInstances.
Policy
A JSON document that lists permissions (Allow/Deny on actions/resources).
ARN
Amazon Resource Name — unique address of a resource, e.g. arn:aws:s3:::my-bucket.
Root account
The account owner email; full access, should rarely be used.
2. How access works (the big picture)
Principal (user / role / service)
|
v
Policies attached to the principal ----+
| |
v v
Service control policies (org) Resource-based policies
| |
+---------------+---------------+
v
Evaluation: Deny wins,
else Allow needed
|
v
AWS API call succeeds / fails
Style
Where the policy lives
Example
Identity-based
Attached to a user, group, or role
"This role may read from bucket X."
Resource-based
Attached to the resource itself
"Bucket X allows this account/role to write."
Most permissions are identity-based. Resource policies are common for S3 bucket policies, Lambda function policies, KMS key policies, and cross-account access.
3. IAM entities: users, groups, roles
Entity
What it is
When to use
IAM user
A long-lived identity with a password (console) and/or access keys (CLI/API).
Humans that need steady access; legacy cases. Prefer roles + SSO now.
IAM group
A collection of users that share the same policies. Groups cannot be principals in policies.
Applying the same permissions to many users (e.g. Developers, Admins).
IAM role
A set of permissions that anyone/something can temporarily assume. Has a trust policy (who may assume) + permissions policy (what they can do).
EC2/Lambda service access, cross-account access, SSO/federation, switching contexts.
Instance profile
A wrapper that delivers an IAM role's credentials to an EC2 instance.
Attaching a role to EC2 (created automatically when you attach a role in the console).
Root user vs IAM user
Root user
IAM user / role
Identity
Account root email + password
Named identity inside the account
Permissions
Everything (no restrictions)
Only what policies allow
Recommended use
Few account-level tasks only
All daily work
MFA
Strongly required
Required by best practice
Golden rule: Do not use the root account for everyday work. Do not create long-lived IAM users with access keys when a role (via IAM Identity Center) will do.
Access methods compared
Method
Credential
Typical use
Console
Username + password (+ MFA)
Humans in the browser
AWS CLI / SDK
Access key ID + secret access key
Scripts, CI/CD, local dev (prefer roles)
Assumed role
Temporary STS credentials
EC2, Lambda, cross-account, SSO
IAM Identity Center
SSO portal / AWS CLI SSO
Humans, org-wide access
4. Policy types
Policy type
Attached to
Purpose
AWS managed policy
Users, groups, roles
Ready-made policies maintained by AWS (e.g. AmazonS3ReadOnlyAccess). Convenient but often broad.
Customer managed policy
Users, groups, roles
Your own reusable policy; versioned, editable — preferred for production.
Inline policy
One specific user/group/role
Embedded in the entity; deleted with the entity. Use sparingly.
Permissions boundary
IAM entity
A maximum permission limit. An entity can never exceed the boundary.
Service control policy (SCP)
OU / account (AWS Organizations)
Org-wide guardrail. Does not grant permissions by itself — it filters what member accounts can ever do.
Session policy
Passed at assume-role time
Extra limit applied to the session; advanced role-assumption flows.
Note: SCPs limit, they do not grant. An identity still needs an Allow in its own policy; the SCP just removes what is outside the org guardrail.
Policy language version; use "2012-10-17" for all modern policies.
Statement
Array of permission rules.
Sid
Optional label to identify a statement.
Effect
Allow or Deny.
Action
API actions, e.g. s3:GetObject, or wildcards s3:*.
Resource
Target ARNs. Use * for "all resources" carefully.
Condition
Extra tests: region, MFA, source IP, tags, time, etc.
Principal
Only in resource-based policies — who may use the resource.
Action wildcards
s3:* all S3 actions (too broad for production)
s3:Get* all actions starting with Get
s3:GetObject one precise action (prefer this)
Conditions you will actually use
Condition key
Example
Effect
aws:MultiFactorAuthPresent
"Bool": {"aws:MultiFactorAuthPresent": "true"}
Require MFA for the action.
aws:SourceIp
"IpAddress": {"aws:SourceIp": "203.0.113.0/24"}
Restrict by caller IP.
aws:RequestedRegion
"StringEquals": ...
Restrict to specific regions.
aws:PrincipalTag/Team
Tag-based access control
Allow only if the caller's role has a matching tag.
6. Policy evaluation logic
1. Start: request is denied by default (implicit deny).
2. Any explicit Deny anywhere (identity, resource, SCP, boundary)
matching the request? ---- YES ----> DENIED (final).
3. Any explicit Allow matching? ------- NO ----> still denied (implicit deny).
4. Also check permissions boundaries and SCPs:
the action must be allowed by identity policy
AND not blocked by boundary/SCP.
5. ALLOW.
Situation
Result
Identity Allow + no Deny
Allowed
Identity Allow + explicit Deny on the same action
Denied (Deny wins)
No Allow anywhere
Denied (implicit deny)
Allow, but action outside permissions boundary
Denied
Allow, but SCP blocks the action in the org
Denied
Resource policy grants Allow to this principal
Allowed (can be enough even without identity Allow)
Tip: "Deny if not MFA" is implemented as an explicit Deny statement with a condition — explicit Deny beats any Allow.
aws iam list-access-keys --user-name my-user
aws iam get-access-key-last-used --access-key-id AKIA...
10. Tools: CLI, simulator, Access Analyzer
Useful IAM CLI commands
# Who am I?
aws sts get-caller-identity
# List users, groups, roles, policies
aws iam list-users
aws iam list-groups-for-user --user-name alice
aws iam list-attached-role-policies --role-name app-role
aws iam list-policies --scope Local
# Inline policies
aws iam list-role-policies --role-name app-role
aws iam get-role-policy --role-name app-role --policy-name my-inline
# Attach / detach managed policy
aws iam attach-role-policy \
--role-name app-role \
--policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
aws iam detach-role-policy \
--role-name app-role \
--policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
# Assume a role and use it
aws sts assume-role \
--role-arn arn:aws:iam::123456789012:role/DataReader \
--role-session-name my-session
IAM policy simulator
Web tool: IAM → Policy simulator (or aws iam simulate-principal-policy). Answers "would this identity be allowed to do X on resource Y?" without executing the action. Excellent for debugging before granting more permissions.
Can generate policies from CloudTrail activity (only actions the workload actually used).
Validates policies against IAM best practices and AWS service limits.
Enable it in every region you use. Treat findings as a to-do list: every external share should be intentional.
CloudTrail for auditing
Every IAM API call (CreateUser, AttachRolePolicy, AssumeRole, …) appears in CloudTrail. Alert on:
Root account usage
IAM policy/role/user changes
Failed AccessDenied spikes (possible probing)
New access keys created
11. Troubleshooting AccessDenied
Symptom
Likely cause
Fix
AccessDenied on S3 GetObject
Missing object permission or wrong resource ARN (/* needed)
Check both bucket and object statements.
AccessDenied on ListBucket
Statement targets bucket/* instead of bucket
Split List vs Get resources.
Role cannot be assumed
Trust policy does not include caller, or caller lacks sts:AssumeRole
Fix trust policy + identity policy.
Works in console, fails in CLI
CLI uses different identity (keys vs SSO session)
aws sts get-caller-identity first.
Region errors
Policy has Condition on region
Align aws:RequestedRegion.
EC2 app denied
Instance has no instance profile / wrong role
Attach profile; wait for credentials.
Works alone, fails with boundary/SCP
Action outside boundary or blocked by org SCP
Check boundary and SCPs.
"Still denied" after adding Allow
Explicit Deny exists elsewhere
Search all policies for Deny on that action.
Debugging order
aws sts get-caller-identity # 1. Who am I?
aws iam simulate-principal-policy \ # 2. Would this identity pass?
--policy-source-arn arn:aws:iam::123456789012:role/app-role \
--action-names s3:GetObject \
--resource-arns arn:aws:s3:::example-bucket/*
Then read the error code carefully: AccessDenied, AccessDeniedException, UnauthorizedOperation — each points at a different layer (identity policy vs resource policy vs service auth).
12. IAM best practices checklist
Never use the root account for daily work; secure it with hardware MFA and no access keys.
Enable MFA for root and every IAM user (prefer hardware/FIDO2 for root).
Prefer IAM Identity Center + roles over long-lived IAM users with access keys.
Grant least privilege: precise actions, precise resources; avoid *:* and Resource: *.
Use customer managed policies (versioned, reusable) over inline and broad AWS managed policies.
Use permissions boundaries for roles that developers create.
Use SCPs in AWS Organizations as org guardrails.
EC2/Lambda/ECS always use roles, never embedded keys.
Require IMDSv2 on all EC2 instances.
Store secrets in Secrets Manager or Parameter Store (SecureString), not in code or user data.
Rotate access keys automatically; delete unused keys and stale roles.
Enable IAM Access Analyzer in all regions; act on findings.
Enable CloudTrail and alarm on IAM changes and root usage.
Review permissions quarterly; remove unused users, roles, and policies.
Tag IAM entities (Team, Environment) for ownership and automation.
Remember: IAM is allow-by-default-deny. Start from zero permissions, add the minimum Allow needed, test with the Policy Simulator, and use explicit Deny + conditions for guardrails. Roles over users; temporary credentials over permanent keys.