SaveMyCert
Log in
5 of 5 free questions left today·for 30 a day
Identity and Access Management

IAM Policy Evaluation and Authorization Strategies on AWS

30 min readSCS-C03 · Identity and Access ManagementUpdated

IAM policy evaluation follows a fixed order: an explicit deny anywhere ends the request; otherwise the request must survive every applicable gate — service control policies, resource control policies, resource-based policies, identity-based policies, permissions boundaries, and session policies — with everything outside an explicit allow denied by default. Task 4.2 of the SCS-C03 exam tests whether you can trace a request through that chain, explain why an action works same-account but fails cross-account, write Deny exceptions and organization perimeters that do not break AWS services, delegate IAM safely with boundaries and paths, design ABAC and RBAC, and authorize applications and outside workloads with Amazon Verified Permissions and IAM Roles Anywhere. It also covers how to find unintended access with IAM Access Analyzer and how to diagnose AccessDenied with the policy simulator, CloudTrail, and decoded messages. Issuing credentials is Task 4.1's job; here we decide what an authenticated principal may do.

What you’ll learn
  • Trace any request through the IAM policy evaluation chain, applying explicit denies, SCPs, RCPs, resource policies, boundaries, and session policies in order
  • Explain why same-account access needs only one allow while cross-account access must be allowed on both sides, including KMS keys and role trust policies
  • Apply condition operators, set operators, and security condition keys correctly — including Deny exceptions on aws:PrincipalArn and perimeters that exempt AWS service principals
  • Design delegated administration with permissions boundaries, IAM paths, and scoped iam:PassRole that delegates cannot escape
  • Design ABAC with principal, resource, and request tags, and judge when RBAC is the better fit
  • Choose authorization controls for applications and outside workloads with Amazon Verified Permissions and IAM Roles Anywhere
  • Find and prevent unintended access with IAM Access Analyzer findings, archive rules, and custom policy checks
  • Diagnose AccessDenied errors using CloudTrail messages, the IAM Policy Simulator (and its limits), and sts:DecodeAuthorizationMessage

The policy evaluation chain: every gate a request must pass

Every AWS request starts from implicit deny: nothing is permitted until some policy explicitly allows it. Evaluation then applies a fixed sequence, and internalizing it is the single highest-value preparation for this exam.

  1. Explicit deny check. AWS evaluates all applicable policies of every type first; a "Effect": "Deny" statement matching the request in any of them ends evaluation immediately. No allow anywhere can override an explicit deny.
  2. Service control policies. If the account belongs to an AWS Organization, the SCPs inherited along its OU path must allow the action. SCPs bound what identities in the account — including the root user of a member account — can ever do.
  3. Resource control policies. RCPs are the resource-side counterpart to SCPs: organization-managed ceilings on what can be done to supported resources (such as S3 buckets and KMS keys) in member accounts, regardless of who calls.
  4. Resource-based policy. If the target resource has one, it is evaluated next — and in the same account, its allow can be sufficient by itself.
  5. Identity-based policies. The policies attached to the calling user, group, or role.
  6. Permissions boundary. If the identity has one, the action must also be allowed inside the boundary.
  7. Session policy. If one was passed when the session was created, the action must also fall inside it.

The unifying rule: SCPs, RCPs, permissions boundaries, and session policies never grant anything. They are filters — ceilings the effective permission set is intersected with. Only identity-based and resource-based policies actually grant. When an exam option claims an SCP or boundary "gives" access, it is wrong by definition.

The five policy types and when to use each

Choosing the right policy type for a control objective is a recurring exam pattern. The capability matrix:

Policy typeAttached toCan grant?Primary use
Identity-basedUser, group, or roleYesDay-to-day permissions for principals
Resource-basedThe resource (bucket, key, queue, topic, secret, table, role trust)Yes — including to other accountsCross-account access; per-resource control that travels with the resource
Permissions boundaryUser or roleNo — ceiling onlyCapping what delegated admins can grant
SCP / RCPOrganization root, OU, or accountNo — ceiling onlyOrganization-wide guardrails on principals (SCP) or resources (RCP)
Session policyPassed at AssumeRole, federation, or Roles Anywhere session timeNo — narrows the sessionHanding a broad role to a caller in scoped-down form

Not every service supports resource-based policies. S3, KMS, Lambda, SQS, SNS, Secrets Manager, API Gateway, DynamoDB (tables and streams), and IAM role trust policies are the ones the exam leans on. EC2 instances have none, so cross-account access to them always routes through role assumption.

A session policy is the per-session ceiling: the effective permissions of the session are the intersection of the role's identity policies, its boundary (if any), and the session policy. One broad role can therefore serve many callers, each receiving a narrower session. The permissions boundary is the per-identity ceiling and earns its place through delegated administration — letting teams create their own roles that can never exceed an approved maximum. The full pattern, including the denies that stop delegates from removing the boundary, has its own section below.

Same-account OR versus cross-account AND

This distinction decides more exam questions than any other single fact. Within one account, an identity-based allow or a resource-based allow is sufficient (assuming no deny and no failed ceiling). If the bucket policy on my-bucket allows a same-account role to call s3:GetObject, the role needs no S3 permissions in its own identity policy at all — and vice versa. The two grant paths are evaluated as a union.

