Back to blog
AWSBest PracticesCloud SecurityIdentity & Access

Secrets Manager Secret Not Using a Customer-Managed Key (CMK)

Learn why AWS Secrets Manager secrets should use a customer-managed KMS key instead of the default, plus CLI, Terraform, and policy-as-code fixes.

TL;DR

This check flags Secrets Manager secrets encrypted with the default AWS-managed key (aws/secretsmanager) instead of a customer-managed KMS key (CMK). Without a CMK you lose fine-grained access control, key rotation policy, and cross-account audit visibility. Fix it by creating a CMK and re-pointing the secret with aws secretsmanager update-secret --kms-key-id.

Secrets Manager encrypts every secret at rest, so it is easy to assume the job is done. But the which key question matters more than the is it encrypted question. When you create a secret without specifying a key, AWS quietly falls back to the default service-managed key. That key works, but it strips you of most of the controls that make encryption meaningful from a security standpoint.

This Lensix check, ssm_secretscmk, identifies any Secrets Manager secret still relying on the AWS-managed key so you can move it to a customer-managed key (CMK) you actually govern.


What this check detects

Every secret in AWS Secrets Manager has a KmsKeyId attribute. The check inspects that value across your secrets and flags any where the key resolves to the default AWS-managed key, aws/secretsmanager, rather than a CMK in your account.

You can see the distinction yourself:

aws secretsmanager describe-secret \
  --secret-id prod/payments/db-credentials \
  --query 'KmsKeyId'

If the result is null or contains aws/secretsmanager, the secret is using the AWS-managed key and will be flagged. A passing secret returns an ARN pointing to a key in your own account, like arn:aws:kms:us-east-1:123456789012:key/abcd1234-....

Note: "AWS-managed" and "customer-managed" are both KMS keys. The difference is ownership of the key policy. AWS-managed keys come with a policy you cannot edit, rotate on AWS's schedule, and do not let you scope access per principal. CMKs are keys you create, whose policy and rotation you fully control.


Why it matters

Encryption at rest protects you against one specific threat: someone reading the raw storage media or a leaked backup. The AWS-managed key handles that fine. The problem is everything else a key policy gives you.

You lose a second layer of access control

With a CMK, decrypting a secret requires two things: permission to call secretsmanager:GetSecretValue and permission to use the KMS key via kms:Decrypt. That second gate is independent of the secret's resource policy and IAM. With the AWS-managed key, the KMS key policy automatically grants decrypt access to any principal in the account that already has Secrets Manager permissions. The key stops being a meaningful boundary.

This matters in a real breach. If an attacker compromises a role with broad secretsmanager:* permissions, a CMK key policy that only allows a small set of service roles to decrypt can stop them cold. The AWS-managed key offers no such friction.

You cannot do cross-account sharing safely

Sharing a secret with another account requires a CMK. You cannot grant another account access to a secret encrypted with the AWS-managed key, because you cannot modify that key's policy. Teams that try to share secrets across accounts hit this wall immediately, then sometimes work around it by copying secrets into multiple accounts, which spreads the blast radius.

You lose rotation and audit granularity

CMKs let you enable annual automatic key rotation and define exactly which CloudTrail events you care about per key. With the AWS-managed key, every secret in the account shares one key, so KMS-level audit logs blur together and you cannot rotate the encryption key on your own schedule.

Warning: A customer-managed key carries a cost the AWS-managed key does not. Each CMK is roughly $1 per month plus API request charges. Frequent GetSecretValue calls against many secrets generate kms:Decrypt requests that add up. Plan keys per environment or per sensitivity tier rather than one CMK per secret.


How to fix it

The fix is two steps: create (or pick) a CMK, then update the secret to use it. Re-encrypting an existing secret does not change its value or version, so applications keep reading the same secret without interruption.

Step 1: Create a customer-managed key

aws kms create-key \
  --description "Secrets Manager CMK - prod" \
  --tags TagKey=purpose,TagValue=secretsmanager \
  --query 'KeyMetadata.KeyId' \
  --output text

Give it a friendly alias so it is easy to reference:

aws kms create-alias \
  --alias-name alias/secretsmanager-prod \
  --target-key-id <key-id-from-previous-step>

Step 2: Scope the key policy

