AWS DMS cross-account Secrets Manager architecture — two AWS accounts connected via IAM role and KMS
Cross-account credentials flow: DMS endpoint in the DataLake account fetches secrets from the Remote account's Secrets Manager via an assumed IAM role.

The Scenario

You have AWS DMS running in a DataLake account — where your analytics and replication infrastructure lives — and you need it to connect to a source database whose credentials are stored in a Remote account's Secrets Manager. Rather than duplicating the secret or hardcoding credentials into the DMS endpoint, you want DMS to fetch the secret directly from the remote account at runtime.

This is the cleaner pattern: credentials stay owned by the team that owns the database, rotation continues to work, and the DataLake account never sees the plaintext password in its own configuration.

"Credentials stay owned by the team that owns the database — rotation works, no plaintext duplication."

Prerequisite: Network Connectivity

Before anything else, the DMS replication instance must be able to reach the remote database over the network. This typically means VPC peering, Transit Gateway, or a similar arrangement between the two accounts, plus the appropriate security group and route table entries.

None of the IAM and secret-sharing configuration below will help if the TCP connection to the database can't be established. Sort the networking out first, verify with a basic connectivity test from the DMS subnet, then proceed.

What Needs to Be in Place

On the Remote account (where the secret resides)

The secret must be encrypted with a customer-managed KMS key. The default aws/secretsmanager key cannot be shared across accounts — if your secret is currently using the AWS-managed key, you'll need to recreate it with a CMK before going further. This is the single most common gotcha.

The secret's resource policy must grant secretsmanager:GetSecretValue and secretsmanager:DescribeSecret to the IAM role in the DataLake account that DMS will assume.

The KMS key policy must grant kms:Decrypt and kms:DescribeKey to the same DataLake IAM role. Both policies need to allow the role — granting one without the other is another frequent source of confusion, because the resulting error message points at the secret rather than the key.

The secret value itself must be a JSON object containing the keys DMS expects: username, password, host, and port. DMS reads these specific field names; if the secret is structured differently, the endpoint will fail to connect.

On the DataLake account (where DMS resides)

You need an IAM role trusted by the DMS service (dms.amazonaws.com as the principal in the trust policy) with permissions to:

  • Call GetSecretValue and DescribeSecret against the remote secret's ARN
  • Call Decrypt against the remote KMS key's ARN

The DMS endpoint is then configured to assume this role when retrieving credentials. In the endpoint configuration, you point at the remote secret ARN and specify the IAM role ARN under "Secrets Manager access role."

Database-side requirements

The user defined in the secret must actually exist on the database and have sufficient privileges for whatever DMS is doing — typically CDC, which on most engines means replication-level grants (e.g., REPLICATION SLAVE and REPLICATION CLIENT on MySQL, or the equivalent logical replication permissions on PostgreSQL). The cleanest IAM setup in the world won't help if the database user can't read the binlog.

Order of Steps

Assuming network connectivity is already in place:

  1. Create the IAM role on the DataLake account. You need its ARN before you can reference it from the remote account's policies.
  2. On the Remote account, update the secret's resource policy and the KMS key policy to allow access to the IAM role from step 1.
  3. Create the DMS endpoint on the DataLake account, pointing at the remote secret ARN and referencing the IAM role from step 1.

Doing it in this order avoids the chicken-and-egg problem of policies referencing principals that don't exist yet, and makes troubleshooting easier — by the time you create the endpoint, every dependency it needs is already in place.

Policy Reference

The three policy artifacts below are the minimum set needed to wire AWS DMS in one account to a credentials secret in another. Account IDs, region, secret name, and KMS key ID are placeholders; substitute your own.

Account roles in the examples: 111111111111 is the DataLake account (where DMS runs), 222222222222 is the Remote account (where the secret and database live). Region is eu-west-1.

1. IAM Role on the DataLake Account

This role is assumed by the DMS service principal when the replication endpoint connects to the source database. The trust policy is scoped with aws:SourceAccount and aws:SourceArn to defend against the confused-deputy pattern — without these conditions, any DMS endpoint in any AWS account could in principle assume the role.

Permissions are split into two statements (one for Secrets Manager, one for KMS) so that CloudTrail AccessDenied events make the failed permission obvious without log correlation.

CloudFormation — DataLake account YAML
AWSTemplateFormatVersion: "2010-09-09"
Description: IAM role on DataLake account, assumed by DMS, granting access
  to a Secrets Manager secret and KMS CMK in a remote account.

Resources:
  DmsCrossAccountSecretRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: dms-cross-account-secret-access
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: dms.amazonaws.com
            Action: sts:AssumeRole
            Condition:
              StringEquals:
                aws:SourceAccount: !Ref AWS::AccountId
              ArnLike:
                aws:SourceArn: !Sub arn:aws:dms:${AWS::Region}:${AWS::AccountId}:endpoint:*
      Policies:
        - PolicyName: ReadRemoteSecret
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Sid: GetRemoteSecret
                Effect: Allow
                Action:
                  - secretsmanager:GetSecretValue
                  - secretsmanager:DescribeSecret
                Resource: arn:aws:secretsmanager:eu-west-1:222222222222:secret:prod/source-db/credentials-aB3xYz
              - Sid: DecryptWithRemoteKmsKey
                Effect: Allow
                Action:
                  - kms:Decrypt
                  - kms:DescribeKey
                Resource: arn:aws:kms:eu-west-1:222222222222:key/8d4f2a1c-7b6e-4f3d-9c2a-1e5b7d8f9a0b

