Back to blog
AWSBest PracticesCloud SecurityDatabasesIdentity & Access

DynamoDB Table Not Using CMK: Why Default Encryption Is Not Enough

Learn why DynamoDB tables should use a customer-managed KMS key instead of the default, plus CLI and Terraform steps to remediate and prevent drift.

TL;DR

This check flags DynamoDB tables encrypted with an AWS-owned or AWS-managed key instead of a customer-managed KMS key (CMK). Without a CMK you lose control over key policies, rotation, and audit visibility. Fix it by re-creating or migrating the table to use a CMK and setting SSESpecification to KMS with your own key ARN.

DynamoDB encrypts all tables at rest by default, so it is easy to assume you are covered and move on. The catch is in the type of key doing the encrypting. By default, DynamoDB uses an AWS-owned key that lives entirely inside AWS, outside your account, with no key policy you can read or edit. For most workloads that is fine. For regulated data, multi-tenant isolation, or anything where you need to prove who can decrypt what, it is not enough.

The dynamodb_nocmk check identifies tables that are not using a customer-managed KMS key (CMK), so you can decide where stronger key control is actually required.


What this check detects

DynamoDB supports three server-side encryption options for data at rest:

  • AWS owned key — the default. The key is shared across AWS, never appears in your account, and you cannot view or manage it. No extra cost.
  • AWS managed key — a KMS key named aws/dynamodb that lives in your account but is created and controlled by AWS. You can see it in KMS and view CloudTrail usage, but you cannot edit its key policy or change its rotation schedule.
  • Customer managed key (CMK) — a KMS key you create and fully control: key policy, grants, rotation, enable/disable, and CloudTrail auditing.

The check fails when a table is using either the AWS owned key or the AWS managed aws/dynamodb key rather than a CMK that you control.

Note: Encryption at rest cannot be turned off for DynamoDB. The question is never "is it encrypted" but "whose key, and who can use it." This check is about key ownership and control, not whether encryption exists.


Why it matters

The difference between an AWS-managed key and a CMK comes down to control and provability. Here is where it bites in practice.

You cannot restrict who decrypts the data

With the AWS owned or managed key, any principal with IAM permissions on the table can read its contents. There is no separate key policy layer. With a CMK you get a second gate: even if someone has dynamodb:GetItem, they also need kms:Decrypt on your key. This is the foundation of true least privilege and is how you isolate sensitive tables from broad read roles.

You cannot prove key access in an audit

Compliance frameworks like PCI DSS, HIPAA, and SOC 2 often require evidence that you control the encryption keys protecting regulated data and can produce an access log. The AWS owned key generates no CloudTrail events in your account at all. You literally cannot show an auditor who used it.

You cannot revoke access in an incident

Danger: With a CMK, disabling the key instantly makes the table unreadable, which is a powerful kill switch during a breach. With an AWS-owned or managed key you have no such control. If a role is compromised and you cannot quickly revoke its key access, your only lever is racing to revoke IAM permissions while data exfiltration is in progress.

You cannot control rotation

CMKs support automatic annual rotation that you enable and audit. The AWS owned key rotation is handled by AWS and is invisible to you, which is acceptable for low-sensitivity data but does not satisfy stricter key lifecycle requirements.


How to fix it

The remediation depends on whether the table already exists. DynamoDB lets you change the SSE type on a live table, so in most cases you do not need to re-create it.

Step 1: Create a customer-managed key

aws kms create-key \
  --description "CMK for DynamoDB encryption" \
  --tags TagKey=purpose,TagValue=dynamodb-encryption

# Give it a friendly alias
aws kms create-alias \
  --alias-name alias/dynamodb-cmk \
  --target-key-id <key-id-from-previous-output>

Enable automatic rotation so you are not managing key lifecycle by hand:

aws kms enable-key-rotation --key-id <key-id>

Step 2: Update the table to use the CMK

Warning: Switching SSE types triggers a background re-encryption of the table. The table stays available during this process, but it can take time on large tables and the table status will show UPDATING. Customer-managed keys also incur KMS request charges (roughly $0.03 per 10,000 requests) plus the monthly key cost, so high-throughput tables will see a small ongoing KMS bill.

aws dynamodb update-table \
  --table-name Orders \
  --sse-specification \
    Enabled=true,SSEType=KMS,KMSMasterKeyId=alias/dynamodb-cmk

