A
Arun's Blog
All Posts

Cross-Account S3 Bucket Access via IAM Roles and Bucket Policies

|4 min read|
CLIS3SecurityIAM
TL;DR

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
Security Warning

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

  1. Create a S3 bucket called 'mybucket2share'
    • aws s3 mb mybucket2share

Account B

  1. 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"}]}'
  2. 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/"]}]}'
  3. 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
  4. 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
Pro Tip

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

  1. 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/"]}]}'
Note

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

  1. Log into the EC2
  2. Create a file called 'mytestfile.txt' with the text content of 'test' locally to the EC2 instance
    • echo 'test' > mytestfile.txt
  3. Test copying a file to the S3 bucket
    • aws s3 cp mytestfile.txt s3://mybucket2share/
  4. 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.

Related Articles