
Share S3 buckets across AWS accounts by creating an IAM role in the accessing account with S3 permissions, attaching that role to an EC2 instance, then adding a bucket policy in the source account that grants access to the cross-account role ARN.
Introduction
There are a few ways to share an S3 bucket across AWS accounts. The most common pattern is: create an IAM role in the accessing account, attach it to the resource that needs access (an EC2 instance in this example), then add a bucket policy in the source account that grants access to that role's ARN. Two-sided trust, easy to audit, no shared access keys to rotate.
This post walks through the full setup with AWS CLI commands. The example uses s3:* permissions to keep the demo short. Tighten to the specific actions you need (s3:GetObject, s3:PutObject, etc.) for production.
Prerequisites
- Account A to be the owner of the S3 bucket you want to share
- Account B to be the account that will hold a role to be used to access the S3 bucket in Account A
- Adequate permissions in both AWS accounts to accomplish the tasks
- AWS CLI installed
The example below grants s3:* permissions for demonstration purposes. In production, always follow the principle of least privilege - grant only the specific S3 actions needed (e.g., s3:GetObject, s3:PutObject) rather than wildcard permissions.
Account A
- Create a S3 bucket called 'mybucket2share'
- aws s3 mb mybucket2share
Account B
In Account B I create an EC2 service role (call it ec2role2accessmybucket2share) with the standard EC2 trust policy that lets ec2.amazonaws.com assume it via sts:AssumeRole. Then I create a managed policy (mycrossaccounts3bucketpolicy) that allows the S3 actions you need on both arn:aws:s3:::mybucket2share and arn:aws:s3:::mybucket2share/*. Attach the policy to the role with aws iam attach-role-policy, then launch an EC2 instance with --iam-instance-profile Name=ec2role2accessmybucket2share so the instance picks up the role on boot. I usually grab the current Account B ID with aws sts get-caller-identity --query Account --output text and substitute it into the policy ARN at attach time.
Use IAM roles instead of IAM users for cross-account access whenever possible. Roles provide temporary credentials that automatically rotate, eliminating the need to manage long-term access keys.
Back to Account A
Back on Account A, apply a bucket policy to mybucket2share via aws s3api put-bucket-policy. The policy has a single Allow statement with Principal.AWS pointing at the full role ARN from Account B (arn:aws:iam::<Account-B-ID>:role/ec2role2accessmybucket2share), the S3 actions you want to permit, and the bucket and bucket/* as the resource. This is the cross-account trust piece, and it must reference the role ARN exactly. A typo in the account ID gives a silent AccessDenied at runtime.
Replace '123456' with the actual 12-digit AWS Account ID of Account B. The bucket policy grants access to the specific IAM role ARN, ensuring only that role can access the bucket.
Back to Account B
- Log into the EC2
- Create a file called 'mytestfile.txt' with the text content of 'test' locally to the EC2 instance
- echo 'test' > mytestfile.txt
- Test copying a file to the S3 bucket
- aws s3 cp mytestfile.txt s3://mybucket2share/
- Test listing the contents of the bucket to confirm copy action was successful
- aws s3 ls s3://mybucket2share/
Troubleshooting
| Issue | Possible Cause | Solution |
|---|---|---|
| "Access Denied" when accessing bucket from Account B | Bucket policy doesn't include the role ARN or typo in ARN | Verify the bucket policy Principal ARN exactly matches the role ARN in Account B. Check for typos in account ID or role name. |
| EC2 instance cannot assume the role | Instance profile not created or not attached | Create an instance profile with the same name as the role: aws iam create-instance-profile and aws iam add-role-to-instance-profile. |
| Can list objects but cannot download | Bucket policy missing object-level permissions | Ensure the bucket policy Resource includes both the bucket ARN and bucket ARN with /* for object-level access. |
| "InvalidIdentityToken" error | Role trust policy incorrect | For EC2 roles, ensure the trust policy allows ec2.amazonaws.com as the Principal Service with sts:AssumeRole action. |
| Permissions work inconsistently | IAM policy and bucket policy conflicting | Both the IAM policy in Account B AND the bucket policy in Account A must allow the action. Check both policies for the specific operations needed. |
Want Help With This?
If you're working on something similar and want a second set of eyes, or you'd like to talk through how this applies to your environment, reach out via the contact form. Happy to help.