Granting cross-account access to DynamoDB means one AWS account (the "requester") needs to read from or write to DynamoDB tables owned by another AWS account (the "owner"). The standard, secure way to do this is by leveraging IAM roles.
Here’s how you’d typically set this up and what it looks like in practice.
Let’s say Account A wants to access DynamoDB tables in Account B.
Account B (Owner Account) Setup:
-
Create a DynamoDB Table: First, you need a table in Account B. For example, let’s call it
MyCrossAccountTable. -
Create an IAM Role for Account A: In Account B, you create an IAM role that Account A’s principals (users, applications, or other roles) can assume.
- Go to IAM -> Roles -> Create role.
- Trusted entity type: Select "AWS account."
- Account ID: Enter the AWS Account ID of Account A.
- Optional: You can add conditions here, like requiring MFA or specifying a particular external ID if Account A is assuming this role from an external identity provider. For simplicity, we’ll skip this for now.
- Click "Next."
-
Attach a Policy to the Role (Permissions): This is where you define what Account A can do. Create a new inline policy or attach an existing one.
-
Example Policy (Read-only access to
MyCrossAccountTable):{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "dynamodb:GetItem", "dynamodb:Query", "dynamodb:Scan", "dynamodb:BatchGetItem" ], "Resource": "arn:aws:dynamodb:us-east-1:ACCOUNT_B_ID:table/MyCrossAccountTable" } ] }Replace
ACCOUNT_B_IDwith the actual Account ID of Account B andus-east-1with the correct region. -
Click "Next."
-
-
Name the Role: Give it a descriptive name, like
DynamoDBReaderRoleForAccountA. Click "Create role."
Account A (Requester Account) Setup:
-
Create an IAM Role for the Principal: In Account A, you create a role that your application or user will assume. This role needs permission to assume the role created in Account B.
- Go to IAM -> Roles -> Create role.
- Trusted entity type: Select "AWS account."
- Account ID: Enter the AWS Account ID of Account A itself (this is for roles that will be assumed by principals within Account A).
- Click "Next."
-
Attach a Policy to this Role (Permission to Assume): This policy allows principals in Account A to assume the
DynamoDBReaderRoleForAccountAin Account B.-
Example Policy:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "sts:AssumeRole", "Resource": "arn:aws:iam::ACCOUNT_B_ID:role/DynamoDBReaderRoleForAccountA" } ] }Replace
ACCOUNT_B_IDwith the actual Account ID of Account B. -
Click "Next."
-
-
Name the Role: Give it a name, like
AssumeDynamoDBAccessRole. Click "Create role." -
Attach the Role to the Principal: Now, associate the
AssumeDynamoDBAccessRolewith the entity in Account A that needs to access DynamoDB.- For an EC2 instance: Attach an IAM instance profile with this role to the EC2 instance.
- For an IAM user: Attach this role directly to the IAM user.
- For an application running on-premises or elsewhere: The application will need AWS credentials configured to call
sts:AssumeRolewith the ARN ofDynamoDBReaderRoleForAccountAand then use the temporary credentials obtained to interact with DynamoDB.
How it Works in Code (Example using AWS SDK for Python - Boto3):
Let’s say you have an application running in Account A (associated with the AssumeDynamoDBAccessRole).
import boto3
from botocore.exceptions import ClientError
ACCOUNT_B_ID = "YOUR_ACCOUNT_B_ID"
ROLE_NAME = "DynamoDBReaderRoleForAccountA"
REGION_NAME = "us-east-1"
TABLE_NAME = "MyCrossAccountTable"
def get_dynamodb_client_cross_account():
"""Assumes the cross-account role and returns a DynamoDB client."""
try:
# 1. Assume the role in Account B
sts_client = boto3.client("sts")
assumed_role_object = sts_client.assume_role(
RoleArn=f"arn:aws:iam::{ACCOUNT_B_ID}:role/{ROLE_NAME}",
RoleSessionName="CrossAccountDynamoDBAccessSession"
)
credentials = assumed_role_object['Credentials']
# 2. Create a DynamoDB client using the assumed role credentials
dynamodb_client = boto3.client(
"dynamodb",
aws_access_key_id=credentials['AccessKeyId'],
aws_secret_access_key=credentials['SecretAccessKey'],
aws_session_token=credentials['SessionToken'],
region_name=REGION_NAME
)
return dynamodb_client
except ClientError as e:
print(f"Error assuming role or creating client: {e}")
return None
def scan_dynamodb_table(client):
"""Scans the DynamoDB table using the provided client."""
if not client:
print("DynamoDB client is not available.")
return
try:
response = client.scan(
TableName=TABLE_NAME,
Limit=10 # Example limit
)
print("Scan successful. Items:")
for item in response.get('Items', []):
print(item)
except ClientError as e:
print(f"Error scanning table: {e}")
if __name__ == "__main__":
dynamodb_client = get_dynamodb_client_cross_account()
if dynamodb_client:
scan_dynamodb_table(dynamodb_client)
Key Takeaway:
The core mechanism is the sts:AssumeRole API call. When an entity in Account A calls sts:AssumeRole with the ARN of a role in Account B (and has permission to do so), AWS Security Token Service (STS) issues temporary security credentials. These temporary credentials have the permissions granted by the role in Account B and are valid for a limited time (default 1 hour, max 12 hours). The application then uses these temporary credentials to interact with DynamoDB in Account B.
The most surprising truth about this setup is that the DynamoDB client library on Account A’s side doesn’t inherently know it’s talking to another account. It simply uses the credentials it’s given. If those credentials are for a role that has permissions to access a DynamoDB table in Account B, the request succeeds as if the application were running natively in Account B, provided the network path is correct.
The next step you’ll likely encounter is needing to grant write access, or perhaps more granular permissions like only allowing PutItem on a specific partition key.