This is the whole point of using a CMK, so do not skip it. Restrict kms:Decrypt to the principals that genuinely need the secret. Here is a key policy that grants account admins management rights and limits decryption to a specific application role:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowKeyAdministration",
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::123456789012:root" },
      "Action": [
        "kms:Create*", "kms:Describe*", "kms:Enable*",
        "kms:List*", "kms:Put*", "kms:Update*",
        "kms:Revoke*", "kms:Disable*", "kms:Get*",
        "kms:Delete*", "kms:ScheduleKeyDeletion", "kms:CancelKeyDeletion"
      ],
      "Resource": "*"
    },
    {
      "Sid": "AllowSecretDecryptForApp",
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::123456789012:role/payments-app" },
      "Action": ["kms:Decrypt", "kms:DescribeKey"],
      "Resource": "*",
      "Condition": {
        "StringEquals": {
          "kms:ViaService": "secretsmanager.us-east-1.amazonaws.com"
        }
      }
    }
  ]
}

The kms:ViaService condition ensures the role can only use the key through Secrets Manager, not for arbitrary decryption.

Note: Enable automatic rotation on the CMK with aws kms enable-key-rotation --key-id <key-id>. This rotates the underlying key material yearly and is separate from Secrets Manager's own secret value rotation.

Step 3: Point the secret at the CMK

Danger: Before updating a production secret, confirm the application roles already have kms:Decrypt on the new key. If you switch keys and a role lacks decrypt permission, GetSecretValue will start failing immediately and your application can lose database or API access.

aws secretsmanager update-secret \
  --secret-id prod/payments/db-credentials \
  --kms-key-id alias/secretsmanager-prod

Verify the change:

aws secretsmanager describe-secret \
  --secret-id prod/payments/db-credentials \
  --query 'KmsKeyId'

Doing it in Terraform

If you manage secrets as code, set kms_key_id explicitly so new secrets never fall back to the default key:

resource "aws_kms_key" "secrets" {
  description             = "Secrets Manager CMK - prod"
  enable_key_rotation     = true
  deletion_window_in_days = 30
}

resource "aws_kms_alias" "secrets" {
  name          = "alias/secretsmanager-prod"
  target_key_id = aws_kms_key.secrets.key_id
}

resource "aws_secretsmanager_secret" "db_credentials" {
  name       = "prod/payments/db-credentials"
  kms_key_id = aws_kms_key.secrets.arn
}

Tip: Reference one shared CMK per environment in a module variable rather than hardcoding it in every secret resource. That keeps your key inventory small, your costs predictable, and your key policy in one place.


How to prevent it from happening again

Manual remediation fixes today's secrets. To stop new ones from drifting back to the default key, push the requirement left into your pipeline and policy controls.

Catch it in CI with policy-as-code

If you use Terraform, a Checkov or OPA rule can fail the build when a secret omits kms_key_id. Checkov ships with this check built in:

checkov -d . --check CKV_AWS_149

CKV_AWS_149 flags Secrets Manager secrets that are not encrypted with a CMK. Wire it into your pull request checks so unencrypted-by-CMK secrets never merge.

Enforce with a service control policy

An SCP can deny creating secrets without a CMK across an entire AWS Organization. This blocks the default-key path at the API level, even for engineers acting outside your IaC pipeline:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenySecretsWithoutCMK",
      "Effect": "Deny",
      "Action": "secretsmanager:CreateSecret",
      "Resource": "*",
      "Condition": {
        "Null": { "secretsmanager:KmsKeyId": "true" }
      }
    }
  ]
}

Warning: Test SCPs in a sandbox OU first. A deny rule like this will break any automation or console workflow that creates secrets without specifying a key, which can surprise teams that relied on the default behavior.

Run the check continuously

SCPs and CI gates only cover the paths you control. Lensix runs ssm_secretscmk on a schedule against your live accounts, so a secret created through an unmanaged path or an exception still gets surfaced. Continuous detection is what closes the gap between policy intent and actual state.


Best practices

  • One CMK per environment or sensitivity tier. Avoid a single shared key for everything and avoid a key per secret. Group by blast radius: a prod key, a staging key, maybe a separate key for the most sensitive secrets.
  • Always scope the key policy. A CMK with a wide-open policy gives you the cost of a CMK and the security of the default key. The kms:ViaService condition and explicit principal lists are where the value lives.
  • Enable key rotation. Turn on annual KMS key rotation, and separately configure Secrets Manager rotation for the secret values themselves. They solve different problems.
  • Audit decryption activity. CloudTrail logs every kms:Decrypt call against your CMK. Alert on decryption from unexpected principals or regions, which is a strong signal of credential misuse.
  • Bake the key into your modules. Make kms_key_id a required input in your secrets module so omitting it is a build error, not a silent fallback.

Moving from the AWS-managed key to a properly scoped CMK is a small change with an outsized payoff. You gain a real second access boundary, the ability to share secrets across accounts, and per-key audit and rotation control, all for about a dollar a month per key. For anything touching production credentials, that is an easy trade.