Across accounts, the logic flips to an intersection: the resource's account must allow the access in a resource-based policy, and the caller's account must allow it in the caller's identity-based policy. Trust must exist on both sides of the account boundary. When something "works from the same account but fails cross-account," the cause is almost always a missing grant on one side.

Refinements that matter at Specialty level:

  • Account principal versus a named principal. A resource policy that grants to another account's account principal (arn:aws:iam::111122223333:root) delegates to the whole account — individual principals there still need identity-policy allows, which that account's admins control.
  • Which ARN a same-account grant names changes how ceilings apply. A resource policy that grants to a role ARN is still limited by an implicit deny in that role's permissions boundary or session policy. A grant that names an IAM user ARN or a specific assumed-role session ARN is not limited by an implicit deny in the boundary or session policy — the resource-policy allow stands on its own. Explicit denies still win in every case.
  • Resource policy or role assumption. Where a service has no resource policy, the caller assumes a role inside the resource's account and everything becomes a same-account evaluation. Where it has one, either works. DynamoDB now supports resource-based policies: a table's policy covers the table and its indexes, while a stream takes its own resource-based policy. Cross-account access still needs the caller's identity policy as well.

Role trust policies are the resource-based policy of a role: they decide who may call sts:AssumeRole on it. For a cross-account assumption, the trust policy in the role's account must name the caller (or the caller's account), and the caller's identity policy in its own account must allow sts:AssumeRole on that role ARN. One subtlety: when a trust policy's Principal names a specific role ARN, IAM binds it to that role's unique ID. If the role is deleted and re-created with the same name, the trust no longer matches, and the policy displays the old unique ID (beginning AROA) instead of the ARN. To trust a role by name, name the account principal and add a condition such as ArnEquals on aws:PrincipalArn for the role ARN.

A full evaluation trace, gate by gate

