Skip to main content

Command Palette

Search for a command to run...

How to Access an S3 Bucket Across AWS Accounts Using IAM Roles

Updated
4 min read
How to Access an S3 Bucket Across AWS Accounts Using IAM Roles

(No Access Keys)

This blog explains a production-grade, AWS-recommended solution using IAM Roles and STS AssumeRole.

No access keys.
No secrets.
No hardcoding.


Why This Matters

Many teams still:

  • Share access keys ❌

  • Store secrets in files or environment variables ❌

  • Use overly permissive bucket policies ❌

All of these approaches are risky and hard to audit.

AWS provides a secure, least-privilege mechanism for this exact problem:
Cross-account IAM roles with STS AssumeRole.


The Goal

  • EC2 runs in Account A

  • S3 bucket exists in Account B

  • EC2 can access the bucket securely

  • No access keys or secrets

  • IAM roles only

  • Automatic credential rotation


High-Level Architecture

EC2 (Account A)
 └── IAM Role (EC2 Base Role)
       └── sts:AssumeRole
             └── IAM Role (S3 Access Role – Account B)
                   └── S3 Bucket

Important rule: An EC2 instance can only attach an IAM role from its own account.
Cross-account access always happens using STS AssumeRole.


Example Accounts (Dummy Values)

PurposeAccount ID
EC2 account111111111111
S3 account222222222222

Account B Setup (S3 Owner)

Step 1: Create an IAM Role in Account B

Role name:

cross-account-s3-role

Role ARN:

arn:aws:iam::222222222222:role/cross-account-s3-role

This role represents who is allowed to access the S3 bucket.


Step 2: Attach IAM Policy to the Role

This policy defines what the role can do.

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

Step 3: Add Bucket Policy to the S3 Bucket

Bucket name:

example-content-bucket
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::222222222222:role/cross-account-s3-role"
      },
      "Action": "s3:ListBucket",
      "Resource": "arn:aws:s3:::example-content-bucket"
    },
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::222222222222:role/cross-account-s3-role"
      },
      "Action": [
        "s3:GetObject",
        "s3:PutObject"
      ],
      "Resource": "arn:aws:s3:::example-content-bucket/*"
    }
  ]
}

Step 4: Configure Trust Relationship (Critical)

This allows Account A to assume the role.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::111111111111:root"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

Account B setup is now complete.


Account A Setup (EC2 Owner)

Step 5: Create an IAM Role for EC2

Role name:

ec2-cross-account-role

Role ARN:

arn:aws:iam::111111111111:role/ec2-cross-account-role

This role will be attached to the EC2 instance.


Step 6: Allow This Role to Assume the S3 Role

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

Note: This role does not have direct S3 permissions.


Step 7: Trust Relationship for EC2 Role

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

Step 8: Create Instance Profile (Required)

aws iam create-instance-profile \
  --instance-profile-name ec2-cross-account-role

aws iam add-role-to-instance-profile \
  --instance-profile-name ec2-cross-account-role \
  --role-name ec2-cross-account-role

Attach this role to the EC2 instance.


Validation on EC2

Step 9: Verify EC2 Role

aws sts get-caller-identity

Expected output:

{
  "Account": "111111111111",
  "Arn": "arn:aws:sts::111111111111:assumed-role/ec2-cross-account-role/..."
}

Permanent Access (No Keys, No Manual AssumeRole)

Step 10: Configure AWS CLI for Auto-Assume

mkdir -p ~/.aws
nano ~/.aws/config
[default]
region = ap-south-1

[profile s3-cross-account]
role_arn = arn:aws:iam::222222222222:role/cross-account-s3-role
credential_source = Ec2InstanceMetadata
region = ap-south-1

Important:

  • No ~/.aws/credentials file

  • No access keys

  • Uses EC2 metadata automatically


Step 11: Access S3 Bucket

aws s3 ls s3://example-content-bucket --profile s3-cross-account

This will work permanently.


Final Outcome

  • Secure cross-account S3 access

  • IAM roles only

  • No secrets stored anywhere

  • Automatic credential rotation

  • Fully auditable

  • Production-ready


Key Takeaways

  • EC2 cannot attach roles from another account

  • Cross-account access always uses STS AssumeRole

  • IAM roles are the credentials

  • AWS CLI supports permanent auto-assume

  • This is the AWS-recommended pattern


When to Use This Pattern

  • Multi-account AWS environments

  • Shared S3 buckets

  • Centralized data platforms

  • CI/CD pipelines

  • Bastion or admin EC2 instances

    LinkedIn