2. Secret Resource Policy on the Remote Account

Cross-account access to a Secrets Manager secret requires both an IAM identity policy on the caller side (covered above) and a resource policy on the secret itself. Both must allow the action — IAM evaluation in cross-account scenarios is a logical AND, not OR.

The Principal here is the full ARN of the DataLake role, not the DataLake account root. Granting to root would let any principal in the DataLake account read the secret if their identity policy allowed it, which is broader than needed.

Secret resource policy — Remote account JSON
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowDataLakeRoleToReadSecret",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::111111111111:role/dms-cross-account-secret-access"
      },
      "Action": [
        "secretsmanager:GetSecretValue",
        "secretsmanager:DescribeSecret"
      ],
      "Resource": "arn:aws:secretsmanager:eu-west-1:222222222222:secret:prod/source-db/credentials-aB3xYz"
    }
  ]
}

3. KMS Key Policy on the Remote Account

Because the secret is encrypted with a customer-managed key, Secrets Manager calls KMS Decrypt on the caller's behalf when GetSecretValue runs. The DataLake role therefore needs Decrypt + DescribeKey on the CMK in addition to the Secrets Manager permissions.

The kms:ViaService condition is recommended belt-and-braces: it ensures the key can only be used to decrypt through Secrets Manager in this region, not through any other AWS service that might happen to call KMS with the same credentials.

The first statement preserves root-account administrative access to the key. Removing it is a common foot-gun — once gone, only AWS Support tickets can recover key administration.

KMS key policy — Remote account JSON
{
  "Version": "2012-10-17",
  "Id": "key-policy-source-db-credentials",
  "Statement": [
    {
      "Sid": "EnableRootPermissions",
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::222222222222:root" },
      "Action": "kms:*",
      "Resource": "*"
    },
    {
      "Sid": "AllowDataLakeRoleToDecrypt",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::111111111111:role/dms-cross-account-secret-access"
      },
      "Action": [
        "kms:Decrypt",
        "kms:DescribeKey"
      ],
      "Resource": "*",
      "Condition": {
        "StringEquals": {
          "kms:ViaService": "secretsmanager.eu-west-1.amazonaws.com"
        }
      }
    }
  ]
}

Applying and Verifying

The two resource-side policies can be applied via the CLI from the Remote account. The final command verifies end-to-end access from the DataLake side once the role has been provisioned and the trust relationship configured.

CLI commands bash
# Attach the resource policy to the secret (run in the Remote account)
aws secretsmanager put-resource-policy \
  --secret-id prod/source-db/credentials \
  --resource-policy file://secret-resource-policy.json \
  --region eu-west-1

# Update the KMS key policy (run in the Remote account)
aws kms put-key-policy \
  --key-id 8d4f2a1c-7b6e-4f3d-9c2a-1e5b7d8f9a0b \
  --policy-name default \
  --policy file://kms-key-policy.json \
  --region eu-west-1

# Verify the DataLake role can fetch the secret
# (run from DataLake account with credentials of the DMS role, via STS AssumeRole)
aws secretsmanager get-secret-value \
  --secret-id arn:aws:secretsmanager:eu-west-1:222222222222:secret:prod/source-db/credentials-aB3xYz \
  --region eu-west-1

Common Failure Modes

AccessDeniedException on Secrets Manager only

Symptom: DMS endpoint test fails with an AccessDenied referencing GetSecretValue. The IAM identity policy is fine, the KMS key policy is fine, but the secret's resource policy is either missing or refers to the wrong principal ARN. Re-check that the Principal in the resource policy is the exact role ARN (case-sensitive, no trailing whitespace).

AccessDeniedException on KMS Decrypt

Symptom: GetSecretValue returns AccessDenied with a KMS reference in the error message. The secret's resource policy is allowing the caller, but the KMS key policy is not. The fix is to grant Decrypt + DescribeKey on the CMK to the same role ARN.

Endpoint test passes but replication tasks fail

Symptom: the DMS endpoint connection test succeeds, but the replication task fails to start or stalls at "starting." The IAM/Secrets/KMS chain is correctly configured — the problem is downstream. Check that the database user inside the secret actually has CDC privileges (REPLICATION SLAVE + REPLICATION CLIENT on MySQL, the appropriate logical replication grants on PostgreSQL) and that the database itself has CDC prerequisites enabled (binlog_format=ROW on MySQL, wal_level=logical on PostgreSQL).

Default KMS key was used

Symptom: cannot add a cross-account principal to the key policy at all — the AWS console doesn't expose the policy for editing. The secret was encrypted with the AWS-managed aws/secretsmanager key, which cannot be shared cross-account. Re-encrypt the secret under a customer-managed CMK; the secret value itself stays the same, but the encryption layer changes.

Closing Thought

The pattern looks like a lot of moving parts the first time you set it up, but once you've done it once it becomes routine. The discipline it enforces — credentials owned by the team that owns the database, no plaintext duplication, proper KMS-based encryption boundaries — is worth the upfront effort, especially in regulated industries where audit trails on credential access matter.