Walk one request through every gate. A data analyst assumes the role AnalystRole in account 111122223333, which carries a permissions boundary allowing only s3:* and athena:*. The role's identity policy allows s3:GetObject on arn:aws:s3:::finance-data/*. The automation that created the session passed a session policy allowing s3:GetObject only on finance-data/reports/*. The account sits in an OU whose SCP allows all actions except a deny on S3 outside approved Regions. The bucket finance-data is in the same account, with a bucket policy that denies requests where aws:SecureTransport is false. The analyst requests s3:GetObject on finance-data/reports/q3.csv over HTTPS.

  1. Explicit denies: the bucket policy's SecureTransport deny does not match (the request is HTTPS); the SCP's Region deny does not match (the bucket is in an approved Region). No deny fires.
  2. SCP: allows the action. Pass.
  3. Resource-based policy: the bucket policy grants nothing here — it only denies. No grant from this gate, but no block either.
  4. Identity-based policy: allows s3:GetObject on finance-data/*, which covers the key. Grant found.
  5. Permissions boundary: s3:* covers the action. Pass.
  6. Session policy: reports/* covers the key. Pass.

Result: allowed. Now mutate it: request finance-data/raw/dump.csv and the session policy gate fails — AccessDenied even though the identity policy and boundary both allow it. Request over plain HTTP and the explicit deny ends evaluation before any allow is considered. Move the caller to another account and the bucket policy's lack of a grant becomes fatal, because cross-account requires both sides. One scenario, four different outcomes — this is exactly how the exam varies a single stem.

Policy language pitfalls: Not-elements and condition operators

The policy grammar's sharp edges are exam currency. NotAction with Allow grants everything except the listed actions — far broader than intended if you meant "allow these few things"; its safe use is inside a Deny (deny everything except approved actions) or paired with conditions. NotResource carries the same inversion risk. NotPrincipal is covered in the Deny-exceptions section next.

Operators decide whether a wildcard is a wildcard.

OperatorWildcards?Behaviour
StringEqualsNoExact, case-sensitive match; * is a literal character
StringEqualsIgnoreCaseNoExact match that ignores case; still literal
StringLikeYes (*, ?)Pattern match, case-sensitive; an over-broad pattern (for example a bare * in an account or ARN position of a trust or bucket policy) widens access and is itself a finding
ArnEquals / ArnLikeYes, bothThe two behave identically: ARN match, segment by segment, with wildcards accepted by either
Null—Tests whether a key is present ("true" = absent)
...IfExists suffix—Treats an absent key as a match

The Null operator tests key presence: "Null": {"aws:TokenIssueTime": "true"} matches requests made with long-term credentials, because temporary-credential requests carry that key.

How a condition block combines. Several values listed for one key are ORed — the condition matches if the request matches any of them. Several keys under one operator, and several operators in one block, are ANDed — all must match. Negated operators (StringNotEquals, ArnNotLike) with several values match only when the request value matches none of them, and a negated operator evaluates to true when the key is absent from the request. That last rule is behind several perimeter mistakes covered later.

IfExists for multi-resource actions. Some actions are authorized against several resource types in one call. ec2:RunInstances is checked against the instance, image, subnet, security group, network interface, and volume ARNs; ec2:InstanceType exists only in the context for the instance resource. A StringEquals on ec2:InstanceType in an Allow covering all those resources therefore fails for every non-instance resource and the launch is denied. Use StringEqualsIfExists, or put the condition in a statement scoped to instance/* only.

The multivalue trap. When a context key carries multiple values (like aws:TagKeys or aws:PrincipalOrgPaths), use set operators. ForAnyValue matches if at least one value matches; ForAllValues matches if every value matches — and it matches vacuously when the key is absent or empty. AWS warns against ForAllValues in an Allow for exactly this reason: a request that omits the key sails through. If the intent is "the key must be present and every value approved," pair ForAllValues with a Null check. Set operators belong only on multivalued keys; on single-valued keys they can match more than intended, and IAM Access Analyzer policy validation flags that usage.

Writing Deny exceptions: aws:PrincipalArn instead of NotPrincipal

The reliable way to write "deny everyone except these principals" is a Deny with "Principal": "*" and a condition on aws:PrincipalArn — not NotPrincipal. The difference comes from how sessions are identified.

  • A role is never the caller; a role session is. Its principal is the assumed-role session ARN, arn:aws:sts::111122223333:assumed-role/AppRole/session-name. A NotPrincipal exception that lists only the role ARN does not match the session, so the session is denied. Exempting a role with NotPrincipal means listing the session ARN (whose session name changes) and the account — brittle by design. Worse, a Deny with NotPrincipal always denies any IAM user or role that has a permissions boundary, whatever NotPrincipal lists, so the exception silently fails for bounded principals.
  • aws:PrincipalArn resolves to the role ARN (including its path) for any session of that role, so one condition covers every session.
  • aws:userid carries the role's unique ID plus the session name (AROAEXAMPLEID:session-name). It is useful for matching a specific session or tolerating re-creation; it is not a substitute for the ARN.

Several exceptions belong in one statement. Put every exempted principal as multiple values of one ArnNotEquals or ArnNotLike on aws:PrincipalArn. Because a negated operator matches only when the value matches none of the list, the statement denies everyone except the listed principals. Splitting the exceptions into two separate Deny statements breaks it: the statement that exempts principal A still denies principal B, and the one that exempts B denies A — so both end up denied.

"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": ["arn:aws:s3:::payroll", "arn:aws:s3:::payroll/*"],
"Condition": {"ArnNotLike": {"aws:PrincipalArn": [
  "arn:aws:iam::111122223333:role/PayrollApp",
  "arn:aws:iam::111122223333:role/aws-reserved/sso.amazonaws.com/*AWSReservedSSO_PayrollAdmin_*"
]}}

IAM Identity Center roles. A permission set provisions a role in each assigned account with an ARN of the form arn:aws:iam::<account>:role/aws-reserved/sso.amazonaws.com/<Region>/AWSReservedSSO_<PermissionSetName>_<suffix>. Instances in us-east-1 have no Region segment. The suffix is generated and changes if the role is re-created, so match these roles with a trailing wildcard (and a wildcard before the role name if you need to cover both path shapes), as in the example above.

Condition keys every security engineer must know

A handful of global condition keys carry most of the security weight on this exam.

  • aws:PrincipalOrgID — in a resource policy, matches any IAM principal from your AWS Organization. "Allow access only from accounts in our organization" has this one answer, and it beats maintaining account-ID lists forever. It is absent when the caller is an AWS service principal.
  • aws:PrincipalOrgPaths — the OU-level version, for "only principals from accounts under this OU."
  • aws:SourceArn / aws:SourceAccount / aws:SourceOrgID — the confused-deputy defense for service principals. When SNS, S3, or EventBridge invokes your Lambda function or writes to your bucket, the service acts as a deputy; without these conditions, anyone who can point that service at your resource inherits its access. Conditioning on the expected source resource, account, or organization pins the deputy to resources you own. aws:SourceOrgID (and the multivalued aws:SourceOrgPaths) scales this to every account in the organization without listing them.
  • aws:SourceIp and aws:SourceVpce — network provenance. A Deny on a bucket whose aws:SourceVpce is not the approved VPC endpoint (or whose aws:SourceVpc is not the approved VPC) is the standard network-perimeter control for data stores. aws:SourceIp sees the caller's public IP and does not match traffic that reaches the service through a VPC endpoint.
  • aws:SecureTransport — deny when false to refuse plaintext HTTP.
  • aws:PrincipalTag / aws:ResourceTag / aws:RequestTag — the ABAC keys, covered in the ABAC section.
  • aws:PrincipalIsAWSService, aws:ViaAWSService, aws:CalledVia — tell a service principal's own call apart from a service acting with your credentials; the next section shows why that matters.

Read every exam condition twice for direction: a Deny with StringNotEquals on aws:PrincipalOrgID and an Allow with StringEquals achieve related but different postures — the deny blocks outsiders even when another statement would allow them.

Organization perimeters, service principals and the confused deputy

An organization perimeter must handle three kinds of caller differently: IAM principals in your organization, IAM principals outside it, and AWS service principals. Most perimeter bugs come from treating the third like the second.

Scoping to an OU. aws:PrincipalOrgPaths is multivalued, so it needs a set operator, and it needs StringLike with a trailing /* to include child OUs: "ForAnyValue:StringLike": {"aws:PrincipalOrgPaths": "o-a1b2c3d4e5/r-ab12/ou-ab12-11111111/*"}. StringEquals on the exact path matches only principals in that OU itself. The resource-side twin, aws:ResourceOrgID, belongs in identity policies or SCPs when the goal is "our principals may only reach resources owned by our organization."

The service-principal blind spot. A bucket policy that denies when StringNotEquals aws:PrincipalOrgID also denies CloudTrail, AWS Config, and other services writing to the bucket. A service principal carries no aws:PrincipalOrgID, and a negated operator on a missing key is true. Exempt service principals in the same Deny with "BoolIfExists": {"aws:PrincipalIsAWSService": "false"}, and scope the separate service grant with aws:SourceAccount or aws:SourceOrgID so the exemption cannot be borrowed by another customer's resources.

Confused deputy at organization scale. SCPs govern IAM principals in member accounts, not service principals, so they cannot enforce this. A resource control policy can. AWS documents an RCP that denies a service principal acting on behalf of a resource outside the organization:

"Effect": "Deny",
"Principal": "*",
"Action": ["s3:*", "sqs:*", "kms:*", "secretsmanager:*", "sts:*"],
"Resource": "*",
"Condition": {
  "StringNotEqualsIfExists": {"aws:SourceOrgID": "o-a1b2c3d4e5"},
  "Null": {"aws:SourceAccount": "false"},
  "Bool": {"aws:PrincipalIsAWSService": "true"}
}

Read it as three ANDed tests: the caller is a service principal, the request carries source context (so it is acting for a specific resource), and that resource is not in your organization.

Network denies and services acting for you. Several services call other services with the original caller's credentials — Athena reading S3, S3 calling KMS for SSE-KMS. Those downstream calls come from AWS networks, not the caller's IP, so a Deny on NotIpAddress aws:SourceIp breaks them. Add "Bool": {"aws:ViaAWSService": "false"} so the Deny applies only to direct calls.

KeyTrue whenTypical use
aws:PrincipalIsAWSServiceThe caller is a service principal (for example cloudtrail.amazonaws.com writing logs)Exempting or isolating service-principal calls in perimeters and RCPs
aws:ViaAWSServiceA service makes the request using an IAM principal's credentials on that principal's behalfKeeping network-based denies from blocking service-to-service calls
aws:CalledViaThe request passed through the listed service(s)Allowing an action only through a specific service, such as Athena

SCP and RCP ceilings as they appear in authorization failures

Organization policies cause failures that look identical to missing grants, so recognising their effect is part of troubleshooting. Designing them is Task 6.1; here the question is what they do to a given request.

  • Allow-list SCPs need an Allow at every level. When the default FullAWSAccess SCP is replaced by an allow-list, an action is permitted only if an SCP allows it at the root, at every OU on the path to the account, and on the account itself. A gap at any one level denies the action, even though the policy at the other levels allows it.
  • The management account is exempt. SCPs never restrict principals in the management account, and RCPs do not apply to its resources. A guardrail that "works everywhere except one account" usually points there. Service-linked roles are also not restricted by SCPs.
  • SCPs do not reach outsiders. An SCP only filters principals in member accounts of your organization. A principal in an external account calling your bucket is untouched by your SCPs. The organization-level control for outside callers is an RCP — for example a Deny unless aws:PrincipalOrgID equals your organization (with the service-principal exemption above).
  • Whose organization denied it? An SCP denial comes from the policies on the caller's account path; an RCP denial comes from the policies on the resource owner's account path. In a cross-account failure, check both organizations' policies accordingly.

Delegated administration: permissions boundaries and IAM paths

Delegated administration lets an application team create its own roles while nothing it creates can exceed an approved ceiling. A permissions boundary supplies the ceiling; conditions, denies, and IAM paths stop the team from escaping it. Each piece closes a specific gap.

  1. Require the boundary on creation. Allow iam:CreateRole only with "StringEquals": {"iam:PermissionsBoundary": "arn:aws:iam::111122223333:policy/TeamABoundary"}. Attach the same condition to the policy-management actions on the team's roles (AttachRolePolicy, PutRolePolicy and their detach/delete counterparts).
  2. Put the same condition on iam:PutRolePermissionsBoundary. That action replaces a boundary in one call. Without the condition, a delegate can swap the boundary for a broader policy without ever deleting it.
  3. Deny removing the boundary. Deny iam:DeleteRolePermissionsBoundary (and iam:DeleteUserPermissionsBoundary if users are delegated).
  4. Deny editing the boundary policy itself. Deny iam:CreatePolicyVersion, iam:SetDefaultPolicyVersion, iam:DeletePolicyVersion, and iam:DeletePolicy on the boundary policy's ARN. Otherwise a delegate could widen the ceiling for every role at once. Protect the delegate's own admin role and its policies the same way.
  5. Scope iam:PassRole to the team's own roles. The request context for PassRole has iam:PassedToService and iam:AssociatedResourceArn but not iam:PermissionsBoundary, so you cannot require a boundary there. Instead limit both CreateRole and PassRole to the same resource pattern — the team's IAM path — and restrict iam:PassedToService to the services the team uses. The team can then pass only roles it created, and every one of those carries the boundary.

IAM paths are the organising tool. A path is part of a role, user, or policy ARN: arn:aws:iam::111122223333:role/team-a/orders-api. Grant IAM administration and PassRole on role/team-a/* and a team can manage only roles it created under that path. Paths work cleanly in identity policies. In resource policies, two limits apply:

  • The Principal element does not accept partial wildcards, so you cannot write role/team-a/* there.
  • Assumed-role session ARNs omit the path (arn:aws:sts::111122223333:assumed-role/orders-api/session).

To match roles by path in a resource policy, use "Principal": "*" (or the account principal) with "ArnLike": {"aws:PrincipalArn": "arn:aws:iam::111122223333:role/team-a/*"} — aws:PrincipalArn carries the role's full ARN, path included.

ABAC versus RBAC: designing tag-based access

RBAC maps job functions to roles or permission sets; access changes mean editing policies or assignments. ABAC writes one policy that compares attributes at request time — the canonical pattern conditions on "StringEquals": {"aws:ResourceTag/project": "${aws:PrincipalTag/project}"}, so a principal tagged project=phoenix reaches only resources tagged project=phoenix. New project? Tag the people and the resources; no policy edits. ABAC scales where projects and teams are added quickly and role counts would otherwise explode. RBAC stays the better fit when permissions differ structurally per role rather than by attribute value, when services lack tag-based authorization support, and for coarse multi-account access through Identity Center permission sets. Real designs are hybrids: RBAC for the shape of access, ABAC for its scope.

Where each condition lives. The comparison of aws:ResourceTag with aws:PrincipalTag that controls access to resources belongs in the permissions policy (identity- or resource-based). A role's trust policy governs only who may assume the role and which session tags they may pass; a resource-tag condition there controls nothing about resource access.

Three tag keys, three jobs.

KeyDescribesUse it to
aws:PrincipalTag/<key>Tags on the calling principal or its sessionIdentify the caller's attribute
aws:ResourceTag/<key>Tags already on the existing resourceMatch the caller to resources it may touch
aws:RequestTag/<key>, aws:TagKeysTags being set in this requestControl which tags may be applied on create or tag calls

ABAC on creation. A resource being created has no tags yet, so aws:ResourceTag is absent and a condition on it cannot authorize the create. Use aws:RequestTag and aws:TagKeys to require the right tags in the request. In EC2, tagging during RunInstances also needs ec2:CreateTags; scope that permission with "StringEquals": {"ec2:CreateAction": "RunInstances"} so it can be used only as part of the launch, not to retag existing resources.

Tag governance is the security control. If users can retag resources or set their own session tags, they can rewrite their own access. Restrict tag-modification actions, and when a role accepts session tags, allow sts:TagSession in its trust policy only when each passed tag equals the caller's own attribute, for example "StringEquals": {"aws:RequestTag/project": "${aws:PrincipalTag/project}"}. A caller can then only carry its own project value into the session.

S3 Block Public Access in request evaluation

S3 Block Public Access (BPA) is four independent settings, configurable at the organization level (through AWS Organizations S3 policies applied to the root, an OU or an account), the account level, the bucket level and the access point level; the most restrictive combination applies. Two act on ACLs and two act on policies, and only some of them affect what already exists.

SettingWhat it doesEffect on existing configuration
BlockPublicAclsRejects requests that set a public ACL on a bucket or objectNone — existing ACLs stay as they are
IgnorePublicAclsS3 ignores all public ACLs on the bucket and its objectsYes — existing public ACLs stop granting access
BlockPublicPolicyRejects a bucket (or access point) policy that would grant public accessNone — an already-saved public policy is untouched
RestrictPublicBucketsIf the bucket's policy is public, only AWS service principals and authorized users in the bucket owner's account can access the bucketYes — it neutralises an existing public policy

So the ACL settings affect ACLs only, and when a bucket already has a public policy, only RestrictPublicBuckets changes what that policy grants. With modern buckets, ACLs are usually disabled by the bucket-owner-enforced Object Ownership setting, which leaves the bucket policy as the single authorization surface.

"Public" is decided for the whole policy. S3 judges the bucket policy as a unit: one statement that grants to "Principal": "*" without a restricting condition makes the entire policy public. With RestrictPublicBuckets on, access is then limited to the owner account and service principals — so legitimate cross-account grants elsewhere in the same policy stop working too. A policy is non-public when every grant is to fixed principals, or is conditioned on fixed values of keys such as aws:SourceArn, aws:SourceAccount, aws:SourceVpc, aws:SourceVpce, or a bounded set of aws:SourceIp ranges. Because RestrictPublicBuckets keys on whether the policy as a whole is public, restoring named grants is a matter of making every statement non-public; the setting itself is working as designed.

Access points give each consumer its own named endpoint and policy, so one busy bucket's policy does not become an unmaintainable wall of statements; BPA applies to access point policies too.

KMS authorization: key policies, grants and cross-account use

Every KMS key has a key policy, and it is the root of trust. Unlike other resources, IAM identity policies grant nothing for a key unless the key policy enables IAM. The default key policy's statement for the account principal (arn:aws:iam::111122223333:root) is what does that. A key whose policy omits that statement ignores your identity policies entirely. Only principals the key policy itself names — typically a key administrator allowed kms:PutKeyPolicy — can fix it. No IAM policy can, and creating a grant does not help either: kms:CreateGrant is itself an action the key policy must allow.

Grants are a second, independent authorization path. A grant gives a grantee principal specific operations on one key without editing the key policy, and AWS services use them to borrow your keys. Because grants authorize independently, an over-broad grant is unintended access even when the key policy looks tight. IAM Access Analyzer evaluates both the key policy and grants. Remove unwanted access with RevokeGrant (by an administrator) or RetireGrant (by the retiring principal or grantee).

Cross-account reads of SSE-KMS objects need three grants.

  1. The bucket policy (or an access point policy) allows the S3 action to the other account's principal.
  2. The key policy, in the key's account, allows that principal or its account kms:Decrypt.
  3. The caller's identity policy, in its own account, allows kms:Decrypt on the key ARN as well as the S3 action.

A bucket policy cannot grant KMS actions — it authorizes only S3 actions on S3 resources — so "the bucket policy allows it but GetObject fails" with an SSE-KMS object points at the key.

Restricting how a key is used. kms:ViaService limits key use to requests a named service makes on the principal's behalf (for example s3.us-east-1.amazonaws.com), so the principal cannot call KMS directly. When S3 uses SSE-KMS, it calls KMS with the caller's identity, so aws:PrincipalIsAWSService is false for that call — a condition requiring it to be true would deny every read. Encryption context is supplied by the caller and proves nothing about who the caller is; use it for auditing and binding ciphertext to a purpose, not as an identity check. Key management depth (rotation, multi-Region keys, custom key stores) belongs to Domain 5.

Application authorization with Amazon Verified Permissions

Amazon Verified Permissions is a managed authorization service for your own applications' permissions — "can this user view this document?" — written in the Cedar policy language. IAM still authorizes calls to AWS APIs; Verified Permissions answers decisions inside your application, so authorization logic leaves the application code.

  • Policy store. The container for an application's Cedar policies, its schema (entity types, attributes, and actions, used to validate policies), and optionally an identity source.
  • Evaluation rules. Cedar is default deny: without a matching permit, the answer is Deny. A matching forbid overrides any permit, so forbid policies are the guardrails.
  • Policy templates. A template contains placeholders (?principal, ?resource); a template-linked policy fills them in, for example when a user shares a document. Editing the template changes every linked policy at once, which is how you change "what sharing means" without rewriting thousands of policies.

Two ways to ask for a decision.

APIWho supplies the principalFits when
IsAuthorizedThe application passes the principal, action, resource, context, and any entity dataThe app already has the user and attributes, or principals are not from a token
IsAuthorizedWithTokenVerified Permissions derives the principal and its attributes from an ID or access token issued by the identity source (an Amazon Cognito user pool or an OIDC provider)Users sign in through Cognito or OIDC and you want token claims used directly

Batch variants exist for checking many decisions at once. Each response gives the decision and its determining policies: on Allow, the satisfied permits; on a Deny caused by a forbid, the satisfied forbid policies; on a default Deny (nothing permitted), the list is empty. That empty list is how you tell "a guardrail blocked this" from "nothing granted this."

IAM Roles Anywhere authorization controls

IAM Roles Anywhere lets workloads outside AWS exchange an X.509 certificate for temporary role credentials (the authentication side is in Task 4.1). Authorization is controlled in three places, and the exam tests what each can and cannot restrict.

  • Trust anchor — the certificate authority whose certificates are accepted. It decides whether a certificate is trusted, not which role it may use.
  • Profile — lists the roles that sessions may assume and can attach managed policy ARNs and an inline policy. Those policies act as session policies: the session's permissions are the intersection of the role's policies and the profile's policies. An action the role allows but the profile policies do not is denied with an AccessDenied that says no session policy allows the action.
  • Role trust policy — trusts rolesanywhere.amazonaws.com for sts:AssumeRole, sts:TagSession, and sts:SetSourceIdentity, and is where per-workload restrictions belong.

Why the trust policy carries the workload separation. The client chooses the profile and role when it calls CreateSession, so any certificate from the trust anchor can ask for any role in any profile it is allowed to use. To keep the billing service's certificate out of the reporting service's role, use separate roles whose trust policies test certificate attributes. Roles Anywhere maps the certificate's subject, issuer, and subject alternative names into session tags such as aws:PrincipalTag/x509Subject/CN, and sets aws:SourceArn to the trust anchor ARN.

"Condition": {
  "StringLike": {"aws:PrincipalTag/x509Subject/CN": "billing-*"},
  "ArnEquals": {"aws:SourceArn": "arn:aws:rolesanywhere:eu-west-1:111122223333:trust-anchor/EXAMPLE-ID"}
}

Use StringEquals for an exact common name and StringLike for a prefix. The issuer CN is the same for every certificate the CA issues, so it can pin the CA but cannot tell one workload from another.

IAM Access Analyzer: finding and preventing unintended access

IAM Access Analyzer finds unintended permissions after they exist and stops them before they deploy. Its jobs split into analyzers that produce findings and on-demand policy checks.

CapabilityQuestion it answers
External access analyzerWhich resources are shared outside the zone of trust (the account or the organization)?
Internal access analyzerWhich principals inside the organization or account can reach selected critical resources — S3 buckets and directory buckets, DynamoDB tables and streams, RDS DB and cluster snapshots? It combines identity policies, resource policies, SCPs, RCPs, and permissions boundaries.
Unused access analyzerWhich roles, access keys, passwords, and permissions have gone unused over a tracking period?
Policy generationWhat least-privilege policy matches the actions CloudTrail recorded for a role?
Policy validation (ValidatePolicy)Does this policy have grammar errors, security warnings, or best-practice problems?
Custom policy checksDoes this policy meet my security standard?

Custom policy checks run in CI/CD pipelines and return pass or fail:

  • CheckAccessNotGranted — fails if the policy allows any of the named actions (or access to named resources). Use it for "no policy may grant iam:PassRole or s3:DeleteBucket."
  • CheckNoNewAccess — compares the new policy with a reference policy (often the currently deployed version) and fails only if it grants something the reference does not. Access that already existed in the reference passes, so it catches widening, not existing over-permission.
  • CheckNoPublicAccess — fails if a resource policy for a given resource type would allow public access.

Archive rules keep external access findings meaningful. When sharing is intended — a known partner account, a resource type meant to be shared — an archive rule filters on finding attributes such as the principal's account or the resource type and archives matching findings automatically, current and future. New, unexpected sharing still raises an active finding. For KMS keys, remember the analyzer reports access through grants as well as the key policy.

Least-privilege tooling and the AccessDenied triage tree

Least privilege is a process: generate or write a policy, check it before deployment, then prune what goes unused. When a request fails, use tools that explain a denial — not inventory tools. The IAM credential report (credential status per user), last-accessed information (when services and actions were last used), and Access Analyzer policy validation (grammar and best practice) are useful for cleanup but neither record nor explain a live denial.

The IAM Policy Simulator tests "would this principal be allowed to do X?" without making real calls. It evaluates identity policies, permissions boundaries, SCPs (it reports a denial by Organizations without showing the matching statement), and resource policies you supply. It does not evaluate RCPs, and its results can differ from live behaviour for VPC endpoint policies and role chaining. A simulator "allowed" is therefore not proof that the live request will succeed.

When AccessDenied strikes, triage in order rather than guessing:

  1. Identify the triple. Which principal, which action, which resource? The CloudTrail event's errorCode and message usually name all three.
  2. Read the message. Many services now name the policy type: "with an explicit deny in a service control policy," "with an explicit deny in a resource control policy," "because no identity-based policy allows," "because no session policy allows," or "because no VPC endpoint policy allows" (the endpoint's policy, not the bucket or role, is missing the grant). Some messages include the ARN of the denying policy. An SCP denial points to the caller's account path; an RCP denial points to the resource owner's.
  3. Hunt explicit denies across every layer: SCPs and RCPs, the resource policy, boundaries, identity policies.
  4. Same account? Verify at least one side — the identity policy or the resource policy — grants the action. Cross-account? Verify both sides grant — and for SSE-KMS, the key policy and caller's identity policy too.
  5. Check the ceilings: a permissions boundary or session policy (including a Roles Anywhere profile policy) missing the action is the classic "the policy clearly allows it" mystery.
  6. Check conditions: wrong VPC endpoint, missing tag or MFA context, HTTP instead of HTTPS, a condition key absent for this resource type.
  7. Decode when offered. EC2 and some other services return UnauthorizedOperation with an encoded authorization failure message. sts:DecodeAuthorizationMessage reveals the evaluated context — and the person decoding needs that permission.

Then reproduce the decision in the policy simulator (remembering what it cannot see) before you change a policy.

Tip. This task is tested through evaluation scenarios: a stem hands you an SCP or RCP, a resource policy, an identity policy, and a boundary or session policy, then asks why a request failed or which change fixes it. Expect near-twin choices between condition operators (StringEquals versus StringLike, ForAllValues versus ForAnyValue, IfExists), between NotPrincipal and aws:PrincipalArn Deny exceptions, and between the four Block Public Access settings. Perimeter questions test what happens to AWS service principals, delegation questions test whether a boundary can be removed or bypassed with PassRole, and newer services appear as design choices: Verified Permissions for application authorization, Roles Anywhere trust-policy conditions, and Access Analyzer internal access findings and custom policy checks. SCPs, RCPs, boundaries, and session policies never grant, and an explicit deny beats every allow.

Key takeaways
  • Evaluation order: explicit deny first, then the organization ceilings, resource-based and identity-based grants, permissions boundaries, and session policies — with implicit deny for anything never allowed.
  • Only identity-based and resource-based policies grant access; SCPs, RCPs, boundaries, and session policies are ceilings that can only take away.
  • Same-account access needs one allow; cross-account access needs both the resource policy and the caller's identity policy — and for SSE-KMS, the key policy and the caller's kms:Decrypt permission too.
  • Exempt principals from a Deny with one statement listing them all under ArnNotLike on aws:PrincipalArn; NotPrincipal misses assumed-role sessions.
  • Perimeter Denies on aws:PrincipalOrgID also hit service principals unless you exempt aws:PrincipalIsAWSService; network Denies need aws:ViaAWSService false; organization-wide confused-deputy protection is an RCP on aws:SourceOrgID.
  • Delegated administration needs the iam:PermissionsBoundary condition on CreateRole and PutRolePermissionsBoundary, denies on removing or editing the boundary, and PassRole limited to the team's IAM path.
  • ForAllValues matches vacuously when the key is absent — pair it with a Null check; use IfExists for keys present on only some resource types of an action.
  • RestrictPublicBuckets is the only Block Public Access setting that changes what an existing public bucket policy grants; one unconditioned Principal * statement makes the whole policy public.
  • Verified Permissions authorizes inside your applications with Cedar (default deny, forbid wins); Roles Anywhere separates workloads through trust-policy conditions on certificate attributes.
  • The IAM Policy Simulator evaluates SCPs but not RCPs; Access Analyzer custom policy checks block risky policies before deployment.

Frequently asked questions

What is the IAM policy evaluation order?

AWS first checks every applicable policy for an explicit deny, which ends evaluation immediately. The request must then be allowed by the organization's SCPs and RCPs where they apply, and by at least one granting policy — resource-based or identity-based — and must also fall inside the permissions boundary and session policy if either exists. Anything not explicitly allowed is implicitly denied.

Does an explicit deny always override an allow in AWS?

Yes. An explicit deny in any applicable policy — identity-based, resource-based, SCP, RCP, permissions boundary, or session policy — overrides every allow, with no exceptions. That is what makes deny statements the right tool for guardrails: a deny with a condition, such as denying requests where aws:SecureTransport is false, holds no matter what other policies grant.

Why does cross-account access fail when the same policy works within one account?

Within one account, an allow from either the identity-based policy or the resource-based policy is sufficient. Across accounts, both are required: the resource owner must grant the caller (or its account) in a resource-based policy, and the caller's own identity policy must allow the action. If access works same-account but fails cross-account, one side — usually the resource policy — is missing its grant.

What is a permissions boundary and when should you use one?

A permissions boundary is a managed policy attached to a user or role that sets the maximum permissions that identity can have — its effective access is the intersection of the boundary and its identity policies, and the boundary itself grants nothing. Use it to delegate IAM administration safely: allow CreateRole and PutRolePermissionsBoundary only with the iam:PermissionsBoundary condition naming the approved boundary, deny deleting the boundary or editing the boundary policy, and limit iam:PassRole to roles under the team's IAM path.

What is the difference between ABAC and RBAC in AWS?

RBAC grants access through roles or permission sets mapped to job functions, so changing access means changing policies or assignments. ABAC grants access through attributes — typically a condition matching aws:PrincipalTag against aws:ResourceTag — so one policy scales across projects and new access is granted by tagging. ABAC suits large, dynamic environments with strong tag governance; RBAC suits structurally distinct job functions and services without tag-based authorization.

What does S3 Block Public Access RestrictPublicBuckets do?

RestrictPublicBuckets limits access to a bucket whose policy is public to AWS service principals and authorized users in the bucket owner's account. It is the only Block Public Access setting that changes what an already-saved public policy grants: BlockPublicPolicy only rejects new public policies, and the two ACL settings affect ACLs only. Because S3 judges the whole policy, one unconditioned Principal * statement makes it public, and named cross-account grants in the same policy then stop working too.

When should I use Amazon Verified Permissions instead of IAM?

Use IAM to authorize calls to AWS APIs and Amazon Verified Permissions to authorize decisions inside your own application, such as whether a user may view or share a record. Verified Permissions stores Cedar policies and a schema in a policy store, evaluates them with default deny and forbid-overrides-permit, and answers IsAuthorized (application-supplied principal) or IsAuthorizedWithToken (principal from a Cognito or OIDC token) requests.

How do I troubleshoot an AccessDenied error in AWS?

Pin down the exact principal, action, and resource from the CloudTrail event and read the message, which often names the policy type that denied — for example a service control policy, a resource control policy, a session policy, or a VPC endpoint policy. Hunt explicit denies, confirm both sides grant for cross-account calls, and check boundaries, session policies, and conditions. Decode EC2 UnauthorizedOperation messages with sts:DecodeAuthorizationMessage, and remember that the IAM Policy Simulator evaluates SCPs but not RCPs.

Source

This lesson covers the "Identity and Access Management" domain of the official SCS-C03 exam guide. Vendors revise their guides — check the source for the current version.

Test yourself on this topic
Practice now

Sign up free to mark lessons complete, bookmark topics and track your exam readiness.

Spotted a mistake in this lesson?