Cross-Account Secret Access with AssumeRole

Two AWS accounts, one secret: the shared-services account owns a database credential, and a workload in another account needs to read it. Copying the secret creates a second thing to rotate and leak. The correct pattern is to assume a scoped IAM role in the secret’s account with STS, read the secret with the temporary credentials, and let them expire. This extends AWS Secrets Manager integration and the secrets management discipline.

Problem 1: sharing a long-lived access key

# ANTI-PATTERN — a static key from the other account, baked into config
import boto3
client = boto3.client(
    "secretsmanager",
    aws_access_key_id="AKIA...",       # long-lived, cross-account, a durable liability
    aws_secret_access_key="...",
)

A shared static key is a permanent credential in a second account — exactly what you are trying to avoid.

Key APIs in Problem 1: sharing a long-lived access key The main identifiers used in Problem 1: sharing a long-lived access key AKIAboto3.clientclient
The APIs and identifiers this section uses.

Problem 2: copying the secret into your account

Duplicating the secret means two copies to rotate in lockstep; miss one and services drift onto a stale credential. Keep a single source of truth and grant access instead.

Problem 2: copying the secret into your account The core point of Problem 2: copying the secret into your account copying the secret into your acco…Duplicating the secret means two copies to rotate in lockstep; miss one and service…
The key point of this section at a glance.

Secure implementation

Assume a role scoped to the one secret, then build a client from the temporary credentials.

# cross_account.py
import boto3
from pydantic import SecretStr


def read_cross_account_secret(role_arn: str, secret_id: str, region: str) -> SecretStr:
    sts = boto3.client("sts")
    creds = sts.assume_role(
        RoleArn=role_arn,                 # role in the secret's account
        RoleSessionName="config-loader",  # shows up in CloudTrail for audit
    )["Credentials"]

    sm = boto3.client(
        "secretsmanager",
        region_name=region,
        aws_access_key_id=creds["AccessKeyId"],
        aws_secret_access_key=creds["SecretAccessKey"],
        aws_session_token=creds["SessionToken"],   # temporary, auto-expiring
    )
    value = sm.get_secret_value(SecretId=secret_id)["SecretString"]
    return SecretStr(value)

The role’s trust policy names your account; its permission policy grants only secretsmanager:GetSecretValue on that one secret ARN.

Key APIs in Secure implementation The main identifiers used in Secure implementation SecretStrRoleArnRoleSessionNameCloudTrailCredentialsAccessKeyId
The APIs and identifiers this section uses.

Gotchas & version-specific behaviour

  • The assumed role’s permission policy should scope to the exact secret ARN, not *.
  • RoleSessionName appears in CloudTrail, so use a descriptive name for auditing.
  • Temporary credentials expire; cache the fetched secret in memory with a TTL rather than re-assuming per request.
  • The secret’s resource policy in the owning account may also need to allow the role.
  • Prefer an OIDC or instance role as the base identity so no static key exists anywhere.
Gotchas & version-specific behaviour Key points from Gotchas & version-specific behaviour The assumed role'spermission…RoleSessionName appears inCl…Temporary credentialsexpire;…The secret's resourcepolicy …Prefer an OIDC or instancero…
The essentials of Gotchas & version-specific behaviour.

Production parity checklist

  • Grant secretsmanager:GetSecretValue on the specific secret ARN only.
  • Use STS AssumeRole, never a shared static access key.
  • Give the role a short maximum session duration.
  • Cache the secret with a TTL and wrap it in SecretStr.
  • Alarm on AssumeRole failures and unexpected GetSecretValue callers in CloudTrail.
Production parity checklist Key points from Production parity checklist Grantsecretsmanager:GetSecre…Use STS AssumeRole, never as…Give the role a shortmaximum…Cache the secret with a TTLa…Alarm on AssumeRolefailures …
Every item to tick off for Production parity checklist.

Frequently asked questions

How do I read a Secrets Manager secret from another AWS account?

Assume a role in the secret’s account with STS AssumeRole, then create a Secrets Manager client from the temporary credentials and call get_secret_value. The role’s trust policy names your account and its permission policy grants secretsmanager:GetSecretValue on the specific secret.

Should I copy the secret into my own account instead?

No. Copying creates a second place to rotate and a second thing to leak. Cross-account AssumeRole keeps one source of truth and grants time-limited, scoped access instead.

How long do assumed-role credentials last?

STS returns temporary credentials whose lifetime you request, from 15 minutes up to the role’s maximum session duration. Fetch the secret, cache it with a TTL, and let the STS credentials expire.

Key takeaways

Grant cross-account access with a scoped STS AssumeRole, not a copied secret or a shared static key; read the secret with the temporary credentials, cache it as SecretStr with a TTL, and let the session expire.