S3 Role External Access#
It provides the mechanism to create an IAM role with access on a specific location (path) within a S3 bucket. The access policy is assigned to the root of the external account, which is responsible for creating the internal policies to allow its internal roles to assume the role.
Features#
Fine-grained S3 access: Grant access to a specific bucket and prefix, not the entire bucket
External account trust: Only the specified AWS account ID can assume the role
Optional External ID: Support for External ID to prevent confused deputy attacks (recommended for third-party access)
Configurable access level: Choose between
READ,WRITE, orREAD_WRITEpermissionsOptional bucket listing: Optionally allow the external account to list all buckets
Secure by default: No public access, and permissions are limited to the specified path
Tagging support: Apply custom tags to IAM resources for organization and cost tracking
Outputs#
This module exports the following outputs:
Name |
Description |
|---|---|
iam_role_arn |
The ARN of the IAM role created for external access |
iam_policy_arn |
The ARN of the IAM policy attached to the role |
Access Levels#
READ: Allows listing and reading objects under the specified prefixWRITE: Allows uploading objects under the specified prefixREAD_WRITE: Allows both reading and writing (full access) under the specified prefix
Access Level |
s3:GetObject |
s3:PutObject |
s3:ListBucket |
|---|---|---|---|
READ |
✅ |
❌ |
✅ |
WRITE |
❌ |
✅ |
❌ |
READ_WRITE |
✅ |
✅ |
✅ |
Security Considerations#
The role can only be assumed by the specified external AWS account
The policy restricts access to the given bucket and prefix only
Use
external_idparameter for third-party access to prevent confused deputy attacksThe external account should create internal policies limiting which users/roles can assume this role
Account ID validation ensures only valid 12-digit AWS account IDs are accepted
How to Use#
Variables#
Name |
Description |
Type |
Default |
Required |
|---|---|---|---|---|
role_name |
Name of the IAM role |
string |
n/a |
Yes |
aws_external_account |
AWS Account ID (12 digits) of the external account |
string |
n/a |
Yes |
bucket_name |
Name of the S3 bucket to grant access to |
string |
n/a |
Yes |
prefix |
Prefix/path within the bucket where access is granted |
string |
n/a |
Yes |
access_level |
Access level: READ, WRITE, or READ_WRITE |
string |
“READ” |
No |
allow_list_buckets |
Allow listing all S3 buckets |
bool |
false |
No |
external_id |
Optional External ID for additional security |
string |
null |
No |
tags |
Tags to apply to IAM resources |
map(string) |
{} |
No |
Basic Example#
resource "aws_s3_bucket" "s3_bucket" {
bucket = "shared-bucket-xcfu-uysters"
}
resource "aws_s3_bucket_public_access_block" "block_public_access" {
bucket = aws_s3_bucket.s3_bucket.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
module "some_external_access" {
source = "./modules/s3/role_external_access"
aws_external_account = "123456789012"
role_name = "role_for_client"
bucket_name = aws_s3_bucket.s3_bucket.bucket
prefix = "uploads/client"
access_level = "READ"
}
Configuration on the External Account#
Because the access is granted to the root of the external account, the administrator of it could create an IAM policy allowing other users to access the bucket location.
IAM Policy#
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "arn:aws:iam::{{account_own_bucket}}:role/{{role_name}}"
}
]
}
AWS CLI Commands#
Accessing the bucket location using the AWS CLI console commands.
Assuming the Role#
aws sts assume-role --role-arn arn:aws:iam::060923676874:role/role_for_nissan --role-session-name testing-s3-role
Listing the Buckets#
aws s3 ls
We will get an error because the lack of permission, we can grant it via: allow_list_buckets = true.
An error occurred (AccessDenied) when calling the ListBuckets operation: User:
arn:aws:sts::******:assumed-role/role_for_nissan/testing-s3-role is not authorized to
perform: s3:ListAllMyBuckets because no identity-based policy allows
the s3:ListAllMyBuckets action.
Downloading Files#
aws s3 cp s3://shared_bucket/uploads/nissan/report.txt .
For READ and READ_WRITE permissions we will get download: s3://shared_bucket/uploads/nissan/report.txt to ./report.txt, otherwise fatal error: An error occurred (403) when calling the HeadObject operation: Forbidden.
Uploading Files#
aws s3 cp ./report.txt s3://shared_bucket/uploads/nissan/report.txt
For WRITE and READ_WRITE permissions we will get upload: ./report.txt to s3://shared_bucket/uploads/nissan/report.txt, otherwise:
upload failed: ./report.txt to s3://shared_bucket/uploads/nissan/report.txt
An error occurred (AccessDenied) when calling the PutObject operation: User:
arn:aws:sts::******:assumed-role/role_for_nissan/testing-s3-role is not authorized to
perform: s3:PutObject on resource: "arn:aws:s3:::shared_bucket/uploads/nissan/report.txt"
because no identity-based policy allows the s3:PutObject action.