IAM is the spine of AWS security. Wherever you go among the six domains we saw yesterday, everything ultimately reduces to the question: "Can this principal perform this action on this resource?" KMS decryption, S3 GetObject, cross-account deployment — all are decided on the same engine: IAM evaluation logic. That is why you must approach IAM not as "memorizing policy JSON" but as understanding the evaluation algorithm if you want to solve SCS-C03's subtle scenarios.
Today we walk through exactly what IAM's four building blocks (users, groups, roles, policies) are, and step through the evaluation flow by which AWS decides Allow/Deny when a request comes in. To give you the one key sentence of the evaluation order up front: "An explicit Deny beats everything. Then there must be an explicit Allow for the request to be permitted. If neither exists, it's an implicit Deny."
| Element | Definition | Credentials | Primary use |
|---|---|---|---|
| User | Permanent identity, corresponding to one person or app | Long-term access keys, password | Avoid where possible (key exposure risk) |
| Group | A collection of users, a container for attaching policies | None (no credentials) | Grant policies to users in bulk |
| Role | An identity anyone can temporarily "assume" | STS temporary credentials (expiring) | EC2/Lambda, cross-account, federation |
| Policy | A JSON document describing permissions | N/A | Attached to the elements above to define permissions |
Here is an insight the exam targets repeatedly: avoid long-term credentials (IAM User access keys) as much as possible, and use a Role's temporary credentials instead. If EC2 needs access to S3, you do not put User access keys on the instance — you attach an instance profile (IAM Role). If Lambda accesses DynamoDB, you use an execution role. Even humans should preferably sign in via IAM Identity Center (SSO) and receive temporary credentials instead of using IAM Users.
💡 Related theory: A Group is only a "container" and has no credentials of its own, so This is a spot newcomers frequently get wrong — writing in a Role's trust policy does not work. A group is merely a conduit for delivering policies to users; it cannot be the target of AssumeRole.
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 on the user's account contains an explicit `Deny` on `s3:*`. What happens when this user tries to read an S3 object?
Question 2
An IAM role in account A wants to write objects to an S3 bucket in account B. Which condition must be satisfied 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. What is the result and the reason?
Question 5
A security team wants to enforce that no one (including admins and root) in any member account can disable CloudTrail. What is the most appropriate method?
"Principal": {"AWS": "arn:...:group/Devs"}🔍 Going deeper: What "assuming" a Role actually means is that STS (Security Token Service) issues a set of three temporary credentials (AccessKeyId, SecretAccessKey, SessionToken). These credentials have an expiration time (default 1 hour, maximum 12 hours), so even if they are exposed, the impact is bounded in time. This is the fundamental reason they are safer than long-term keys, and we cover STS in depth on Day 4.
Policies split into two kinds by "where they attach." Today we only establish the concept; Day 3 goes deep.
The decisive utility of resource-based policies is that they enable cross-account access. To open your S3 bucket to a principal in another account, you specify the other account in the bucket's resource-based policy.
Every policy is an array of Statements, and each Statement has the following 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:*) are allowed* (the policy is already attached to that resource)⚠️ Pitfall: Combining
"Resource": "*"with"Action": "*"is effectively admin. When the exam asks about "least privilege," the answer choice that narrows the wildcards is correct. In particular,s3:ListBucketapplies to the bucket ARN (arn:aws:s3:::bucket), whiles3:GetObjectapplies to the object ARN (.../*) — mixing up the two ARN levels so the policy does not work is a common mistake.
This is today's core. Within a single account, when a request comes in, AWS decides in the following order.
[ 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. Is there even one explicit Deny? ── Yes ──▶ DENY (top priority, unconditional)
| No
3. Does the SCP allow this Action? ── No ──▶ DENY
| Yes
4. Does the Permissions Boundary allow it? ── No ──▶ DENY
| Yes
5. Is there even one explicit Allow? ── No ──▶ DENY (implicit deny)
| Yes
▼
ALLOW
It compresses into three principles.
💡 Related theory: When memorizing this evaluation order, the key point is that "guardrails (SCPs, Permissions Boundaries) do not grant permissions — they only draw an upper bound." Even if an SCP allows
s3:*, that alone creates no permissions — there must be an explicit Allow in an identity-based policy. An SCP only defines the "maximum allowed scope." That is why the common pattern in SCPs is not Allow but Deny guardrails.
🔍 Going deeper: In the cross-account case, evaluation happens separately in both accounts. For a principal in account A to access a bucket in account B, you need ① an Allow in account A's identity-based policy and ② account B's resource-based policy allowing A — both are required (within the same account, either one alone suffices). This "both sides" rule is the recurring trap in cross-account scenarios.
Let's hold the abstract algorithm up against concrete situations.
Situation 1: A developer has admin permissions, but you want to block only one specific S3 bucket.
The identity-based policy is AdministratorAccess, so it allows everything. If you add an explicit Deny statement (e.g., "Effect": "Deny" on the specific bucket ARN), the explicit Deny beats the Allow, and only that bucket is blocked. Put the same Deny in an SCP and it is enforced across the whole account.
{
"Effect": "Deny",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::sensitive-prod-bucket",
"arn:aws:s3:::sensitive-prod-bucket/*"
]
}Situation 2: An SCP explicitly denies ec2:*, but the user has AmazonEC2FullAccess.
The result is Deny. The SCP's explicit Deny beats everything. The Allow in the user's policy is neutralized. This is why SCPs are used as governance guardrails.
🎯 Scenario: A security team wants to prevent CloudTrail from being disabled in all accounts. The answer is not to modify per-user policies but to apply an SCP denying
cloudtrail:StopLoggingandcloudtrail:DeleteTrailto the OU. Then even an admin or root in that account cannot turn off CloudTrail (the management account root is exempt from SCPs). It is the governance application of the principle that an explicit Deny beats every Allow.
If you trace the evaluation logic only in your head, you will make mistakes. AWS provides verification tools.
# IAM Policy Simulator: simulate whether a specific principal can perform specific actions
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
# Who is the current caller? (the starting point of role debugging)
aws sts get-caller-identity
# List the 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: In production, 80% of "why is access denied" comes down to four causes: ① implicit Deny (nothing allows it), ② a hidden Deny in an SCP, ③ exceeding a Permissions Boundary, ④ only one side allowed in a cross-account setup. The habit of first checking "which role am I right now" with
aws sts get-caller-identitycuts debugging time in half. CloudTrail'sAccessDeniedevents leave clues about which policy caused the denial.
IAM Access Analyzer analyzes resource policies to automatically find resources that allow access from outside the account or organization boundary. If an S3 bucket, IAM role, KMS key, Lambda function, etc. is open to an external principal, it generates a finding.
This is the intersection of Domain 2 (detection) and Domain 4 (IAM). A human cannot manually review hundreds of buckets and roles, so "access crossing the trust boundary" is detected automatically. The policy generation feature also analyzes CloudTrail logs to build a least-privilege policy from only the permissions actually used.
💡 Related theory: The core of Access Analyzer is the external vs trusted distinction. Access within the same account or organization is treated as normal; only access crossing that boundary is raised as a finding. If you "set the zone of trust to the organization," cross-account access within the organization is treated as normal and only genuine external exposure is caught.
Today's three key points. First, among IAM's building blocks, use Roles (temporary credentials) by default and avoid Users (long-term keys) — this is the starting point of every best practice. Second, policy evaluation is an algorithm of three principles — implicit Deny → explicit Allow overrides it → explicit Deny beats everything — and SCPs and Permissions Boundaries do not grant permissions, they only draw the upper bound. Third, cross-account access requires allows in both accounts.
Tomorrow we dig deeper into policies. We cover the subtle interplay of Identity vs Resource policies, precision control using Condition keys, and practical patterns for designing least privilege with Permissions Boundaries. The evaluation algorithm you learned today is the foundation for all of it.