Skip to content

Secrets Manager secrets should be encrypted using CMK

The default aws/secretsmanager key encrypts secrets adequately, but you cannot control its key policy, disable it, or restrict which principals can use it. A customer-managed CMK lets you define granular kms:Decrypt permissions, enforce separation of duties between secret administrators and secret consumers, and revoke access instantly by modifying the key policy or disabling the key.

CMKs also give you CloudTrail visibility into every Decrypt call against your secrets, tied to a key you own. That matters during incident response when you need to determine exactly which identity accessed a secret and when.

Retrofit consideration

Changing the KMS key on an existing secret re-encrypts all secret versions with the new CMK immediately. Any consumer without kms:Decrypt on the new key loses access at that moment. Set up key policy grants before you make the switch.

Implementation

Choose the approach that matches how you manage Terraform.

Use the compliance.tf module to enforce this control by default. See get started with compliance.tf.

module "secrets_manager" {
  source  = "pcidss.compliance.tf/terraform-aws-modules/secrets-manager/aws"
  version = ">=2.0.0"

  name          = "abc123"
  secret_string = "${jsonencode({ "pofix" : "example" })}"
}

module "secrets_manager" {
  source  = "hipaa.compliance.tf/terraform-aws-modules/secrets-manager/aws"
  version = ">=2.0.0"

  name          = "abc123"
  secret_string = "${jsonencode({ "pofix" : "example" })}"
}

module "secrets_manager" {
  source  = "nist80053.compliance.tf/terraform-aws-modules/secrets-manager/aws"
  version = ">=2.0.0"

  name          = "abc123"
  secret_string = "${jsonencode({ "pofix" : "example" })}"
}

module "secrets_manager" {
  source  = "nydfs23.compliance.tf/terraform-aws-modules/secrets-manager/aws"
  version = ">=2.0.0"

  name          = "abc123"
  secret_string = "${jsonencode({ "pofix" : "example" })}"
}

module "secrets_manager" {
  source  = "cccsmedium.compliance.tf/terraform-aws-modules/secrets-manager/aws"
  version = ">=2.0.0"

  name          = "abc123"
  secret_string = "${jsonencode({ "pofix" : "example" })}"
}

module "secrets_manager" {
  source  = "eugmpannex11.compliance.tf/terraform-aws-modules/secrets-manager/aws"
  version = ">=2.0.0"

  name          = "abc123"
  secret_string = "${jsonencode({ "pofix" : "example" })}"
}

module "secrets_manager" {
  source  = "cfrpart11.compliance.tf/terraform-aws-modules/secrets-manager/aws"
  version = ">=2.0.0"

  name          = "abc123"
  secret_string = "${jsonencode({ "pofix" : "example" })}"
}

module "secrets_manager" {
  source  = "nistcsfv11.compliance.tf/terraform-aws-modules/secrets-manager/aws"
  version = ">=2.0.0"

  name          = "abc123"
  secret_string = "${jsonencode({ "pofix" : "example" })}"
}

module "secrets_manager" {
  source  = "pcidssv321.compliance.tf/terraform-aws-modules/secrets-manager/aws"
  version = ">=2.0.0"

  name          = "abc123"
  secret_string = "${jsonencode({ "pofix" : "example" })}"
}

If you use terraform-aws-modules/secrets-manager/aws, set the right module inputs for this control. You can later migrate to the compliance.tf module with minimal changes because it is compatible by design.

module "secrets_manager" {
  source  = "terraform-aws-modules/secrets-manager/aws"
  version = ">=2.0.0"

  name          = "abc123"
  secret_string = "${jsonencode({ "pofix" : "example" })}"
}

Use AWS provider resources directly. See docs for the resources involved: aws_secretsmanager_secret.

resource "aws_secretsmanager_secret" "this" {
  name = "pofix-abc123"

  kms_key_id = "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012"
}

What this control checks

