Asterrr's Handbook

Cross-account access

Role assumption vs resource-based policies, how AWS evaluates a cross-account request, external IDs, and the S3 and KMS patterns the exam loves.

Exam tasks: 1.2 (cross-account access, IAM roles and policies, resource-based policies, least privilege across accounts)

The decision: should the caller switch into a role in the other account, or should the resource in the other account trust the caller directly? And which policies in both accounts have to say yes?

Choosing a pattern

Roles vs resource-based policies

Assume a role (STS)Resource-based policy
How it worksCaller calls sts:AssumeRole and gets temporary credentials in account BResource in account B lists account A (or a principal in it) as allowed
Caller's own permissionsGiven up for the session. It only has the role's permissionsKept. It acts as itself in account A
Who must allow itRole trust policy (B) + sts:AssumeRole in the caller's identity policy (A)Resource policy (B) + identity policy (A)
ServicesAny serviceOnly services that support resource policies (S3, SQS, SNS, KMS, Lambda, Secrets Manager, ECR, EventBridge and others)
Resource ownershipObjects written are owned by account BS3 writes are owned by the bucket owner when Object Ownership is enforced
Audit trailCloudTrail shows the role session in B, with the source identity if setCloudTrail shows the principal from A
Good forOperators, pipelines, broad admin accessOne bucket, queue or key shared with a few accounts

The copy-between-accounts signal

A Lambda function in account A must read from its own bucket and write to a bucket in account B in one flow. If it assumes a role in B, it loses access to its own bucket for that session. A bucket policy in B that grants the function's role s3:PutObject lets it keep both.

Roles in practice

  • Trust policy says who can assume the role. Permissions policy says what the session can do.
  • Session duration is 1 hour by default and can be set up to 12 hours on the role. Role chaining (a role session assuming another role) is capped at 1 hour.
  • Pass a session policy on AssumeRole to narrow a session further. It can only remove permissions.
  • Set sts:SourceIdentity so CloudTrail keeps the original human or workload name across chained sessions.

How a cross-account request is evaluated

For a request from account A to a resource in account B, every layer must allow it and an explicit deny anywhere wins.

LayerLives inLimitsGrants anything?
SCPOrganization, on the caller's accountWhat principals in that account can doNo, it only sets the ceiling
RCPOrganization, on the resource's accountWhat anyone (including outsiders) can do to resources in that accountNo, ceiling only
Permissions boundaryThe caller's IAM user or roleThat one principal's maximumNo
Session policyPassed at AssumeRole or federationThat one sessionNo
Identity policyThe caller's user, group or roleThe callerYes
Resource policyThe bucket, key, queue and so onWho can use that resourceYes
  • Same account: either the identity policy or the resource policy can grant access on its own. KMS key policies and role trust policies are the exceptions: they must allow it explicitly.
  • Cross-account: you need both the identity policy in A and the resource policy in B.
  • SCPs and RCPs don't restrict the management account. SCPs also don't restrict service-linked roles.
  • RCPs cover a set of services that includes S3, STS, KMS, SQS and Secrets Manager. Use them to put a data perimeter around resources, for example "nothing outside my organization can read any bucket".

An SCP that 'grants' access

An SCP with Allow never gives anyone a permission. If a question says a user can't reach a resource and the fix offered is "add an Allow to the SCP", it's wrong unless the SCP was the thing denying it. The grant must come from an identity or resource policy.

External ID and the confused deputy

A SaaS vendor's account assumes roles in hundreds of customer accounts. If customer X can tell the vendor "my role ARN is arn:aws:iam::222233334444:role/Vendor" (which is really customer Y's role), the vendor becomes a confused deputy acting on Y's data.

  • The vendor generates a unique external ID per customer and stores it with the customer record.
  • Each customer's trust policy requires sts:ExternalId to match. The vendor always sends the ID it holds for the customer who made the request, so it can't be tricked into using another customer's role.
  • The external ID is not a secret password. It's an anti-mix-up token that the vendor controls.
  • For AWS services acting on your behalf (for example SNS writing to your SQS queue), the equivalent is aws:SourceArn and aws:SourceAccount in the resource policy.

Exam signal

"Third party", "vendor", "monitoring provider" or "several customers" with cross-account roles means the answer includes an external ID. For your own accounts, use aws:PrincipalOrgID or the account ID instead.

Organization-wide conditions

Condition keyUse it to
aws:PrincipalOrgIDAllow every principal in your organization in a resource policy, without listing 300 account IDs
aws:PrincipalOrgPathsAllow only principals under a given OU path
aws:ResourceOrgIDIn an identity policy or SCP, stop principals from writing to resources outside the organization
aws:SourceOrgIDLimit an AWS service acting for you to your organization's resources

New accounts that join the organization get access automatically, and accounts that leave lose it.

S3 cross-account patterns

Object Ownership

SettingACLsWho owns objects uploaded by another account
Bucket owner enforced (default for new buckets)DisabledThe bucket owner, always
Bucket owner preferredEnabledThe bucket owner, if the uploader sends bucket-owner-full-control
Object writerEnabledThe uploading account

With Object writer, the bucket owner can be unable to read objects that sit in its own bucket. Bucket owner enforced removes that problem and lets bucket policies alone control access.

When objects are encrypted with KMS

For SSE-KMS objects, S3 permissions aren't enough. The reader in account A also needs kms:Decrypt (and writers need kms:GenerateDataKey):

  1. The key policy in the key's account must allow account A (or its role).
  2. The identity policy in account A must allow the KMS actions on that key ARN.
  3. It must be a customer managed key. The AWS managed aws/s3 key's policy can't be edited, so it can't be shared across accounts.

See encryption for the key policy details.

Bucket policy fixed, still AccessDenied

If a cross-account reader gets AccessDenied after the bucket policy is correct, check the KMS key policy. It's the most common missing piece in exam scenarios that mention SSE-KMS.

Requester Pays

  • The requester pays for requests and data transfer out. The bucket owner still pays for storage.
  • Requests must include x-amz-request-payer: requester (or the --request-payer CLI flag), which acknowledges the charge. Without it, S3 returns 403.
  • Anonymous requests are rejected. Every requester must be an authenticated AWS principal.
  • If the requester assumes a role in the bucket owner's account, the bucket owner pays. For the requester to be billed, it must call with credentials from its own account, and the bucket policy grants that account access.

Finding what's shared

IAM Access Analyzer with the organization as the zone of trust flags every bucket, key, role, queue and other supported resource that grants access to principals outside the organization. Use it to prove least privilege after you've set up cross-account access.

Scenarios

Scenario
A security vendor needs read-only access to the CloudTrail logs and configuration of 400 customer AWS accounts. Each customer creates an IAM role that the vendor's AWS account assumes. The vendor wants to prevent one customer from tricking the vendor into accessing another customer's account. What should the vendor require in each customer's role trust policy?
Scenario · choose 2
A data team in account 111111111111 runs an EMR job that reads from its own S3 bucket and writes results to a bucket in account 222222222222. Objects in the destination bucket are encrypted with SSE-KMS using a customer managed key in account 222222222222. The job fails with AccessDenied on write. The EMR role has s3:PutObject on the destination bucket in its identity policy. Which TWO changes are required?
Scenario
A genomics institute publishes 800 TB of reference data in S3 for research partners, who all have their own AWS accounts. The institute wants partners to pay for downloads. Partners currently get the data by assuming a role in the institute's account. After enabling Requester Pays, the institute's data transfer bill doesn't fall. Why?

Further reading

On this page