Designing Secure Access to AWS Resources for AWS Certified Solutions Architect (SAA-C03)
Here’s a version that feels a little more natural, with less of that stiff, textbook rhythm. I kept the meaning the same, just made the wording a bit looser and more conversational. --- ### Rewritten version
For SAA-C03, secure access isn’t just “an IAM thing.” It’s an architecture problem wearing an IAM costume. The exam keeps circling the same deeper questions: who’s actually making the call, how did they prove they are who they say they are, what gives them permission, which route is the request taking, is anything being encrypted, and what other layer might still slam the door shut.
I break this into four questions, because otherwise it all blurs together. Authentication: who is this, really? Authorization: what are they allowed to do? Network controls: from where, and along which path, is the request permitted? Audit: what happened after the fact, and who actually touched it? In AWS, those answers get spread all over the place — IAM, STS, Organizations, S3 policies, KMS key policies, VPC endpoint policies, CloudTrail, Config, and Access Analyzer. Honestly, it can feel a bit like a treasure hunt.
The best exam habit is almost annoyingly simple: whenever you spot an Allow, immediately ask, “Okay, but what could still kill this request?” Because in AWS, deny is the default mood. Any explicit deny in an applicable policy wins. And even a valid allow still has to get past the guardrails—SCPs, permission boundaries, session policies, resource policies where they apply, endpoint policies, and KMS authorization if encryption enters the picture.
One shortcut is worth keeping in your back pocket:
- Employees or admins → IAM Identity Center or federation
- Applications running on AWS → IAM roles and temporary credentials
- Customers of a mobile/web app → Cognito or application-layer identity
- Cross-account temporary access → STS AssumeRole
- Encrypted S3 objects or secrets → service permission plus KMS permission
IAM users are long-lived identities that live inside an AWS account. They still show up in real life for awkward legacy setups or tightly controlled break-glass access, but they’re usually not what you want for a new design. Roles, though—that’s the workhorse. Assumable, temporary, STS-backed. Good for workloads, federation, cross-account access. The sane default, basically.
The key distinction is easy to say and easy to mix up: a trust policy says who can assume the role, while a permissions policy says what the role can do once it has been assumed. People trip over this constantly. Very exam-friendly trap.
If a role trust policy names arn:aws:iam::111122223333:root, that does not mean the root user and only the root user. It means principals in that account may be trusted, assuming their own permissions and any trust conditions line up. Subtle. Sneaky, even.
AWS authorization isn’t one neat straight-line process, but the exam wants you to think like it is. Start with implicit deny. Then layer in the policies that matter. Identity-based and resource-based policies may combine, depending on the service. Permission boundaries, session policies, and SCPs act like ceilings. Endpoint policies are filters on top of that. KMS runs in its own lane whenever a KMS key shows up. And explicit deny? End of story.
So yeah—“IAM allows it” is never the full sentence. Just one piece of the mess.
AWS STS hands out temporary credentials. The APIs worth keeping close are AssumeRole, AssumeRoleWithSAML, and AssumeRoleWithWebIdentity. Workforce federation usually leans on SAML or IAM Identity Center. Web identity setups usually mean OIDC. EKS IRSA is the classic example here; newer Pod Identity options exist too, but IRSA is still exam candy.
AWS Single Sign-On got renamed AWS IAM Identity Center. It’s built for workforce access across AWS accounts and business apps—not for customer login on a public-facing app. In a multi-account setup, Identity Center plus Organizations is usually the cleanest way to do workforce access: one identity source, permission sets, account assignments. No IAM-user-sprawl circus.
Trust policies get much better when you tighten them with conditions:
aws:MultiFactorAuthPresentfor MFA-gated admin role assumptionsts:ExternalIdfor third-party cross-account access to reduce confused deputy riskaws:PrincipalOrgIDto limit assumption to principals in your AWS Organizationaws:PrincipalArnto narrow trust to specific roles
Session controls matter too, and they’re easy to forget until they wreck your day. Roles have a maximum session duration. Role chaining often hits a 1-hour cap in common scenarios, which can be a nasty surprise in multi-hop access flows. Session policies can shrink what an assumed session can do. Session tags help with ABAC and traceability. If the question says access should scale by project or environment without multiplying roles, tags should start blinking in your head.
AWS Organizations gives you governance across accounts. Service Control Policies do not grant permissions. They only set the upper limit of what member accounts can do. If an SCP says no, even an admin in that account is stuck. No amount of confidence fixes that.
SCPs are guardrails, not scalpels. Good uses: blocking CloudTrail tampering, limiting risky services, enforcing region rules. But region-deny SCPs can get weird, because some services are global or lean on endpoints like us-east-1. In practice, exceptions often sneak in for IAM, Organizations, Route 53, CloudFront, Support... the usual suspects.
Permission boundaries are what you want when a team can create roles, but only up to a certain ceiling. That is a different beast from an SCP, which affects the whole account or OU. Easy to confuse in a hurry. Very easy.
Cross-account access usually has to work on both ends, which is where people get lazy and things break. The source principal needs permission to call sts:AssumeRole or otherwise reach the target resource. The target account has to trust that principal, or expose the resource through a resource policy. And if KMS is involved, well, that’s another gate entirely. Because of course it is.
The pattern I usually prefer is role assumption: principal in Account A assumes a role in Account B. Temporary, auditable, narrow. Clean. Resource-based policies are common too—S3, SNS, SQS, Lambda invoke permissions, KMS—but they answer a slightly different question: who gets to touch this specific resource?
ABAC belongs in this conversation too. With session tags and conditions like aws:PrincipalTag/Project matching aws:ResourceTag/Project, you can scale access without minting one role per team per environment. Which, thankfully, saves everyone from role explosion.
S3 gets tested constantly because it’s a collision point for IAM, bucket policies, access points, network restrictions, and encryption. The modern baseline is: use bucket policies and IAM policies, turn on S3 Block Public Access where it makes sense, and prefer Object Ownership = Bucket owner enforced so ACLs are out of the picture. Cleaner. Less ancient baggage.
Be precise about S3 Block Public Access. It’s an S3-specific safety net that blocks certain attempts to make buckets or objects public through ACLs or policies, depending on the settings. It’s not some magical universal IAM override. But for exam purposes, it absolutely matters as an anti-exposure control.
Access Points are handy when different teams or applications need different ways into the same bucket. VPC-restricted access points are especially useful when the data should stay private. Pre-signed URLs are the right move for short-lived access to a single object action; keep the expiration tight, and make sure the signing principal itself only has the permissions you intended. Otherwise… well, you know how that goes.
If you need SSE-KMS uploads enforced with a particular customer managed key, don’t stop at “encryption is required.” Also pin the KMS key ID. Otherwise the caller might encrypt with the wrong key and still technically satisfy the wrong rule. That’s exactly the kind of half-right answer the exam loves to sneak in.
One technical point that matters: if you want to restrict S3 access to a VPC endpoint, an Allow with aws:SourceVpce by itself is not a hard exclusive control when other permissions exist. Use an explicit deny for requests that do not come through the approved endpoint, and keep an eye out for AWS-service exceptions if they’re needed.
KMS is where a lot of “but IAM says yes” designs fall apart. When a resource uses a customer managed KMS key, effective access usually depends on both the service permission and KMS authorization. For customer managed keys, the key policy is the foundation. IAM alone doesn’t carry the whole load unless the key policy allows it directly or delegates to IAM in the account.
Know the key types:
- AWS owned keys: fully managed by AWS for a service
- AWS managed keys: created in your account for a service, limited customization
- Customer managed keys: full policy control, best for governance and cross-account design
KMS authorization can involve key policies, IAM policies, and grants. Grants are not some weird side alley; they’re a normal way AWS services and applications get tightly scoped cryptographic permissions. Conditions like kms:ViaService and kms:EncryptionContext help narrow the blast radius.
For workloads, roles are the secure default. EC2 uses instance profiles; lock down IMDS with IMDSv2. Lambda uses execution roles. ECS has a split that matters: the task role is for the application, while the task execution role handles pulling images and writing logs. For EKS, remember IRSA, and don’t be surprised if newer Pod Identity wording shows up.
Do not bake access keys into code, AMIs, or container images. Pull secrets dynamically. If rotation matters, Secrets Manager is usually the right answer. Parameter Store SecureString uses KMS encryption and works fine for configuration and simpler secret-ish values, but it does not give you the same native managed rotation story as Secrets Manager.
Private connectivity is a separate axis entirely. Gateway endpoints are only for S3 and DynamoDB. Interface endpoints cover a lot more through PrivateLink. Endpoint policies are just filters; they don’t grant access on their own. For interface endpoints, private DNS and endpoint security groups matter. For gateway endpoints, route table association matters. And “no NAT required” only applies to the AWS services that endpoint actually covers—external APIs and package repos still need a path out.
Match the API auth model to the caller. IAM authorization with SigV4 is the right fit for AWS-native service-to-service access. Cognito user pools fit customer-facing identity nicely, especially for REST APIs using Cognito authorizers. For HTTP APIs, JWT authorizers are a native pattern worth knowing. Lambda authorizers are your custom-tokens-and-special-cases escape hatch.
CloudTrail is the main audit trail for AWS API activity. Management events are logged by default; data events like S3 object-level access or some Lambda events need explicit setup and can add cost. In multi-account setups, organization trails are the clean default. I usually think of CloudTrail as the activity record, Config as the drift checker, Security Hub as the findings dashboard, GuardDuty as the threat detector, and IAM Access Analyzer as the tool for spotting public or external exposure in resource policies.
Access Analyzer is excellent at surfacing unintended reachability, but it’s not the thing you reach for when you’re trying to figure out why a live API call just failed. For failed access, the better move is a layered check, one piece at a time:
- Confirm the caller identity and whether the right role session was assumed
- Check the trust policy for
AssumeRolefailures - Check the identity policy for missing actions or bad ARNs
- Check SCPs, permission boundaries, and session policies for limiting denies
- Check resource policies such as S3 bucket, Lambda, SNS, or secret resource policy
- Check the endpoint policy if the request goes through a VPC endpoint
- Check the KMS key policy and
kms:Decryptwhen encryption is involved - Verify conditions such as MFA, tags, source VPC endpoint, source IP, or org ID
The IAM policy simulator helps, but it doesn’t fully model every effective authorization layer—trust relationships, SCP behavior in all contexts, endpoint policies, KMS key policies. Useful tool, not gospel.
Compact case study: an EC2 app in a private subnet reads an S3 object encrypted with a customer managed KMS key. The instance profile allows s3:GetObject. The bucket policy allows org principals and denies non-TLS. Traffic goes through an S3 gateway endpoint. The read still fails. So what’s left? Probably the gateway endpoint policy, plus KMS decrypt permission and the key policy. Classic SAA-C03 territory: service permission, network path, encryption plane. All three have to behave.
If you keep that layered model in your head, most SAA-C03 secure-access questions stop feeling like riddles. The exam is rarely fishing for a random feature name. It’s testing whether you can see how identity, policy, encryption, network path, and governance all lean on each other.
--- If you want, I can do a second pass and make the whole piece sound even more conversational and less “study guide,” while keeping all the AWS accuracy intact.