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 auditing – sts: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.