Skip to main content

Two Approaches to Cross-Account Amazon S3 Access: Direct Policies vs. AssumeRole

· 4 min read

In AWS multi-account environments, it’s common to separate workloads, environments, or business units into different accounts. But sometimes you need to share an Amazon S3 bucket between accounts. For example, Account A might run an application that needs to read and write data into a bucket owned by Account B.

This blog will guide you through two approaches for enabling cross-account access:

  1. Direct access – Allow a role from Account A directly in Account B’s bucket policy (and KMS key policy if encrypted).
  2. Recommended access – Create a role in Account B and let Account A assume it securely (with External ID).

We’ll also cover necessary KMS permissions when the bucket uses a customer-managed key.

Scenario

  • Account A (111111111111): Workload that needs read/write access.
  • Account B (222222222222): Owns S3 bucket my-shared-bucket (optionally encrypted with a KMS CMK arn:aws:kms:region:222222222222:key/KEY-ID).

Shared Prerequisites

  • Replace account IDs, ARNs, regions, role names, and bucket names with your own.
  • Include ListBucket for listing keys and object-level permissions for actual reads/writes.
  • If the bucket uses a KMS CMK, grant principals KMS permissions (kms:Encrypt, kms:Decrypt, kms:ReEncrypt*, kms:GenerateDataKey*, kms:DescribeKey).
  • Prefer S3 Object Ownership = Bucket owner enforced to avoid ACL complexities.

Approach 1 — Direct Role Access from Account A

Step 1. Create a Role in Account A

Role: RoleInAccountA

IAM policy attached to RoleInAccountA:

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "RWObjects",
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::my-shared-bucket/*"
},
{
"Sid": "ListBucket",
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::my-shared-bucket"
}
]
}

Step 2. Update Bucket Policy in Account B

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowRoleInAccountAReadWrite",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111111111111:role/RoleInAccountA"
},
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::my-shared-bucket/*"
},
{
"Sid": "AllowRoleInAccountAList",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111111111111:role/RoleInAccountA"
},
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::my-shared-bucket"
}
]
}

Step 3. Update KMS Key Policy in Account B (If Encrypted)

{
"Sid": "AllowRoleInAccountAToUseKey",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111111111111:role/RoleInAccountA"
},
"Action": [
"kms:Encrypt", "kms:Decrypt", "kms:ReEncrypt*", "kms:GenerateDataKey*", "kms:DescribeKey"
],
"Resource": "*",
"Condition": {
"StringEquals": {
"kms:EncryptionContext:aws:s3:arn": "arn:aws:s3:::my-shared-bucket"
}
}
}

Approach 2 (Recommended) — Assume a Role in Account B

Why This is Better

  • Least-privilege – Permissions live in Account B, closest to the resource.
  • Clear auditingsts:AssumeRole calls visible in CloudTrail.
  • KMS simplicity – Key trusts only a role inside Account B.
  • External ID – Protects against confused deputy attacks.

Step 1. Create Role in Account B (S3AccessFromA)

Permissions policy:

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "RWObjects",
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::my-shared-bucket/*"
},
{
"Sid": "ListBucket",
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::my-shared-bucket"
}
]
}

If KMS-encrypted:

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "UseKmsForBucket",
"Effect": "Allow",
"Action": [
"kms:Encrypt", "kms:Decrypt", "kms:ReEncrypt*", "kms:GenerateDataKey*", "kms:DescribeKey"
],
"Resource": "arn:aws:kms:region:222222222222:key/KEY-ID"
}
]
}

Step 2. Trust Policy for S3AccessFromA

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "TrustRoleInAccountAWithExternalId",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111111111111:role/RoleInAccountA"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "YOUR-STRONG-EXTERNAL-ID"
}
}
}
]
}

Step 3. KMS Key Policy in Account B

{
"Sid": "AllowS3AccessFromARoleToUseKey",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::222222222222:role/S3AccessFromA"
},
"Action": [
"kms:Encrypt", "kms:Decrypt", "kms:ReEncrypt*", "kms:GenerateDataKey*", "kms:DescribeKey"
],
"Resource": "*",
"Condition": {
"StringEquals": {
"kms:EncryptionContext:aws:s3:arn": "arn:aws:s3:::my-shared-bucket"
}
}
}

Step 4. Bucket Policy in Account B

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowOnlyTargetRoleDataPlane",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::222222222222:role/S3AccessFromA"
},
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::my-shared-bucket/*"
},
{
"Sid": "AllowOnlyTargetRoleList",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::222222222222:role/S3AccessFromA"
},
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::my-shared-bucket"
}
]
}

Step 5. Minimal Policy in Account A to Assume Role

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AssumeCrossAccountRole",
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "arn:aws:iam::222222222222:role/S3AccessFromA"
}
]
}

Usage:

aws sts assume-role \
--role-arn arn:aws:iam::222222222222:role/S3AccessFromA \
--role-session-name from-account-a \
--external-id YOUR-STRONG-EXTERNAL-ID \
--profile account-a

Then use temporary credentials to perform S3 operations.

Comparison

ApproachProsCons
Direct (Account A role in bucket policy)Simpler setupHarder to scale, bucket/key policies trust external principals directly
Recommended (Assume role in Account B)Stronger isolation, External ID support, better auditingSlightly more setup

Conclusion

By configuring bucket policies, IAM roles, and KMS key policies, you can securely enable cross-account read and write access in S3. While direct bucket policy access works, the recommended approach—assuming a role in Account B—provides stronger isolation, auditability, and long-term maintainability.