IAM is the spine of AWS security. Wherever you go in the 6 domains we saw yesterday, you eventually reduce to one question: "Can this principal do this action on this resource?" KMS decryption, S3 GetObject, cross-account deployment — all use the same IAM evaluation engine to decide. That's why IAM must be approached not as "memorizing policy JSON" but as understanding the evaluation algorithm to solve SCS-C03's subtle scenarios.
Today we go through the four building blocks of IAM (users, groups, roles, policies) and step-by-step how AWS decides Allow/Deny when a request arrives. Let me throw ahead the key one-liner: "Explicit Deny defeats everything else. After that, explicit Allow beats the default. With neither, implicit Deny applies."
| Element | Definition | Credentials | Core Use |
|---|---|---|---|
| User | Permanent identity, corresponds to one person or app | Long-term access key, password | Avoid when possible (key exposure risk) |
| Group | Collection of users, container for attaching policies | None (no credentials) | Grant policies to users in bulk |
| Role | Identity anyone can temporarily "assume" | STS temporary credentials (expiring) | EC2/Lambda, cross-account, federation |
| Policy | JSON document describing permissions | N/A | Attached to above elements to define permissions |
Here's the insight the exam repeatedly targets: Long-term credentials (IAM User access key) should be avoided; use Role's temporary credentials instead. When an EC2 needs S3 access, don't put User access keys in the instance — use an instance profile (IAM Role). When Lambda accesses DynamoDB, use its execution role. Even people should prefer IAM Identity Center (SSO) over IAM User, receiving temporary credentials after login.
💡 Related Theory: A Group is just a "container" and has no credentials itself, so you can't designate a Group as a Principal. Common mistake for newcomers — writing
"Principal": {"AWS": "arn:...:group/Devs"}in a Role's trust policy won't work. Groups are just channels to deliver policies to users; they can't be AssumeRole targets.
🔍 Deep Dive: What "assuming" a Role means is that STS (Security Token Service) issues temporary credentials of 3 types: AccessKeyId, SecretAccessKey, SessionToken. These credentials have an expiration time (1 hour default, 12 hours max), so even if exposed, the damage is time-limited. This fundamental time-limit is why temporary creds are safer than long-term keys, covered deeply on Day 4.
Policies split into two types by "where they're attached." Today we just grasp the concept; Day 3 digs deeper.
The decisive utility of resource-based policies is enabling cross-account access. To let another account's principal access my S3 bucket, I specify that account in the bucket's resource-based policy.
Every policy is an array of Statements; each Statement has these elements:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowReadSpecificBucket",
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:ListBucket"],
"Resource": [
"arn:aws:s3:::my-secure-bucket",
"arn:aws:s3:::my-secure-bucket/*"
],
"Condition": {
"Bool": {"aws:SecureTransport": "true"}
}
}
]
}Allow or Denys3:GetObject). Wildcards (s3:*) allowed* in resource-based (policy already on that resource)⚠️ Trap: Using
"Resource": "*"and"Action": "*"together is basically admin. When the exam asks "least privilege," the answer is the option with narrowed wildcards. Specifically,s3:ListBucketapplies to bucket ARN (arn:aws:s3:::bucket), whiles3:GetObjectapplies to object ARN (.../*) — mixing these ARN levels is a common mistake.
This is today's core. When a request arrives in a single account, AWS's decision sequence is:
[ IAM Policy Evaluation Flow (Single Account) ]
Request(Principal + Action + Resource + Context)
|
1. Collect all applicable policies
(Identity-based, Resource-based, SCP, Permissions Boundary, Session policy)
|
2. Any explicit Deny? ── Yes ──▶ DENY (absolute priority)
| No
3. Does SCP allow the Action? ── No ──▶ DENY
| Yes
4. Does Permissions Boundary allow? ── No ──▶ DENY
| Yes
5. Any explicit Allow? ── No ──▶ DENY (implicit)
| Yes
▼
ALLOW
Compressed to three principles:
💡 Related Theory: When memorizing this order, the key is "Guardrails (SCP, Permissions Boundary) don't grant permissions; they only draw upper limits." Even if SCP allows
s3:*, that alone doesn't create any actual permissions — there must be explicit Allow in an identity-based policy. SCP defines "maximum allowed range." That's why SCP patterns commonly use Deny guardrails rather than Allow.
🔍 Deep Dive: For cross-account, evaluation happens separately in each account. For a principal in account A to access a resource in account B, ① A's identity-based policy must Allow AND ② B's resource-based policy must Allow A — both required. Within the same account, either one is enough. This "both needed" rule is a common trap in cross-account scenarios.
Let's apply the abstract algorithm to concrete situations.
Situation 1: Developer has admin permissions but we want to block only one specific S3 bucket.
Identity-based policy is AdministratorAccess allowing everything. Add an explicit Deny statement (e.g., specific bucket ARN with "Effect": "Deny") and the Deny beats the Allow for just that bucket. Adding the same Deny to SCP enforces it account-wide.
{
"Effect": "Deny",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::sensitive-prod-bucket",
"arn:aws:s3:::sensitive-prod-bucket/*"
]
}Situation 2: SCP explicitly Denies ec2:*, but a user has AmazonEC2FullAccess.
Result is Deny. SCP's explicit Deny overrides everything. The user's Allow becomes powerless. This is why SCP is used as a governance guardrail.
🎯 Scenario: "Security team wants to prevent anyone (including admin, root) from disabling CloudTrail across all member accounts." Answer isn't editing user policies individually — it's SCP with Deny on
cloudtrail:StopLoggingandcloudtrail:DeleteTrailapplied to the OU. Then even that account's admin or root can't disable CloudTrail (management account root is SCP-exempt). This applies the principle that explicit Deny beats all Allow.
Tracing evaluation logic mentally causes mistakes. AWS provides validation tools.
# IAM Policy Simulator: Check if a principal can do an action
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::111122223333:user/alice \
--action-names s3:GetObject s3:DeleteObject \
--resource-arns arn:aws:s3:::my-bucket/secret.txt
# Find out who the current caller is (start of role debugging)
aws sts get-caller-identity
# List policies attached to a user
aws iam list-attached-user-policies --user-name alice
aws iam list-user-policies --user-name alice # inline policies📚 Case Study: 80% of "why is access denied" in production comes down to ① implicit Deny (no one Allow'd) ② hidden Deny in SCP ③ exceeding Permissions Boundary ④ cross-account with only one side allowing. Developing the habit of first running
aws sts get-caller-identityto confirm "what role am I under right now" cuts debugging time in half. CloudTrail'sAccessDeniedevents leave clues about which policy denied it.
IAM Access Analyzer analyzes resource policies to automatically find resources allowing access beyond account or organization boundaries. S3 buckets, IAM roles, KMS keys, Lambda functions — if opened to external principals, it creates findings.
This is the intersection of Domain 2 (detective) and Domain 4 (IAM). You can't manually review hundreds of buckets and roles, so it automatically detects "access crossing trust boundaries." Its policy generation feature even creates least-privilege policies from CloudTrail logs of actual use.
💡 Related Theory: Access Analyzer's core is external vs trusted distinction. Access within the same account or organization is treated as normal; only crossing that boundary creates findings. "Set organization boundary as zone of trust" treats cross-account within organization as normal and catches only real external exposure.
Three essentials from today. First, among IAM building blocks, use Role (temporary credentials) by default; avoid User (long-term keys) — this is the starting point of all best practices. Second, policy evaluation is implicit Deny → explicit Allow overrides it → explicit Deny beats everything — the algorithm of 3 principles — and SCP·Permissions Boundary don't grant permissions, only draw upper limits. Third, cross-account requires both accounts to allow.
Tomorrow we dig deeper into policies. The subtle interplay between Identity vs Resource policies, precision controls using Condition keys, and practical patterns for designing least privilege with Permissions Boundary. The evaluation algorithm you learned today is the foundation for all of it.
Click a choice to reveal the answer and explanation.
Question 1
An IAM user has the `AdministratorAccess` managed policy. At the same time, the SCP applied to that user's account contains an explicit `Deny` on `s3:*`. What happens when the user tries to read an S3 object?
Question 2
An IAM role in Account A wants to write an object to an S3 bucket in Account B. Which condition must be met for the access to succeed?
Question 3
Which statement about IAM Groups is correct?
Question 4
A developer has been granted `AmazonS3FullAccess` but tries to call `dynamodb:GetItem`, which no policy explicitly allows. Which outcome and reason are correct?
Question 5
A security team wants to enforce that nobody — including admins and root — can disable CloudTrail in any member account. Which approach is most appropriate?