Amazon IAM — Practical Guide¶
A GitHub-friendly guide to AWS Identity and Access Management: who you are, what you can do, and how AWS decides.
Contents¶
- 1. What is IAM?
- 2. How access works (the big picture)
- 3. IAM entities: users, groups, roles
- 4. Policy types
- 5. Anatomy of a policy
- 6. Policy evaluation logic
- 7. IAM roles in depth
- 8. Federation and IAM Identity Center
- 9. MFA and access security
- 10. Tools: CLI, simulator, Access Analyzer
- 11. Troubleshooting AccessDenied
- 12. IAM best practices checklist
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? (principal: 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
Two ways to grant access:
| 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 (harder to audit/reuse). |
| Permissions boundary | IAM entity | A maximum permission limit. An entity can never exceed the boundary, even with broader policies. Great for limiting roles created by developers. |
| 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; used by advanced role-assumption flows. |
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.
Effective permissions =
Identity policy ∩ Permissions boundary ∩ SCP ∩ (resource policy allow)
...unless an explicit Deny applies anywhere → Deny.
5. Anatomy of a policy¶
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowReadFromOneBucket",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::example-bucket",
"arn:aws:s3:::example-bucket/*"
],
"Condition": {
"StringEquals": {
"aws:RequestedRegion": "us-east-1"
}
}
}
]
}
| Field | Meaning |
|---|---|
Version |
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 (account, role ARN, *). |
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¶
AWS evaluates every request. Memorize this order:
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, for same-account/resource cases) |
Interview/real-world tip: "Deny if not MFA" is implemented as an explicit Deny statement with a condition — explicit Deny beats any Allow.
Example — force MFA for dangerous actions:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyUnlessMFA",
"Effect": "Deny",
"Action": [
"iam:Delete*",
"iam:Create*",
"iam:Attach*",
"iam:Detach*"
],
"Resource": "*",
"Condition": {
"BoolIfExists": {
"aws:MultiFactorAuthPresent": "false"
}
}
}
]
}
7. IAM roles in depth¶
A role is the most important IAM concept in practice. It has two policies:
| Piece | Question it answers |
|---|---|
| Trust policy (principal) | Who may assume this role? (account, user, service, external account) |
| Permissions policy | What can they do once assumed? |
Caller ---- assume-role (STS) ----> Role
|
+--> Trust policy: is caller allowed in?
+--> Permissions: what actions result?
|
v
Temporary credentials
(AccessKey, SecretKey, SessionToken)
expire (15 min – 12 hours)
Common role use cases¶
| Use case | Trust policy principal | Notes |
|---|---|---|
| EC2 instance role | ec2.amazonaws.com |
Delivered via instance profile; safest way for apps on EC2. |
| Lambda execution role | lambda.amazonaws.com |
Permissions for logs, S3, DynamoDB, etc. |
| ECS task role | ecs-tasks.amazonaws.com |
Per-task permissions, distinct from the execution role. |
| Cross-account access | The other account/role ARN | Requires a resource-based policy allowing the assume + matching identity policy. |
| Human access (SSO) | IAM Identity Center permission sets | Preferred over long-lived IAM users. |
| Automation / CI | GitHub OIDC, Jenkins, etc. | Federated assume-role; no static keys stored. |
Example: EC2 instance role trust policy¶
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "Service": "ec2.amazonaws.com" },
"Action": "sts:AssumeRole"
}
]
}
Example: minimal S3-read permissions policy¶
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:GetObject"],
"Resource": "arn:aws:s3:::example-bucket/*"
},
{
"Effect": "Allow",
"Action": ["s3:ListBucket"],
"Resource": "arn:aws:s3:::example-bucket"
}
]
}
Note:
ListBuckettargets the bucket ARN (no/*);GetObjecttargets object ARNs (with/*). Mixing these up is a classic bug.
Credentials on EC2 (instance metadata)¶
Applications on EC2 retrieve temporary credentials from instance metadata. Never bake access keys into AMIs or user data.
EC2 instance
|
v
Instance profile -> IAM role -> least-privilege policies -> AWS services
Require IMDSv2 (session tokens; blocks simple SSRF credential theft):
aws ec2 modify-instance-metadata-options \
--instance-id i-0123456789abcdef0 \
--http-tokens required \
--http-endpoint enabled
Cross-account access (pattern)¶
Account A (trusted) Account B (owns the role)
Role: DataReader <--- trust --- Trust policy: allow Account A
Permissions: read S3 in Account B
- In Account B, create role
DataReaderwith a trust policy allowing Account A's root/role. - In Account A, the caller runs
sts:AssumeRoleonarn:aws:iam::B:role/DataReader. - Temporary credentials for the role are used for subsequent calls.
8. Federation and IAM Identity Center¶
Federation = use an external identity (company IdP, Active Directory, Google, etc.) to get AWS access without long-lived IAM users.
| Option | What it is | When |
|---|---|---|
| IAM Identity Center (successor to AWS SSO) | Central portal: one login → many AWS accounts/permission sets. Integrates with IdPs (Okta, Azure AD, Google, AD). | Recommended for humans in orgs. |
| SAML 2.0 federation | Direct SAML integration with your IdP to assume roles. | Custom integrations; often backs Identity Center. |
| Cognito | Public user sign-in for apps (social login, user pools). | Application end-users, not AWS admin access. |
| Web identity (OIDC) | Roles trusted by an OIDC provider (e.g. GitHub Actions). | CI/CD pipelines without static keys. |
Human -> IdP (Okta/AD/Google)
|
v
IAM Identity Center
|
v
Permission set (policy) assumed into Target AWS account
Permission sets are templates (e.g. AdministratorAccess, PowerUser, ReadOnly) applied to users/groups per account.
9. MFA and access security¶
| Control | Why |
|---|---|
| MFA on root + all IAM users | Blocks password-only takeover. |
| Virtual / hardware MFA | Authenticator app (virtual), hardware key, or FIDO2 security key. |
| No access keys for humans | Humans should use Identity Center; keys are for machines and should rotate. |
| Password policy | Min length, complexity, expiry, no reuse. |
| Condition: require MFA | For sensitive actions (billing, IAM changes). |
Setting a strong password policy (CLI sketch)¶
aws iam update-account-password-policy \
--minimum-length 14 \
--require-symbols \
--require-numbers \
--require-uppercase-characters \
--require-lowercase-characters \
--max-password-age 90 \
--password-reuse-prevention 24
Access keys hygiene¶
| Do | Don't |
|---|---|
| One key pair per application, per environment | Share keys between apps or people |
| Rotate regularly (90 days) or use roles | Commit keys to git |
| Store in Secrets Manager / Parameter Store | Put keys in user data or AMIs |
| Delete unused keys immediately | Leave departed employees' keys active |
List your own keys and last-used info:
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 # customer-managed only
# 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.
IAM Access Analyzer¶
- Finds external sharing (roles trust outside accounts, public S3 buckets, unrestricted KMS keys, etc.).
- 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
AccessDeniedspikes (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 ~minutes 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, AccessDenied from STS — 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
*:*andResource: *. - [ ] 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.
Back to IAM topic index · Back to AWS index · Back to site home