AWS Certified Solutions Architect - Associate (SAA-C03) #2 Domain 1-1 Secure Architectures — IAM in Depth
In #1 Exam Introduction, I noted that among the four SAA-C03 domains, Security carries the largest weight at 30%. IAM sits at the center of that security domain. IAM isn’t just “where you create users” — it’s the authorization layer every AWS request passes through. The exam reframes IAM as a design problem: “who accesses what, under which conditions?”
This post adds Associate-level depth on top of the IAM basics from CLF-C02. It moves quickly past the definitions of User, Group, Role, and Policy and spends time on the points that decide answers on the exam — policy evaluation logic, STS, cross-account, and permission boundaries.
Quick review of the four IAM components #
| Component | One-line definition | Credentials |
|---|---|---|
| User | A permanent identity corresponding to one person or application | Long-term credentials (password, access key) |
| Group | A bundle of Users. A unit for grouping and granting policies | None |
| Role | An identity anyone can temporarily “assume” | Temporary credentials (issued by STS) |
| Policy | A JSON document defining permissions | N/A |
The distinction the exam repeatedly targets here is the credential difference between User and Role. A User has a permanent access key, but a Role stores no credentials and instead has STS issue temporary credentials the moment it’s assumed. That’s exactly why the answer to a scenario like “code on EC2 needs to access S3” is always “attach a Role” rather than “embed an access key.”
IAM is a global service. You don’t pick a region, and Users, Roles, and Policies appear the same across every region.
The structure of a Policy #
An IAM policy is a JSON document whose core is the Statement array. Each Statement is composed of the following elements.
- Effect —
AlloworDeny - Action — the API action to allow/deny (e.g.,
s3:GetObject) - Resource — the ARN of the target resource
- Condition (optional) — the conditions for applying it (e.g., specific IP, when MFA is used, specific tags)
- Principal (resource-based policies only) — who it applies to
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::my-bucket/*",
"Condition": {
"Bool": { "aws:MultiFactorAuthPresent": "true" }
}
}
]
}Condition shows up often on SAA. Requirements like “allow only when MFA is used,” “allow access only through a specific VPC Endpoint,” and “control only resources with a specific tag” are all expressed with Condition.
Identity-based policies vs. resource-based policies #
| Aspect | Identity-based | Resource-based |
|---|---|---|
| Attached to | User / Group / Role | A resource (S3 bucket, SQS queue, KMS key, etc.) |
| Principal element | None (already attached to an identity) | Present (specifies who is allowed) |
| Typical purpose | “What can this user do?” | “Who can access this resource?” |
| Cross-account | Requires role delegation | Can directly allow another account’s Principal |
This distinction is the heart of cross-account access questions. There are two ways to grant another account access to S3. (1) Directly allow the other account’s Principal in the bucket policy (resource-based), or (2) create a Role in your account and have the other account AssumeRole.
Policy evaluation logic (an exam regular) #
When multiple policies apply at once, the order in which AWS decides allow/deny shows up on the exam almost every time. There are three core rules.
- The default is implicit deny — if no policy allows it, it’s denied.
- An explicit Allow grants access — as long as there’s no Deny.
- An explicit Deny beats every Allow — if a Deny appears anywhere even once, the final result is denied.
Add SCPs (Service Control Policies), permission boundaries, and session policies, and the evaluation gets more complex. But the one sentence to memorize for the exam is this.
An explicit Deny always wins. One Deny anywhere ends it.
Role and STS — temporary credentials #
A Role is an identity that stores no credentials. When someone “assumes” a Role, the AWS Security Token Service (STS) issues a set of three temporary credentials (access key ID, secret key, session token). These credentials have an expiration time, so even if they leak, the damage is limited.
Trust policy vs. permission policy #
A Role has two different policies attached. If you can’t tell them apart, you’ll waver on cross-account questions.
| Policy | Question | Where |
|---|---|---|
| Trust Policy | Who can assume this Role | The Role’s AssumeRolePolicyDocument |
| Permission Policy | What can this Role do | The policies attached to the Role |
If the EC2 service goes in the trust policy’s Principal, an EC2 instance can assume the Role; if another account ID goes in, an identity from that account can assume it.
Common forms of AssumeRole #
- EC2 → Role (instance profile) — attach a Role to EC2, and temporary credentials are automatically supplied through the instance metadata. No need to store an access key in code or on the instance.
- Lambda execution role — the Role a Lambda function uses when accessing other services.
- Cross-account AssumeRole — a user in account B assumes a Role in account A to control A’s resources.
- Web identity federation (AssumeRoleWithWebIdentity) — gives temporary credentials to users authenticated by Cognito or an OIDC provider.
Designing cross-account access #
Running multiple accounts at a company is a baseline assumption on SAA. Here are the two axes for designing cross-account access.
- Role delegation — create a Role in account A and put account B in the trust policy. B’s users use
sts:AssumeRoleto assume A’s Role and control A’s resources. Suitable when control spans several services. - Resource-based policy — directly allow another account’s Principal on the resource itself, as with S3 bucket policies or KMS key policies. Simple for sharing a single resource.
At scale, AWS Organizations + IAM Identity Center manages multi-account access centrally. SCPs set the upper bound (guardrail) on the permissions an account can hold at the Organizations level. The key point is that an SCP grants no permissions — it only limits the allowable range.
Permission Boundary #
A permission boundary is a policy that limits the maximum permissions a single IAM identity can hold. Like an SCP, it sets an “upper bound,” but the unit of application differs.
| Mechanism | Unit of application | Role |
|---|---|---|
| SCP | An Organizations account / OU | Permission ceiling for an entire account |
| Permission Boundary | An individual IAM User / Role | Permission ceiling for that identity |
| Identity-based policy | User / Role | The actual permission grant |
Final permissions are the intersection of granted policies and the ceiling (boundary / SCP). For example, you’d use it to delegate permissions to an administrator while imposing a constraint like “create IAM users only within the permission boundary.”
Exam question patterns #
- “An application on EC2 needs to access S3. What’s the most secure way?” → IAM Role (instance profile). The store-an-access-key answer is wrong.
- “A user in account B needs to control resources in account A.” → Create a Role in A + allow B in the trust policy + B does AssumeRole.
- “Multiple policies conflict. What’s the result?” → If there’s an explicit Deny, it’s denied.
- “Forbid use of a specific region across the whole organization.” → SCP (not a grant, a ceiling limit).
- “Delegate permissions to a developer but with a cap.” → Permission Boundary.
- “Give mobile app users temporary AWS access.” → Cognito / AssumeRoleWithWebIdentity (don’t distribute long-term keys to the app).
Common traps #
1) Thinking a Role has an access key #
A Role has no long-term access key. STS simply issues temporary credentials the moment it’s assumed.
2) Misunderstanding that an SCP grants permissions #
An SCP only sets a ceiling. Even with an Allow in an SCP, you have no permission unless an IAM policy separately allows it.
3) Confusing the trust policy with the permission policy #
“Who assumes it” is the trust policy; “what it does” is the permission policy. Swap the two on a cross-account question and you’ll get it wrong.
4) Cloning an IAM User into another account #
Creating another User in the other account just to use that account is usually the wrong answer. Role delegation is the standard approach.
5) Storing long-term keys on EC2 #
Answers like “read the access key from an environment variable in code” are almost always wrong on SAA. Use a Role and there’s no key at all.
Wrap-up #
What this post locked in:
- IAM is a global authorization layer. Users have permanent credentials; Roles have STS temporary credentials
- Policies are Effect , Action , Resource , Condition (+ Principal for resource-based). Use Condition to express MFA, IP, and tag conditions
- Evaluation logic — default deny, allow via explicit Allow, explicit Deny beats them all
- A Role has two policies attached: a trust policy (who assumes it) and a permission policy (what it does)
- Cross-account — role delegation (AssumeRole) or resource-based policies. At scale, Organizations + Identity Center
- Ceiling mechanisms — SCP (account level), Permission Boundary (identity level). Both are limits, not grants
Next — Domain 1-2 KMS and Encryption #
With identity and permissions in hand, next comes encrypting the data itself.
#3 Domain 1-2 KMS and Encryption covers KMS key types (AWS managed, customer managed, customer provided), how envelope encryption works, the difference between at-rest and in-transit encryption, the encryption options for S3, EBS, and RDS, and key policies and cross-account key sharing.