Confirm the change once the table returns to ACTIVE:

aws dynamodb describe-table \
  --table-name Orders \
  --query 'Table.SSEDescription'

A correctly configured table reports "SSEType": "KMS" and a "KMSMasterKeyArn" pointing at your CMK rather than the aws/dynamodb alias.

Step 3: Lock down the key policy

A CMK only adds protection if its key policy is scoped. Grant decrypt rights only to the roles that legitimately read the table. Example policy statement for an application role:

{
  "Sid": "AllowDynamoDBServiceAndAppRole",
  "Effect": "Allow",
  "Principal": {
    "AWS": "arn:aws:iam::111122223333:role/orders-service"
  },
  "Action": [
    "kms:Decrypt",
    "kms:GenerateDataKey"
  ],
  "Resource": "*",
  "Condition": {
    "StringEquals": {
      "kms:ViaService": "dynamodb.us-east-1.amazonaws.com"
    }
  }
}

The kms:ViaService condition ensures the key can only be used through DynamoDB, not directly, which prevents a compromised role from decrypting data outside the intended path.

Doing it in Terraform

For new tables, set encryption from the start so you never inherit the default:

resource "aws_kms_key" "dynamodb" {
  description             = "CMK for DynamoDB tables"
  enable_key_rotation     = true
  deletion_window_in_days = 30
}

resource "aws_dynamodb_table" "orders" {
  name         = "Orders"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "order_id"

  attribute {
    name = "order_id"
    type = "S"
  }

  server_side_encryption {
    enabled     = true
    kms_key_arn = aws_kms_key.dynamodb.arn
  }
}

Tip: If you omit the kms_key_arn but set enabled = true, Terraform uses the AWS managed aws/dynamodb key, which still fails this check. Always pass an explicit kms_key_arn when you want a CMK.


How to prevent it from happening again

One-off remediation does not stick. The default behavior of DynamoDB is to use a non-CMK key, so without guardrails every new table drifts back to a failing state.

Catch it in CI with policy-as-code

Scan Terraform before it merges. With Checkov, the relevant control is CKV_AWS_119, which requires a CMK on DynamoDB tables:

checkov -d ./infra --check CKV_AWS_119

An equivalent OPA/Conftest rule for plan output:

deny[msg] {
  resource := input.resource_changes[_]
  resource.type == "aws_dynamodb_table"
  sse := resource.change.after.server_side_encryption[_]
  not sse.kms_key_arn
  msg := sprintf("Table %v must use a customer-managed KMS key", [resource.address])
}

Detect drift at runtime with AWS Config

The managed rule dynamodb-table-encryption-enabled confirms encryption, but to specifically require a CMK use a custom Config rule that inspects the SSEType and key ARN. Pair it with an EventBridge rule that alerts when a new table is created without a CMK.

Block the default with an SCP-style guardrail

You cannot fully forbid table creation by SSE type through a simple SCP, but you can require that any KMS operations against DynamoDB go through approved keys, and you can use a detective control plus auto-remediation to flag and fix tables shortly after creation. Lensix continuously evaluates dynamodb_nocmk across your accounts so drift surfaces without you building the plumbing yourself.


Best practices

  • Reserve CMKs for data that needs them. Not every table requires a CMK. Apply it to tables holding PII, payment data, credentials, or anything in a compliance scope. Low-sensitivity caches and ephemeral tables can stay on the default key.
  • Use one CMK per data classification, not per table. A key for "regulated" and another for "internal" keeps key policies manageable while still giving you separation. Avoid a sprawl of thousands of keys.
  • Always enable automatic rotation on CMKs so you satisfy rotation requirements without manual effort.
  • Scope key policies with kms:ViaService so the key can only be used through DynamoDB.
  • Set a deletion window of at least 30 days on CMKs. A deleted key permanently destroys access to every table it protects.
  • Audit key usage with CloudTrail. The whole point of a CMK is visibility, so build a dashboard or alert on unexpected Decrypt calls.

Danger: Never schedule deletion of a CMK that is still encrypting a live DynamoDB table. Once the key is deleted, the table data is unrecoverable, and DynamoDB will not be able to read or write items. Disable the key first and monitor for failures before considering deletion.

Encryption at rest is on by default, but default does not mean controlled. Moving sensitive DynamoDB tables to customer-managed keys gives you the access controls, audit trail, and revocation power that the default key simply cannot provide.