Sometimes you need a Lambda function or EC2 instance in one AWS account to read a secret stored in a different account. This is a common setup in multi-account environments — you might keep shared credentials (database passwords, API keys) in a central account and have workloads in other accounts pull them as needed.
This guide covers how to set up cross-account access to AWS Secrets Manager using the AssumeRole pattern. We’ll configure both accounts, handle KMS encryption, and test it with Python and the AWS CLI.
How It Works
Two accounts are involved:
- Account A (111111111111) — owns the secret in Secrets Manager
- Account B (222222222222) — has the EC2 instance or Lambda that needs the secret
The recommended approach is the AssumeRole pattern: create an IAM role in Account A that Account B can assume. The flow goes like this:
- Account B’s Lambda/EC2 calls
sts:AssumeRoleto assume a role in Account A - STS returns temporary credentials scoped to Account A
- Using those credentials, the code calls
secretsmanager:GetSecretValuein Account A - The secret is returned
You could also do this with just a resource-based policy on the secret (no role assumption), but the AssumeRole approach gives you better audit trails in CloudTrail and finer-grained control. If you’re new to AssumeRole, check out How to Set Up Cross-Account Access in AWS with AssumeRole for the fundamentals.
Prerequisites
- Two AWS accounts with admin or IAM management access
- A secret already created in Account A’s Secrets Manager
- AWS CLI configured — see How to Install AWS CLI v2 on Ubuntu 22.04 if needed
Step 1: Create the Cross-Account Role in Account A
In Account A, create an IAM role that Account B is allowed to assume. This role will have permissions to read the secret.
Trust Policy (Who Can Assume This Role)
This trust policy allows a specific role in Account B to assume the role. Replace the account ID and role name with your own:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::222222222222:role/AccountB-LambdaRole"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"aws:PrincipalOrgID": "o-your-org-id"
}
}
}
]
}
The aws:PrincipalOrgID condition is optional but recommended if both accounts are in the same AWS Organization. It prevents any account outside your org from assuming the role, even if the account ID somehow gets into the trust policy by mistake.
Permissions Policy (What the Role Can Do)
Attach this inline or managed policy to the role. It grants permission to read a specific secret:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "secretsmanager:GetSecretValue",
"Resource": "arn:aws:secretsmanager:ap-southeast-1:111111111111:secret:my-shared-secret-*"
}
]
}
Note the -* at the end of the secret ARN — Secrets Manager appends a random suffix to secret ARNs, so you need the wildcard to match it. You can find the full ARN in the Secrets Manager console.
Step 2: Update the KMS Key Policy (If Using a Custom Key)
If your secret is encrypted with the default aws/secretsmanager key, you can skip this step — AWS-managed keys handle cross-account access automatically when using the AssumeRole pattern (since you’re operating as a role within Account A).
But if you’re using a customer-managed KMS key (CMK) to encrypt the secret, you need to update the key policy to allow the cross-account role to decrypt:
{
"Sid": "AllowCrossAccountDecrypt",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111111111111:role/CrossAccountSecretsRole"
},
"Action": [
"kms:Decrypt",
"kms:DescribeKey"
],
"Resource": "*"
}
Add this as a statement to your existing KMS key policy in Account A. The Resource: "*" here is fine because this policy is attached to the key itself — it’s saying “allow this role to use this key,” not “allow access to all keys.”
Step 3: Configure the Role in Account B
The EC2 instance or Lambda function in Account B needs an IAM role with permission to assume the cross-account role in Account A.
Attach this policy to the Account B role (the one assigned to your Lambda or EC2 instance profile):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "arn:aws:iam::111111111111:role/CrossAccountSecretsRole"
}
]
}
This is the only permission Account B needs. The actual Secrets Manager access comes from the assumed role in Account A.
Step 4: Retrieve the Secret with Python
Here’s a Python function that assumes the cross-account role and retrieves the secret. This works the same in Lambda, on EC2, or locally with configured credentials:
import boto3
import json
import os
def get_cross_account_secret():
role_arn = os.environ["ROLE_ARN"]
secret_name = os.environ["SECRET_NAME"]
region = os.environ.get("AWS_REGION", "ap-southeast-1")
# Step 1: Assume the cross-account role
sts_client = boto3.client("sts")
assumed = sts_client.assume_role(
RoleArn=role_arn,
RoleSessionName="cross-account-secrets"
)
credentials = assumed["Credentials"]
# Step 2: Create a Secrets Manager client using the assumed credentials
sm_client = boto3.client(
"secretsmanager",
region_name=region,
aws_access_key_id=credentials["AccessKeyId"],
aws_secret_access_key=credentials["SecretAccessKey"],
aws_session_token=credentials["SessionToken"],
)
# Step 3: Get the secret
response = sm_client.get_secret_value(SecretId=secret_name)
secret = json.loads(response["SecretString"])
return secret
if __name__ == "__main__":
secret = get_cross_account_secret()
print("Secret keys:", list(secret.keys()))
Set the environment variables before running:
export ROLE_ARN="arn:aws:iam::111111111111:role/CrossAccountSecretsRole"
export SECRET_NAME="my-shared-secret"
python3 get_secret.py
The script prints the secret’s keys (not the values) to verify it works without exposing anything in logs. In production, you’d obviously use the values directly in your application instead of printing them.
Testing with the AWS CLI
You can also test this flow from the CLI without writing any code. First, assume the role:
aws sts assume-role \
--role-arn "arn:aws:iam::111111111111:role/CrossAccountSecretsRole" \
--role-session-name "test-session"
This returns temporary credentials. Export them:
export AWS_ACCESS_KEY_ID="returned-access-key"
export AWS_SECRET_ACCESS_KEY="returned-secret-key"
export AWS_SESSION_TOKEN="returned-session-token"
Now fetch the secret:
aws secretsmanager get-secret-value \
--secret-id "my-shared-secret" \
--region ap-southeast-1
If you get the secret back, your cross-account setup is working. Don’t forget to unset the environment variables after testing so you switch back to your default credentials:
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN
Troubleshooting
AccessDeniedException on AssumeRole
Check two things: the trust policy on the Account A role must list Account B’s role ARN as a principal, and Account B’s role must have sts:AssumeRole permission targeting the Account A role ARN. Both sides need to agree.
AccessDeniedException on GetSecretValue
The assumed role probably doesn’t have secretsmanager:GetSecretValue permission, or the secret ARN in the policy doesn’t match. Double-check the wildcard suffix (-*) on the ARN.
KMS Decrypt Error
If you see AccessDeniedException mentioning KMS, the secret uses a customer-managed key and the key policy is missing the decrypt permissions. Go back to Step 2 and add the KMS policy statement.
Summary of What Goes Where
| Component | Account | What It Does |
|---|---|---|
| Cross-account IAM role | A | Allows Account B to assume it; has permission to read the secret |
| KMS key policy update | A | Allows the cross-account role to decrypt (only if using CMK) |
| Lambda/EC2 role policy | B | Allows sts:AssumeRole targeting the Account A role |
| Application code | B | Assumes role, then calls GetSecretValue with temporary credentials |
Conclusion
Cross-account Secrets Manager access comes down to three pieces: a role in Account A that Account B can assume, the right permissions on that role, and KMS key access if you’re using a customer-managed key. Once it’s set up, the actual code is a handful of boto3 calls.
If you’re working on more cross-account patterns, How to Copy S3 Bucket Objects Across AWS Accounts uses a similar AssumeRole setup for S3. And if you need to send emails cross-account, check out How to Send Email Using AWS SES with Cross-Account Secrets Manager — it builds directly on what we covered here.


