AWS SAA-C03: Design Secure Access to AWS Resources
Why this topic matters on the SAA-C03 exam
On SAA-C03, secure-access questions are not usually about naming an AWS service—oddly enough, they are about judgment. Which design is the most secure? Which one scales? Which one is operationally sane? The exam wants you to separate authentication from authorization, to know which policy layer is doing the real work, and to notice when an extra gate appears—encryption, network routing, cross-account trust… all of it.
And the quickest way to stumble? Thinking “IAM policy” and stopping there. That’s the trap. In the real world—and on the exam—access may hinge on IAM roles, trust policies, resource-based policies, SCPs, permission boundaries, session policies, KMS key policy or grants, VPC endpoint policies, and logging that proves who did what. The best answers avoid long-term credentials, centralize workforce access, enforce least privilege, and leave an audit trail that is actually usable. Why settle for less?
Human, workload, and application identity comparison
The first question is always simple. Or at least, it should be: who is trying to access AWS?
| Access Type | Best Default Choice | Typical Exam Clues |
|---|---|---|
| Employees and contractors | IAM Identity Center | Multiple AWS accounts, corporate directory, centralized access, MFA |
| AWS workloads and services | IAM roles with STS-issued temporary credentials | EC2, Lambda, ECS, EKS, Step Functions, CI/CD |
| Application end users | Amazon Cognito | Mobile app users, web sign-in, customer registration |
| Legacy or rare exception cases | IAM users | Specific long-lived credential requirement, legacy integration |
For workforce access, the default answer is usually IAM Identity Center. Centralized. MFA-friendly. Multi-account. Permission sets do the heavy lifting by provisioning IAM roles into target accounts—so no, the permissions are not floating in some abstract cloud ether. They become roles. Real ones.
If the company already has an external identity provider, the exam still tends to point you toward Identity Center as the workforce access layer rather than dragging you into protocol trivia. In practice, external workforce federation is commonly SAML-based—but on the exam, the bigger idea matters more than the acronyms.
For workloads, use roles instead of access keys. Always. EC2 instance profiles, Lambda execution roles, ECS task roles, EKS IAM roles for service accounts and EKS Pod Identity, Step Functions execution roles—different names, same pattern: temporary credentials issued through STS. Sometimes a caller explicitly invokes AssumeRole; other times AWS quietly does the credential work behind the curtain.
For application end users, Cognito is the right fit. User pools handle sign-in; identity pools can exchange identities for limited AWS credentials when the app needs to talk directly to AWS services. Clean separation. Better control.
Exam cue: workforce = Identity Center, workloads = roles, app users = Cognito.
IAM Identity Center and federation in practice
A clean workforce setup usually unfolds in a predictable order: connect IAM Identity Center to an identity source, map groups, define permission sets, assign those permission sets to accounts, enforce MFA, and issue short-lived sessions into only the accounts people actually need. Simple in concept. Much nicer than sprinkling IAM users across accounts like confetti.
Example? Developers get a PowerUser-style permission set in the development account, read-only in production, and nothing in the logging account. Security gets audit roles everywhere. More governable. More scalable. And, frankly, far less painful than maintaining individual users in every account.
Implementation details that matter—because they always do:
- Use group-based assignments instead of one-off user assignments when possible.
- Set a sensible session duration for each permission set.
- Enforce MFA at the Identity Center or external IdP layer.
- Use AWS Organizations for account assignment at scale.
For federation, keep the STS variants straight at a high level: AssumeRole for role assumption, AssumeRoleWithSAML for SAML federation, and AssumeRoleWithWebIdentity for OIDC or web identity federation. That matters more for workloads and CI/CD systems than for a human signing into Identity Center. Different roads… same destination: temporary credentials.
How AWS permission evaluation actually works
AWS authorization begins with implicit deny. Always. A request is allowed only when there is an applicable allow and no applicable explicit deny. Not every policy type applies in every situation, but the mental model stays consistent. That consistency is the trick.
- Authenticate the principal.
- Check for explicit deny in any applicable layer.
- Evaluate identity-based and resource-based policies.
- Apply limiting controls such as SCPs, permission boundaries, and session policies.
- If encryption is involved, evaluate KMS authorization.
- If the path uses a VPC endpoint, apply the endpoint policy as an additional filter.
Identity-based policies attach to users, groups, or roles. Resource-based policies attach to resources such as S3 buckets, Lambda functions, SQS queues, SNS topics, or Secrets Manager secrets. Trust policies are a special kind of resource-based policy on IAM roles; they decide who can assume the role—not what the role can do after it has been assumed. Important distinction. Easy to blur. Expensive to blur on the exam.
SCPs do not grant permissions. They only set the upper ceiling for accounts in an organization. Permission boundaries do the same thing, but at the IAM principal level. Session policies can narrow permissions even further for one specific session. Layer upon layer… and the deny still wins.
Remember: identity and resource policies can combine to allow access, but boundaries, SCPs, and session policies only limit. Explicit deny wins over everything.
Policy examples that matter
Trust policy for cross-account role assumption
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::111122223333:role/SecurityAuditRole" }, "Action": "sts:AssumeRole" } ] }
This trusts a specific role in another account. Better than trusting an entire account broadly—unless, of course, broad trust is truly what you need (rarely the cleanest answer). For third-party access, add conditions like sts:ExternalId. For organization-scoped trust, consider aws:PrincipalOrgID. Narrower. Safer.
SCP example with regional guardrail
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Deny", "Action": "*", "Resource": "*", "Condition": { "StringNotEquals": { "aws:RequestedRegion": ["us-east-1", "us-west-2"] } } } ] }
Useful? Absolutely. But carefully. Some AWS services are global, and some control-plane behaviors do not fit neatly into a region-deny blanket. So yes—guardrails. But not without exceptions and testing.
Cross-account access decision tree
When access crosses account boundaries, start here: does the caller need to become a principal in the target account, or should the resource directly trust the external principal?
- Cross-account administration: usually use
AssumeRoleinto the target account. - Direct access to a resource in another account: often use a resource-based policy.
- Encrypted cross-account access: verify KMS permissions too.
For S3, either pattern can work. If Account A assumes a role in Account B and then accesses a bucket in Account B, an identity-based policy in Account B may be enough. If Account B wants to directly allow a principal from Account A to access the bucket, a bucket policy is commonly used. So—no, bucket policies are not always mandatory in every cross-account S3 design. That would be too neat, and AWS rarely is.
A strong admin pattern is a centralized security account with audit roles in workload accounts. The workload role trust policy allows the security account role to assume it, and the target role grants only the needed read permissions. Cleaner. Easier to review. Much better than scattered users everywhere.
S3 secure access patterns likely to appear on SAA-C03
S3 shows up a lot because it mixes IAM, resource policies, public-exposure controls, encryption, and network restrictions. In other words: it is the exam’s favorite place to make access decisions look deceptively simple.
High-yield controls:
- Bucket policies for direct resource trust and conditional access.
- S3 Block Public Access to stop accidental public exposure.
- Object Ownership: Bucket owner enforced to disable ACLs and avoid cross-account ownership surprises.
- Access Points to simplify access delegation for large shared buckets.
- Presigned URLs for temporary object access without broad bucket permissions.
If hard enforcement is required for private-only access, use explicit deny conditions rather than trusting a conditional allow to do all the work. After all, denial is the lock; allow is just the key.
{ "Version": "2012-10-17", "Statement": [ { "Sid": "DenyNonTLS", "Effect": "Deny", "Principal": "*", "Action": "s3:*", "Resource": [ "arn:aws:s3:::company-data-lake", "arn:aws:s3:::company-data-lake/*" ], "Condition": { "Bool": { "aws:SecureTransport": "false" } } } ] }
If the bucket uses SSE-KMS, S3 permission is not the whole story. The caller may also need KMS permissions such as kms:Decrypt, depending on the action and the integration. One lock opens… and then another one appears.
KMS troubleshooting for architects
KMS is where many “everything looks correct” designs quietly fail. Use current terminology: customer managed KMS key, not the old “CMK” label. AWS managed keys and customer managed keys are both KMS keys; the difference is policy control and lifecycle ownership.
KMS authorization is more nuanced than ordinary resource access. Effective access may come from:
- the key policy directly,
- IAM policies if the key policy enables IAM-based delegation,
- grants, which are commonly used by AWS-integrated services.
So no, it is not universally true that both IAM and key policy must explicitly allow every use case. But on the exam, if encrypted access breaks, your first suspicion should be KMS. Every time. Almost annoyingly often.
{ "Version": "2012-10-17", "Statement": [ { "Sid": "AllowKeyAdmins", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::123456789012:role/SecurityKeyAdmins" }, "Action": [ "kms:Create*","kms:Describe*","kms:Enable*","kms:List*","kms:Put*", "kms:Update*","kms:Revoke*","kms:Disable*","kms:Get*","kms:Delete*", "kms:TagResource","kms:UntagResource","kms:ScheduleKeyDeletion", "kms:CancelKeyDeletion","kms:CreateGrant","kms:ListGrants","kms:RetireGrant" ], "Resource": "*" } ] }
Common failure pattern: a Lambda function can read a secret’s metadata but cannot decrypt the value. Diagnose the execution role, the secret resource policy if cross-account, and the KMS key policy or grant. And remember—KMS keys are regional. That detail matters more than people expect.
Private access is not authorization
A private network path reduces exposure. It does not, however, replace IAM or resource-policy decisions. Never confuse “not public” with “authorized.” They are not the same thing.
| Endpoint Type | Typical Services | Key Notes |
|---|---|---|
| Gateway endpoint | S3, DynamoDB | Uses route tables; endpoint policy can filter traffic |
| Interface endpoint | Many AWS services via PrivateLink | Uses ENIs, security groups, private DNS |
Endpoint policies apply only when traffic actually passes through that endpoint. They are an extra filter, not a universal replacement for IAM, bucket policy, or key policy. A useful gate, yes. The only gate? No.
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": "*", "Action": ["s3:GetObject","s3:ListBucket"], "Resource": [ "arn:aws:s3:::company-data-lake", "arn:aws:s3:::company-data-lake/*" ] } ] }
For S3 gateway endpoints, remember the route table association. For interface endpoints, remember security groups, private DNS, and service support. A private subnet by itself authorizes nothing. Not one thing.
Secrets Manager, Parameter Store, and programmatic access
Secrets Manager is usually the right answer when the requirement includes rotation, lifecycle management, or centralized credential handling. Parameter Store SecureString can also store sensitive values and uses KMS, but it is broader configuration tooling. If the exam leans hard on rotation and secret lifecycle, Secrets Manager is the likely choice.
Cross-account secret access may require both a Secrets Manager resource policy and KMS permissions if the secret uses a customer managed KMS key. Parameter Store SecureString may also require KMS permissions for decryption. So, again, the secret layer is not the whole story.
For workloads, do not embed access keys in AMIs, user data, source code, or environment variables. Use roles. The short version. The correct version. For external CI/CD systems, a modern pattern is OIDC federation into AWS with AssumeRoleWithWebIdentity, so the pipeline gets temporary credentials without storing AWS keys. Much better. Much safer. Much less to clean up later.
Audit, validation, and denied-access troubleshooting
Designing access is only half the job. You also need to validate it—and troubleshoot it when the inevitable denial appears.
Use CloudTrail to inspect the failed API call, principal ARN, error code, region, and request context. Use IAM Policy Simulator to test identity-based permissions. Use IAM Access Analyzer to detect unintended external or public access and to help refine policies. Use Organizations to see whether an SCP is blocking the action. For multi-account governance, organization trails and Config aggregators are the scalable pattern. Boring? Maybe. Essential? Absolutely.
| Symptom | Likely Cause | Check |
|---|---|---|
| AssumeRole denied | Broken trust policy or SCP deny | Trust policy, principal ARN, SCPs |
| S3 AccessDenied | Missing IAM allow, bucket policy deny, endpoint restriction | IAM, bucket policy, VPC endpoint path |
| KMS decrypt denied | Key policy or grant issue | KMS key policy, grants, region |
| Secret retrieval denied | Missing secret resource policy or KMS access | Secrets Manager policy, KMS permissions |
Access Advisor is helpful, but be precise: it shows service last accessed information, not action-level usage. For finer-grained policy refinement, use CloudTrail-based analysis and Access Analyzer policy generation features where they fit. Close enough is not the same as correct.
Secure privileged access and break-glass design
Root user protection is mandatory. Enable MFA. Do not create root access keys. Store recovery procedures securely. Configure alternate contacts. Alert on root usage with CloudTrail and EventBridge. Some account-level tasks still require root, so a break-glass role does not fully replace root access. It helps. It does not erase reality.
For privileged administration, prefer temporary elevation over standing admin access. That can mean short session durations, approval-based role assumption, or controlled IAM Identity Center assignments. Separate audit roles from admin roles, and centralize logs in a security-controlled account where appropriate. Standing privilege is convenient… right up until it isn’t.
If you use conditions, be careful. aws:MultiFactorAuthPresent is useful in some IAM-based patterns, but it is not a universal MFA control for all federated sign-in models. For workforce access through Identity Center, MFA is typically enforced at the Identity Center or external identity provider layer. Likewise, aws:SourceIp can help, but it is only one tool and may be unreliable behind NAT, proxies, or service-to-service paths.
Compact labs and implementation drills
Lab 1: Cross-account read-only audit access
Create an audit role in a workload account, trust a security-account role, attach least-privilege read permissions, assume the role, and confirm the API calls in CloudTrail. If it fails, check trust policy, source identity policy, and SCPs.
Lab 2: EC2 reads S3 and Secrets Manager without access keys
Attach an instance profile role, allow S3 read and secret read, add KMS permissions if needed, and test from the instance. If it fails, verify the role attachment, instance metadata service availability, bucket policy, and KMS authorization.
Lab 3: Restrict S3 access through a VPC endpoint
Create an S3 gateway endpoint, update route tables, attach an endpoint policy, add a bucket policy using aws:SourceVpce, and test both allowed and denied paths.
SAA-C03 exam traps, clues, and final cram sheet
Most tested traps
- SCPs do not grant permissions.
- Trust policy decides who can assume a role; permissions policy decides what the role can do.
- Private network path does not equal authorization.
- KMS is a separate authorization gate for encrypted resources.
- IAM users and long-term access keys are rarely the best answer.
2-minute answer strategy
- Identify the actor: workforce, workload, or app user.
- Check the boundary: same account or cross-account.
- Ask whether direct resource trust is needed.
- Check whether encryption adds a KMS gate.
- Prefer temporary credentials, least privilege, and centralized governance.
Quick scenarios
- Employees need access to many AWS accounts with MFA: IAM Identity Center.
- Lambda needs S3 and Secrets Manager access: execution role, not access keys.
- Another account must invoke your Lambda directly: Lambda resource policy.
- Access to encrypted S3 objects fails even though S3 policy looks correct: check KMS.
Last-day review: Workforce = Identity Center. Workloads = roles. App users = Cognito. Guardrails = SCPs. Encryption gate = KMS. Private path does not equal permission. Explicit deny always wins.