---
cuppy_hub: aws
cuppy_path: guides/iam/cross-account-access.md
cuppy_section: docs
cuppy_doc: 4E07-5KSH
cuppy_doc_uuid: 8d2dca25-32c1-4a11-a9ac-6cf45cc4bdb4
cuppy_permalink: https://cuppy.io/aws/d/4E07-5KSH
cuppy_version: 4
cuppy_updated: 2026-10-23T16:16:52.575Z
cuppy_canonical: https://cuppy.io/aws/docs/guides/iam/cross-account-access
cuppy_mcp: https://cuppy.io/mcp
cuppy_body_sha256: 376625c49ae56c8b41fc5a6ef8a47350237f9a95403334f05a92ee4a162ba220
# cuppy_body_sha256 is over the document body ALONE, excluding both generated blocks.
---


# Let a principal in one account act in another

**Cross-account access needs an allow on both sides, and each side is a different account's change.** Below, the **caller account** is where the identity lives and the **target account** is where the resources are. Decide which path you need, then make the target-side change first so the caller-side change can be tested immediately.

## 1. Choose the path

| Path | Use when | What you configure |
|---|---|---|
| **A. Assume a role in the target account** | The caller needs several resources or services in the target account, or the resource type has no resource policy | Target: a role with a trust policy naming the caller. Caller: permission to `sts:AssumeRole` on that role |
| **B. Resource policy naming the caller** | One resource that supports a resource policy (a bucket, queue, secret, key), and the caller should keep its own identity | Target: the resource policy names the caller. Caller: its identity policy allows the action on the target ARN |

Path A is the general answer. After assuming the role, the caller acts as the role inside the target account, so the role's own permissions policies govern access there and no per-resource policy is needed.

## 2. Path A, target account: create the role

Trust policy naming the caller. Name a specific role in the caller account rather than the whole account where you can:

```json
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "AWS": "arn:aws:iam::<caller-account-id>:role/<caller-role>" },
    "Action": "sts:AssumeRole"
  }]
}
```

```
aws iam create-role --role-name <target-role> --assume-role-policy-document file://trust.json
aws iam attach-role-policy --role-name <target-role> --policy-arn <permissions policy ARN>
```

- Naming the account (`"AWS": "<caller-account-id>"` or its `:root` ARN, which are equivalent) instead lets that account's administrators decide who may assume the role.
- For a **third party**, require `sts:ExternalId` in the trust policy (`patterns/iam/trust-policies.md`).
- Set the role's maximum session duration if callers need sessions longer than one hour (up to 12).
- If saving fails with `Invalid principal in policy`, the caller ARN is malformed, uses a partial wildcard, names a deleted principal, or crosses the GovCloud boundary: see `reference/iam/errors.md`.

## 3. Path A, caller account: allow the assume

Attach to the calling identity:

```json
{
  "Effect": "Allow",
  "Action": "sts:AssumeRole",
  "Resource": "arn:aws:iam::<target-account-id>:role/<target-role>"
}
```

Then use it. Simplest for the CLI and SDKs is a profile that assumes the role and refreshes it automatically:

```ini
[profile target]
role_arn = arn:aws:iam::<target-account-id>:role/<target-role>
source_profile = <profile holding the caller's credentials>
```

```
aws sts get-caller-identity --profile target
```

The returned ARN should be `arn:aws:sts::<target-account-id>:assumed-role/<target-role>/<session-name>`. The session name is visible in the target account's CloudTrail. Calling AssumeRole directly from code: `reference/sts/assume-role.md`.

## 4. Path B: resource policy plus caller permission

Target account, on the resource:

```json
{
  "Effect": "Allow",
  "Principal": { "AWS": "arn:aws:iam::<caller-account-id>:role/<caller-role>" },
  "Action": ["<service>:<Action>"],
  "Resource": "<resource ARN>"
}
```

Caller account, on the identity: an Allow for the same actions on the same resource ARN. Either half alone is not enough. KMS key policies are an exception to the "either side is enough" rule even within one account: they must name the principal explicitly. If the target data is encrypted with a customer managed key, the key policy is a third policy that must allow the caller; check the service's cross-account documentation.

## 5. Test each side on its own

1. From the caller: `aws sts get-caller-identity` (confirms who you are), then the assume or the action.
2. Read the end of any denial (`guides/iam/fix-access-denied.md`):
   - `no role trust policy allows` → target side, step 2.
   - `no identity-based policy allows the sts:AssumeRole action` → caller side, step 3.
   - `no resource-based policy allows` → target side, step 4.
   - A service control policy named → the **caller's** organization caps the principal.
   - A resource control policy named → the **target's** organization caps the resource, and RCPs apply to callers from outside the organization too.
3. An `ExpiredToken` later in a long job means the role session ran out; request a longer `DurationSeconds` within the role's maximum, or use the profile form, which refreshes. Role chaining caps the session at one hour.


---
<!-- cuppy -->
You are reading one file. The same content is free over MCP at https://cuppy.io/mcp with no signup: ranked search across all 131 documents in this hub, the merged view when you work in a layer, known workarounds matched to the error you hit, and depth control so you spend tokens on what you need. Start with the `_hello` tool. How to connect: https://cuppy.io/aws/ Something wrong in this document? Report it with the `session feedback` tool on the same endpoint.
<!-- /cuppy -->