The aws_secretsmanager_secret resource accepts a kms_key_id argument. The control passes when kms_key_id is set to the ARN or ID of a CMK you manage. It fails when kms_key_id is omitted, left empty, or set to an alias that resolves to the AWS-managed key (e.g., alias/aws/secretsmanager). Reference the key as aws_kms_key.example.arn or via a data source that resolves to a CMK you control. The CMK's key policy must also grant kms:Decrypt and kms:GenerateDataKey to any principals that read or write secret values.

Common pitfalls

  • Omitting kms_key_id silently uses AWS-managed key

    Omit kms_key_id on aws_secretsmanager_secret and Terraform creates the secret without complaint, defaulting to aws/secretsmanager. It passes AWS's own default encryption checks, which is exactly why it's easy to miss. Always set kms_key_id explicitly.

  • KMS key policy missing Secrets Manager service principal

    If the CMK key policy doesn't grant kms:Decrypt and kms:GenerateDataKey* to the principals reading and writing secrets, rotation lambdas and application roles will fail with AccessDeniedException. Depending on your key policy design and rotation pattern, kms:CreateGrant may also be required for delegated access.

  • Cross-account secrets require explicit key grants

    Cross-account access to a secret doesn't automatically include access to its CMK. The consuming account's principals need an explicit kms:Decrypt grant, or SecretString retrieval will fail even with a valid secretsmanager:GetSecretValue resource policy in place.

  • Disabled or pending-deletion CMK breaks secret access

    A CMK disabled via is_enabled = false or scheduled for deletion makes all secrets encrypted with it immediately inaccessible. After deletion completes, those secrets are permanently unrecoverable. Run kms_cmk_unused alongside this control to catch orphaned keys before they reach that point.

Audit evidence

Config rule evaluation results showing each aws_secretsmanager_secret as COMPLIANT confirm a CMK is in use. The output of aws secretsmanager describe-secret per secret should show a KmsKeyId value that is a CMK ARN, not the AWS-managed key. CloudTrail logs with Decrypt and GenerateDataKey events against the CMK confirm encryption operations are routing through the expected key.

For PCI DSS and HIPAA reviews, include a KMS key policy document or IAM policy export showing least-privilege access to the CMK. Both frameworks ask for documented access controls around encryption keys.

Framework-specific interpretation

PCI DSS v4.0: For secrets containing cardholder data environment credentials, Requirement 3.5 asks for documented key management procedures and restricted access to encryption keys. An explicit CMK key policy covers both. Requirement 3.6 adds cryptoperiod requirements, which you address by enabling automatic rotation on the CMK.

HIPAA Omnibus Rule 2013: The encryption implementation specifications at 45 CFR 164.312(a)(2)(iv) and 164.312(e)(2)(ii) are addressable, meaning you document why your chosen mechanism is reasonable and appropriate. A CMK for Secrets Manager is a strong answer to that question: you can show auditable key policies, CloudTrail decrypt events, and the ability to revoke access without touching IAM. That independent revocation path also functions as a secondary access control layer that HIPAA reviewers look for.

NIST SP 800-53 Rev 5: SC-12 and SC-28 both apply. SC-12 requires the organization to establish and maintain control over cryptographic keys protecting sensitive data; a CMK with a documented key policy satisfies that directly. SC-28 covers protection of information at rest. CloudTrail decrypt logs against the CMK feed the audit trail requirements under AU-2 and AU-3.

Tool mappings

Use these identifiers to cross-reference this control across tools, reports, and evidence.

  • Compliance.tf Control: secretsmanager_secret_encrypted_with_kms_cmk

  • AWS Config Managed Rule: SECRETSMANAGER_USING_CMK

  • Checkov Check: CKV_AWS_149

  • Powerpipe Control: aws_compliance.control.secretsmanager_secret_encrypted_with_kms_cmk

  • KICS Queries: a2f548f2-188c-4fff-b172-e9a6acb216bd, b0d3ef3f-845d-4b1b-83d6-63a5a380375f

  • Trivy Check: AWS-0098

Last reviewed: 2026-03-09