1 · IAM Deep Dive¶
Domain 1 — Design Secure Architectures · 30%
Security is the biggest domain, and IAM underpins most of it. The exam rarely asks you to recite IAM — it asks you to decide "given these policies, is this request allowed?" or "what's the secure way to grant this access?" Get the evaluation model and the "use a role" reflex, and most IAM questions become mechanical.
Core concept¶
Every request to AWS — a console click, a CLI call, an SDK call — is authenticated to a principal and then checked against the applicable policies. AWS builds a request context of five things and asks whether the policies allow it:
| Element | Meaning | Example |
|---|---|---|
| Principal | Who is making the request | An IAM role, user, or AWS service |
| Action | The operation attempted | s3:GetObject, ec2:StopInstances |
| Resource | The target ARN | arn:aws:s3:::reports/q1.pdf |
| Condition | Extra context checked | Source IP, MFA present, TLS |
| Effect | What a matching statement does | Allow or Deny |
The decision follows one rule you should be able to recite in your sleep:
Explicit Deny > Explicit Allow > Implicit (default) Deny.
Start from an implicit deny (nothing is allowed until granted). If any applicable policy has a
matching Deny, the request is denied — permanently, no override. Otherwise, if a policy grants it
(and it survives any boundary/SCP limits), it's allowed. If nothing allows it, the default deny wins.
Think of it like building security: a Deny is a "this person is barred" note at the front desk
that overrides every access badge, and no badge (no Allow) means you don't get in either.
Two facts, then move on
IAM is global (not Region-scoped) and has no charge. Changes are eventually consistent — usually effective in seconds. None of these are exam differentiators; just know them.
Identities — and when to use which¶
| Identity | What it is | Exam signal |
|---|---|---|
| Root user | The account's all-powerful owner login | Lock away: MFA on, no access keys, don't use daily. Can't be limited by IAM policy — only an SCP can restrict root in an Org member account. |
| IAM user | Long-lived identity with permanent credentials (password and/or access keys) | Avoid access keys wherever a role fits — they don't self-rotate and leak easily. |
| Group | A container to attach policies to a set of users | Not a principal — can't be a policy Principal, can't be assumed, can't nest. Users can be in many. |
| Role | Identity with no permanent credentials; assumed to get temporary STS credentials | The exam's default answer for workloads, cross-account, and federation. |
A role carries two policies: a trust policy (resource-based — who may assume it) and a
permission policy (what it can do once assumed). Temporary credentials always include a
session token (AWS_SESSION_TOKEN) — that's how you spot role/STS creds vs long-term user keys.
The "what do I attach?" reflex — this table answers a large share of IAM questions:
| Scenario | Answer |
|---|---|
| App on EC2 needs AWS access | IAM role via an instance profile (never store keys on the box / in user data / in the AMI) |
| Lambda needs AWS access | The function's execution role |
| ECS task needs AWS access | A task role |
| CloudFormation / CodePipeline acting for you | A service role |
| Humans across many accounts | IAM Identity Center (SSO) + permission sets |
| Corporate IdP (AD / Okta) | SAML 2.0 federation → assume role |
| Web/mobile app users (Google/Facebook) | Amazon Cognito / OIDC → temporary creds (never embed keys in the app) |
| Third-party SaaS assumes a role in your account | Role + trust policy with sts:ExternalId |
The unifying idea: workloads and federated humans never get long-term keys — they assume roles and receive temporary credentials.
Policy types and how they combine (the judgment)¶
There are more policy types than most people expect, and the exam tests their interaction:
| Type | Attached to | Grants or limits? |
|---|---|---|
| Identity-based (managed or inline) | user, group, role | Grants |
| Resource-based (S3 bucket, SQS, SNS, KMS, Lambda, role trust) | the resource | Grants — and can grant cross-account directly |
| Permissions boundary | user, role | Limits (a ceiling) |
| SCP (AWS Organizations) | OU / account | Limits the whole account (incl. root) |
- Managed policies are standalone and reusable (AWS managed = maintained by AWS but often over-broad; customer managed = your reusable, versioned policies). Inline policies are embedded 1:1 in a single identity and deleted with it.
The combination rules are the exam gold (all same-account unless noted):
| Combination | Rule | Plain English |
|---|---|---|
| Identity + identity | Union | any one can grant it |
| Identity + resource | Union | allowed if either grants it (no deny) |
| Identity + permissions boundary | Intersection | must be allowed by both |
| Identity + SCP | Intersection | must be allowed by all |
| Cross-account | Both sides | target resource/role and caller's identity policy must allow |
A permissions boundary or SCP never grants anything — it only caps. And an explicit Deny in
any layer overrides everything.
Policy anatomy (only the fields the exam cares about):
{
"Version": "2012-10-17", // a language version, NOT a date to bump
"Statement": [{
"Effect": "Allow", // or "Deny"
"Action": ["s3:GetObject"], // service:Operation, wildcards ok (s3:*)
"Resource": "arn:aws:s3:::reports/*",
"Condition": { "Bool": { "aws:SecureTransport": "true" } }
}]
}
Principal appears only in resource-based / trust policies (in an identity policy, the identity
it's attached to is the principal).
NotAction / NotPrincipal invert the match
"NotAction": ["s3:*"] with Effect: Allow means "allow everything except S3" — a huge
grant. The exam uses these to see whether you actually read the policy.
Cross-account access¶
Two clean patterns:
- Resource-based policy — services like S3/SQS/SNS/KMS/Lambda let the resource's policy name a principal in another account directly. The caller uses their own credentials; they still need an identity policy allowing the action. Good for sharing one specific resource.
- Role assumption (
sts:AssumeRole) — Account B makes a role whose trust policy names Account A; a principal in A assumes it and gets temporary credentials acting as the role in B. The general-purpose pattern; works even for services without resource policies.
Confused deputy + ExternalId. When a third party assumes a role in your account, an attacker
could trick that third party into acting against your resources. Require a shared secret
sts:ExternalId in the trust policy condition so one customer's ID can't be used against
another. For your own accounts in one Org, prefer the aws:PrincipalOrgID condition instead of
listing account IDs.
STS session facts (verified)
Assumed-role sessions last 15 min – 12 h (role's max); default is 1 hour if unspecified.
Cross-account AssumeRole counts against the caller's STS quota, not the target's.
Conditions worth recognizing¶
The Condition block is where fine-grained control lives. The ones that show up on the exam:
| Key | Use |
|---|---|
aws:SecureTransport |
Deny non-HTTPS access (see trap below) |
aws:MultiFactorAuthPresent |
Require MFA for sensitive actions |
aws:PrincipalOrgID |
Allow any account in my Org without listing IDs |
aws:SourceIp |
Restrict to an office CIDR (not for VPC-internal traffic) |
aws:RequestedRegion |
Confine actions to approved Regions |
aws:PrincipalTag / aws:ResourceTag |
ABAC — scale access by comparing tags |
ABAC vs RBAC (increasingly tested): RBAC adds a policy per job function; ABAC uses a few
policies that compare tags (e.g. a principal may act on resources whose Project tag matches its
own). When a scenario asks "how do we scale permissions across many teams/projects without writing a
new policy each time?" → ABAC with tags.
Worked examples¶
The two IAM mechanics people get wrong under time pressure — work each before expanding.
Example 1 — Deny always wins¶
A user is in the Admins group (AdministratorAccess), and also has this inline policy:
Can the user delete an S3 object?
Answer
No. The admin allow is overridden by the explicit Deny on s3:DeleteObject — explicit deny
beats any allow, however broad. Every other S3 action still works. This is exactly how you carve
a narrow exception out of broad access.
Example 2 — Permissions boundary caps a broad grant¶
A role's identity policy allows ec2:* and s3:* on *. Its permissions boundary is:
Can the role run ec2:StopInstances?
Answer
No. Effective permissions are the intersection of the identity policy and the boundary. EC2 is allowed by the identity policy but not the boundary, so it falls outside the overlap. The role can do S3 (allowed by both). Boundaries only cap — they never add access back.
Exam traps¶
- Explicit deny always wins — even against
AdministratorAccess. Any matchingDeny→ denied. - Boundaries and SCPs never grant — they only cap; you still need an
Allow. - Groups aren't principals — can't be assumed, referenced as
Principal, or nested. - Cross-account needs both sides; same-account identity+resource is a union (either suffices).
- Root can't be limited by IAM policies — only an SCP restricts root in a member account.
- A conditional Allow is not a Deny. To block plain HTTP you need an explicit
Denywhenaws:SecureTransportisfalse— a TLS-only conditional allow doesn't stop another policy from granting HTTP access. - No long-term keys on EC2 or in mobile apps → instance role / Cognito. Enforce IMDSv2 to stop SSRF-based theft of instance credentials.
Version: 2012-10-17is a policy-language version, not a date to update.
Tools (know which answers which question)¶
| Question | Tool |
|---|---|
| "Which services has this principal actually used? Can I trim its policy?" | IAM Access Advisor |
| "CSV of every user's key age, MFA, last use" | IAM credential report |
| "Is anything shared outside my account/Org?" (also generates least-priv policies) | IAM Access Analyzer |
| "Does this policy grant what I think?" | IAM Policy Simulator |
| "Who made this API call, when, from where?" | CloudTrail |
Practice questions¶
Q1. A stateless web app on EC2 in an Auto Scaling group must read/write a specific S3 bucket. No long-term credentials may be stored on the instances. Best approach?
- A. Create an IAM user and pass its access keys via EC2 user data.
- B. Attach an IAM role with an S3 policy to the instances via an instance profile.
- C. Bake access keys into the AMI.
- D. Store keys in Parameter Store and hard-code the retrieval credentials.
Answer: B
B — a role via an instance profile gives temporary, auto-rotating credentials with nothing stored on the instance. A and C store long-term keys (prohibited, leak risk). D is circular — you'd still need credentials to reach Parameter Store.
Q2. A user is in a group granting AdministratorAccess. A policy attached directly to the user
explicitly denies all dynamodb:*. The user runs a DynamoDB Scan. Result?
- A. Allowed —
AdministratorAccessgrants everything. - B. Allowed — user policies override group policies.
- C. Denied — an explicit deny overrides any allow.
- D. Depends which policy is newer.
Answer: C
C. Order is explicit deny > allow > default deny. The union of the user's policies contains an
explicit Deny, which wins. "User beats group" (B) and "recency" (D) are invented rules — all
applicable policies are evaluated together.
Q3. Account A stores files in an S3 bucket. A team in Account B needs read-only access with least privilege and no long-term credentials shared across accounts. Best approach?
- A. Create IAM users in Account A for the B team and email the access keys.
- B. Make the bucket public and share the URL.
- C. Add a bucket policy in A allowing a role in B, and have B's users assume that role (which also has an identity policy allowing
s3:GetObject). - D. Copy all objects nightly into an Account B bucket.
Answer: C
C. Cross-account access needs the target to allow the external principal and the caller's identity policy to allow the action — a bucket policy + assumable role does this with temporary credentials and no shared keys. A shares long-term keys across accounts; B exposes data publicly; D duplicates data and cost and doesn't grant access to the source.
Q4. A platform team wants app teams to create their own IAM roles, but must guarantee those roles
can never exceed S3 + DynamoDB — even if a team writes *:*. What should they use?
- A. An SCP allowing only S3 and DynamoDB for the whole account.
- B. A permissions boundary (allowing only S3 + DynamoDB) on every role the teams create.
- C. A resource-based policy on S3 and DynamoDB.
- D. A deny-all inline policy on each role.
Answer: B
B. A permissions boundary caps effective permissions to the intersection with the team's
policy, so even *:* is limited to S3 + DynamoDB. An SCP (A) would cap the entire account
including the platform team — too blunt. C doesn't constrain the roles generally; D blocks
everything.
Q5. A bucket policy allows s3:GetObject only when aws:SecureTransport is true. A user's
identity policy allows s3:GetObject on the same bucket with no conditions. The user requests an
object over plain HTTP. Result?
- A. Allowed — the identity policy allows it unconditionally.
- B. Denied — the HTTP request fails the bucket policy's condition.
- C. Allowed — bucket-policy conditions don't apply to authenticated users.
- D. Allowed, but the object is returned unencrypted.
Answer: A
A. This tests conditional-allow vs deny. Over HTTP the bucket policy statement simply
doesn't apply (its condition isn't met) — that is not a deny. The identity policy still
allows the action, and same-account identity+resource is a union, so it's allowed. To actually
block HTTP you need an explicit Deny on aws:SecureTransport = false.
Next: 2 · Data Protection → · Compress with the Revise page · Prove it with Mock Exam 1