
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
- Create an EC2 role called 'ec2role2accessmybucket2share'
- aws iam create-role --role-name ec2role2accessmybucket2share --assume-role-policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"ec2.amazonaws.com"},"Action":"sts:AssumeRole"}]}'
- Create policy called mycrossaccounts3bucketpolicy to access the S3 bucket called 'mybucket2share'
- aws iam create-policy --policy-name mycrossaccounts3bucketpolicy --policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["s3:"],"Resource":["arn:aws:s3:::mybucket2share","arn:aws:s3:::mybucket2share/"]}]}'
- Attach the policy to the EC2 role. Here I am creating and using the variable AWS_ACCOUNT_ID which will pull the AWS Account ID for Account B
- AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
- aws iam attach-role-policy --policy-arn arn:aws:iam::${AWS_ACCOUNT_ID}:policy/mycrossaccounts3bucketpolicy --role-name ec2role2accessmybucket2share
- Create an EC2 instance using the latest Linux AMI, using a keypair called mykey, launched in the subnet with the id of subnet-1234, with the security group attached with the id sg-1234, and attach the role created above
- aws ec2 run-instances --image-id $(aws ec2 describe-images --owners amazon --filters 'Name=name,Values=amzn2-ami-hvm-2.0.????????-x86_64-gp2' --query 'Images[*].[ImageId, CreationDate]' --output text | sort -k2 -r | head -n1 | awk '{print $1}') --count 1 --instance-type --key-name mykey --security-group-ids sg-1234 --subnet-id subnet-1234 --iam-instance-profile Name=ec2role2accessmybucket2share
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
- Create a bucket policy to allow full access (modify to your business needs) to the S3 bucket from the EC2 role in Account B
- aws s3api put-bucket-policy --bucket mybucket2share --policy '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":"arn:aws:iam::123456:role/ec2role2accessmybucket2share"},"Action":["s3:"],"Resource":["arn:aws:s3:::mybucket2share/"]}]}'
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. |