Access a Shared AWS S3 Bucket with Two Roles
This guide uses two roles—one in the Bucket Owner’s account and one in the Bucket User’s account—so that the Bucket User can manage (and delegate) who in their organization gains access to the shared S3 bucket.
Scenario Overview
-
Bucket Owner’s Account (Account A)
- Owns the S3 bucket.
- Will create an IAM role that grants permissions to access the bucket, and trusts the Bucket User’s AWS account.
-
Bucket User’s Account (Account B)
- Needs to provide access for multiple users or roles within their own organization.
- Will create an IAM role that trusted internal users can assume, which then “chains” into the role in Account A to access the bucket.
Diagrammatically:
[Bucket Owner's Role] ----> S3 Bucket
^ (Assume)
|
[Bucket User's Role] <---- (Assume from internal principals)
^
[Developers / Internal Principals]
Part A: Bucket Owner’s Steps
Step 1: Determine Which S3 Actions to Allow
Decide which S3 permissions to grant. This example allows read-only (List/Get) access. Adjust as needed (e.g., add s3:PutObject
, s3:DeleteObject
):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:GetObjectAttributes",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::YOUR_BUCKET_NAME",
"arn:aws:s3:::YOUR_BUCKET_NAME/path/to/shared/files/*"
]
}
]
}
Tip: If you only want to permit certain folder paths, adjust the resources (e.g.,
arn:aws:s3:::YOUR_BUCKET_NAME/folder-name/*
).
Step 2: Create a Role in the Bucket Owner’s Account
- Go to the AWS Console → IAM → Roles → Create role.
- Select “Another AWS account” as the trusted entity.
- Enter the Bucket User’s AWS Account ID (Account B).
- Attach a custom policy (from Step 1) or create an inline policy that grants the desired S3 permissions.
- Name this role something like
CrossAccountS3AccessRole
. - Create the role.
When done, open the role’s Trust relationships tab and ensure it looks roughly like:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::222222222222:root"
},
"Action": "sts:AssumeRole"
}
]
}
Important: Make sure the Principal is correct for the Bucket User’s account. If you plan to use an external ID or conditions, include them here.
Finally, copy the ARN for this role, for example:
arn:aws:iam::111111111111:role/CrossAccountS3AccessRole
Part B: Bucket User’s Steps
Step 3: Create a Role in the Bucket User’s Account
In your AWS account (Account B), create a role that your internal users or IAM principals can assume. This “local” role will chain into the bucket owner’s role:
- Go to AWS Console → IAM → Roles → Create role.
- Select your own account (Account B) as the trusted entity (or specify the user/group that can assume it).
- Add a policy that allows “sts:AssumeRole” on the Bucket Owner’s role, for example:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "arn:aws:iam::111111111111:role/CrossAccountS3AccessRole"
}
]
}
- Name the role something like
InternalToExternalS3AccessRole
. - Create the role.
Once created, the Trust relationships of this role might look like:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::222222222222:root"
},
"Action": "sts:AssumeRole"
}
]
}
Note: You can restrict the principal further to specific users or groups within your account.
Step 4: Have Internal Users Assume Your Local Role
Now, internal developers or automation in your account can do the following:
- Assume the
InternalToExternalS3AccessRole
in your account (Account B). - That role policy grants
sts:AssumeRole
on the bucket owner’sCrossAccountS3AccessRole
(in Account A). - Finally, assume that external role to gain S3 access.
Example flow in a developer’s local environment:
# 1) Assume your local role (Account B) to “bridge” into the other account
aws sts assume-role \
--role-arn arn:aws:iam::222222222222:role/InternalToExternalS3AccessRole \
--role-session-name MyLocalSession
# 2) Using the temporary credentials from step (1), assume the external role:
aws sts assume-role \
--role-arn arn:aws:iam::111111111111:role/CrossAccountS3AccessRole \
--role-session-name MyCrossAccountSession
# 3) You'll get credentials that let you access the S3 bucket in Account A.
Part C: Using Environment Variables & jq
To automate environment variables in your shell:
# 1) Assume your local role (in Account B)
eval $(
aws sts assume-role \
--role-arn arn:aws:iam::222222222222:role/InternalToExternalS3AccessRole \
--role-session-name MyLocalSession \
| jq -r '.Credentials |
"export AWS_ACCESS_KEY_ID=\(.AccessKeyId)\n
export AWS_SECRET_ACCESS_KEY=\(.SecretAccessKey)\n
export AWS_SESSION_TOKEN=\(.SessionToken)"'
)
# 2) Chain-assume the external (Bucket Owner) role
eval $(
aws sts assume-role \
--role-arn arn:aws:iam::111111111111:role/CrossAccountS3AccessRole \
--role-session-name MyCrossAccountSession \
| jq -r '.Credentials |
"export AWS_ACCESS_KEY_ID=\(.AccessKeyId)\n
export AWS_SECRET_ACCESS_KEY=\(.SecretAccessKey)\n
export AWS_SESSION_TOKEN=\(.SessionToken)"'
)
# 3) Test S3 operations
aws s3 ls s3://YOUR_BUCKET_NAME/path/
Note: Each set of temporary credentials typically expires after 1 hour (or a configured max session duration). You’ll need to re-run these commands as needed.
Summary Diagram
-
Account A (Bucket Owner)
- Role:
CrossAccountS3AccessRole
- Trusts: Account B
- Policy: Read/Write to S3 (as required)
- Role:
-
Account B (Bucket User)
- Role:
InternalToExternalS3AccessRole
- Trusts: Principals in Account B
- Policy:
sts:AssumeRole
→CrossAccountS3AccessRole
in Account A
- Role:
-
Internal Principals in Account B assume
InternalToExternalS3AccessRole
, which then assumes theCrossAccountS3AccessRole
, granting access to the S3 bucket in Account A.
FAQs
-
Why two roles instead of one?
- Security & Flexibility: The bucket owner sets S3 permissions, but doesn’t manage which individuals in your organization assume them. You (the Bucket User) control internal access separately.
-
What if I just want direct access from my account without an extra role?
- That’s possible if the Bucket Owner trusts the entire external account or specific principals directly. However, it’s often cleaner to separate them for security and delegated access control.
-
Session duration?
- Both roles have a
MaxSessionDuration
. By default, it’s 1 hour. It can be extended up to 12 hours in IAM settings.
- Both roles have a
-
Access Denied errors?
- Check the trust policies on both roles.
- Ensure you’re assuming the correct role(s).
- Verify the resource ARNs and AWS account IDs are correct.
Additional Best Practice Considerations
- Condition Keys / External ID: For tighter security or third-party scenarios, consider using condition keys or an external ID in the trust policy.
- Logging & Monitoring: Enable CloudTrail to audit STS usage and S3 access logs (or Server Access Logging) to track object-level events.
- Encryption: Consider S3 default encryption or KMS keys for sensitive data, plus
aws:SecureTransport
conditions to force HTTPS. - MFA: You can require multi-factor authentication to assume critical roles.
These measures can further strengthen your cross-account setup.
Conclusion
By having two roles—one controlled by the Bucket Owner and one by the Bucket User—you achieve a clear separation of responsibilities. The Bucket Owner decides which actions are allowed in the bucket, while the Bucket User decides who in their organization has permission to assume that cross-account role.