Mastering AWS Cross-Account Secrets with Terraform & KMS
Source: Dev.to
The Multi-Account Reality Check
In modern cloud architecture, multi‑account strategies are the norm. We separate development from production, and often centralize shared services into their own hubs.
A very common scenario is having a central Security or Shared Services AWS account holding sensitive database credentials in AWS Secrets Manager, which need to be accessed by a Lambda function running in a completely different Workload account.
It sounds simple on paper:
- Create the secret in Account A.
- Attach a resource policy to the secret allowing Account B to read it.
- Give the Lambda in Account B IAM permission to read the secret.
You deploy your Terraform, invoke your Lambda, and… fail. You get an AccessDeniedException or a vague KMS error.
When debugging cross‑account access failures with Secrets Manager (or S3, or SQS), 90 % of the time developers focus on IAM policies. But when secrets are involved, you are fighting a two‑front war: authentication (IAM) and cryptography (KMS).
By default, when you create a secret in Secrets Manager without specifying encryption settings, AWS encrypts it using the AWS‑managed key for the service (alias/aws/secretsmanager).
The trap: You cannot modify the key policy of an AWS‑managed key. It is designed to only trust principals within the same account. No matter how wide‑open you make your IAM policies, the external account will never be allowed to decrypt the payload. The door is unlocked, but the box is welded shut.
To enable cross‑account access, you must take control of encryption. Create a Customer Managed Key (CMK) in KMS and explicitly trust the external account.
Terraform Implementation
Variables and Data Sources
variable "external_consumer_account_id" {
description = "The AWS Account ID that needs read access to the secrets."
type = string
# Example: "123456789012"
}
# Helper to get current account ID for policy definitions
data "aws_caller_identity" "current" {}
KMS Key and Alias
resource "aws_kms_key" "cross_account_secrets_key" {
description = "KMS Key for cross-account RDS credentials"
deletion_window_in_days = 30
enable_key_rotation = true
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "EnableIAMUserPermissionsForCurrentAccount"
Effect = "Allow"
Principal = {
AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"
}
Action = "kms:*"
Resource = "*"
},
{
Sid = "AllowExternalAccountToDecrypt"
Effect = "Allow"
Principal = {
AWS = "arn:aws:iam::${var.external_consumer_account_id}:root"
}
Action = [
"kms:Decrypt",
"kms:DescribeKey"
]
Resource = "*"
}
]
})
}
resource "aws_kms_alias" "secrets_key_alias" {
name = "alias/cross-account-secrets-key"
target_key_id = aws_kms_key.cross_account_secrets_key.key_id
}
Secrets Manager Secret
resource "aws_secretsmanager_secret" "database_credentials" {
name = "prod/rds/read-replica-creds"
description = "Database credentials accessible by workload accounts"
# CRITICAL: Force the use of our custom KMS key
kms_key_id = aws_kms_key.cross_account_secrets_key.id
}
Secret Resource Policy
resource "aws_secretsmanager_secret_policy" "database_credentials_policy" {
secret_arn = aws_secretsmanager_secret.database_credentials.arn
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "AllowExternalRead"
Effect = "Allow"
Principal = {
AWS = "arn:aws:iam::${var.external_consumer_account_id}:root"
}
Action = "secretsmanager:GetSecretValue"
Resource = "*"
}
]
})
}
Required Permissions
For a successful cross‑account secret retrieval, three gates must open simultaneously:
- KMS Key Policy (source account) must trust the destination account to
Decrypt. - Secret Resource Policy (source account) must trust the destination account to
GetSecretValue. - IAM Role attached to the Lambda/EC2 (destination account) must have permissions to perform both actions on the respective ARNs.
Using default AWS keys is the most common trap in multi‑account architectures. By shifting to Customer Managed Keys and handling policy definitions explicitly in Terraform, you ensure secure, repeatable cross‑account access.