DynamoDB tables should have AWS KMS encryption enabled
DynamoDB tables are encrypted at rest by default with AWS-owned keys, but those keys do not appear in your AWS account, cannot be audited through CloudTrail, and cannot be rotated or revoked by you. Switching to an AWS KMS key (either the aws/dynamodb managed key or a customer-managed CMK) gives you visibility into key usage via CloudTrail, lets you define key policies, and lets you disable or schedule deletion of the key to render data unreadable.
For regulated workloads, auditors frequently require proof that encryption keys are under customer control. AWS-owned keys do not satisfy that requirement because you cannot demonstrate independent key management.
Retrofit consideration
Changing the encryption key type on an existing DynamoDB table triggers a re-encryption process. For large tables this can take significant time and may briefly increase read/write costs. Tables with global table replicas require the KMS key to be available in every replica region.
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 "dynamodb_table" {
source = "pcidss.compliance.tf/terraform-aws-modules/dynamodb-table/aws"
version = ">=5.0.0"
attributes = [
{
name = "id"
type = "S"
}
]
hash_key = "id"
name = "abc123"
server_side_encryption_kms_key_arn = "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012"
}
module "dynamodb_table" {
source = "hipaa.compliance.tf/terraform-aws-modules/dynamodb-table/aws"
version = ">=5.0.0"
attributes = [
{
name = "id"
type = "S"
}
]
hash_key = "id"
name = "abc123"
server_side_encryption_kms_key_arn = "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012"
}
module "dynamodb_table" {
source = "gdpr.compliance.tf/terraform-aws-modules/dynamodb-table/aws"
version = ">=5.0.0"
attributes = [
{
name = "id"
type = "S"
}
]
hash_key = "id"
name = "abc123"
server_side_encryption_kms_key_arn = "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012"
}
module "dynamodb_table" {
source = "nist80053.compliance.tf/terraform-aws-modules/dynamodb-table/aws"
version = ">=5.0.0"
attributes = [
{
name = "id"
type = "S"
}
]
hash_key = "id"
name = "abc123"
server_side_encryption_kms_key_arn = "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012"
}
module "dynamodb_table" {
source = "nist800171.compliance.tf/terraform-aws-modules/dynamodb-table/aws"
version = ">=5.0.0"
attributes = [
{
name = "id"
type = "S"
}
]
hash_key = "id"
name = "abc123"
server_side_encryption_kms_key_arn = "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012"
}
module "dynamodb_table" {
source = "cisacyberessentials.compliance.tf/terraform-aws-modules/dynamodb-table/aws"
version = ">=5.0.0"
attributes = [
{
name = "id"
type = "S"
}
]
hash_key = "id"
name = "abc123"
server_side_encryption_kms_key_arn = "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012"
}
module "dynamodb_table" {
source = "nydfs23.compliance.tf/terraform-aws-modules/dynamodb-table/aws"
version = ">=5.0.0"
attributes = [
{
name = "id"
type = "S"
}
]
hash_key = "id"
name = "abc123"
server_side_encryption_kms_key_arn = "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012"
}
module "dynamodb_table" {
source = "eugmpannex11.compliance.tf/terraform-aws-modules/dynamodb-table/aws"
version = ">=5.0.0"
attributes = [
{
name = "id"
type = "S"
}
]
hash_key = "id"
name = "abc123"
server_side_encryption_kms_key_arn = "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012"
}
module "dynamodb_table" {
source = "cfrpart11.compliance.tf/terraform-aws-modules/dynamodb-table/aws"
version = ">=5.0.0"
attributes = [
{
name = "id"
type = "S"
}
]
hash_key = "id"
name = "abc123"
server_side_encryption_kms_key_arn = "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012"
}
module "dynamodb_table" {
source = "rbicybersecurity.compliance.tf/terraform-aws-modules/dynamodb-table/aws"
version = ">=5.0.0"
attributes = [
{
name = "id"
type = "S"
}
]
hash_key = "id"
name = "abc123"
server_side_encryption_kms_key_arn = "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012"
}
module "dynamodb_table" {
source = "hipaasecurity2003.compliance.tf/terraform-aws-modules/dynamodb-table/aws"
version = ">=5.0.0"
attributes = [
{
name = "id"
type = "S"
}
]
hash_key = "id"
name = "abc123"
server_side_encryption_kms_key_arn = "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012"
}
module "dynamodb_table" {
source = "nistcsfv11.compliance.tf/terraform-aws-modules/dynamodb-table/aws"
version = ">=5.0.0"
attributes = [
{
name = "id"
type = "S"
}
]
hash_key = "id"
name = "abc123"
server_side_encryption_kms_key_arn = "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012"
}
module "dynamodb_table" {
source = "nist80053rev4.compliance.tf/terraform-aws-modules/dynamodb-table/aws"
version = ">=5.0.0"
attributes = [
{
name = "id"
type = "S"
}
]
hash_key = "id"
name = "abc123"
server_side_encryption_kms_key_arn = "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012"
}
module "dynamodb_table" {
source = "pcidssv321.compliance.tf/terraform-aws-modules/dynamodb-table/aws"
version = ">=5.0.0"
attributes = [
{
name = "id"
type = "S"
}
]
hash_key = "id"
name = "abc123"
server_side_encryption_kms_key_arn = "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012"
}
If you use terraform-aws-modules/dynamodb-table/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 "dynamodb_table" {
source = "terraform-aws-modules/dynamodb-table/aws"
version = ">=5.0.0"
attributes = [
{
name = "id"
type = "S"
}
]
hash_key = "id"
name = "abc123"
server_side_encryption_enabled = true
server_side_encryption_kms_key_arn = "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012"
}
Use AWS provider resources directly. See docs for the resources involved: aws_dynamodb_table.
resource "aws_dynamodb_table" "this" {
attribute {
name = "id"
type = "S"
}
billing_mode = "PAY_PER_REQUEST"
hash_key = "id"
name = "pofix-abc123"
server_side_encryption {
enabled = true
kms_key_arn = "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012"
}
}
What this control checks
The control checks that aws_dynamodb_table includes a server_side_encryption block with enabled = true. Without kms_key_arn, DynamoDB uses the AWS-managed key aws/dynamodb. To use a customer-managed key, set kms_key_arn to the ARN of your aws_kms_key resource. A table that omits the server_side_encryption block entirely, or sets enabled = false, fails this control, even though DynamoDB still encrypts data at rest (with an AWS-owned key) by default. If you reference a customer-managed key, the key policy must grant dynamodb.amazonaws.com the kms:Encrypt, kms:Decrypt, kms:ReEncrypt*, kms:GenerateDataKey*, kms:DescribeKey, and kms:CreateGrant actions.
Common pitfalls
Omitting server_side_encryption defaults to AWS-owned key
Without a
server_side_encryptionblock inaws_dynamodb_table, Terraform does not configure KMS encryption. The table silently falls back to the AWS-owned key, which won't satisfy any control requiring customer-visible KMS.Global tables require KMS keys in every replica region
Each replica in an
aws_dynamodb_tablewithreplicablocks needs a KMS key available in its own region. Omit the replica KMS settings and encryption behavior becomes provider-dependent. Replicas can end up non-compliant for controls that require customer-visible KMS keys without any obvious error duringterraform apply.KMS key policy missing DynamoDB grants
Get the key policy wrong and DynamoDB will throw
AccessDeniedExceptionon the first table operation. The policy must grantkms:CreateGrantwith aGrantIsForAWSResourcecondition todynamodb.amazonaws.com, along with the full set of encrypt and decrypt actions.Terraform plan shows replacement on key change
Switching encryption keys on an existing table triggers an in-place re-encryption, not a resource replacement. Duration scales with table size, so don't assume the change is complete just because
terraform applyfinished. Check theUpdateTableAPI status to confirm re-encryption is done before proceeding.
Audit evidence
An auditor expects AWS Config rule results (such as dynamodb-table-encrypted-kms) showing all DynamoDB tables as compliant. The DescribeTable API output for each table should show SSEDescription.SSEType as KMS and SSEDescription.Status as ENABLED, with KMSMasterKeyArn pointing to an active key. CloudTrail will contain Encrypt and Decrypt events tied to DynamoDB operations when customer-visible KMS keys are in use.
For customer-managed keys, the auditor may also review the KMS key policy and rotation configuration to confirm the key is under organizational control and meets rotation requirements.
Framework-specific interpretation
PCI DSS v4.0: Requirement 3.5 mandates strong cryptography for PAN at rest and documented key management procedures. KMS encryption on DynamoDB satisfies both, with key management handled through KMS key policies and automatic or manual rotation.
HIPAA Omnibus Rule 2013: The HIPAA Security Rule treats encryption as an addressable implementation specification under 45 CFR 164.312(a)(2)(iv). Using a KMS key you control gives you the CloudTrail audit trail and key management evidence auditors ask for when PHI is stored in DynamoDB.
GDPR: Article 32 requires appropriate technical measures to protect personal data, including encryption. KMS encryption on DynamoDB tables is a concrete safeguard you can document. If a breach occurs, encrypted data may reduce notification obligations under Article 34 when the data is unintelligible to unauthorized parties.
NIST SP 800-53 Rev 5: SC-12 and SC-28 are the relevant controls. SC-28 requires protecting data at rest; SC-12 requires documented key lifecycle management, rotation, and audit trails. KMS-managed keys satisfy both.
Related controls
Tool mappings
Use these identifiers to cross-reference this control across tools, reports, and evidence.
Compliance.tf Control:
dynamodb_table_encrypted_with_kmsAWS Config Managed Rule:
DYNAMODB_TABLE_ENCRYPTED_KMSCheckov Check:
CKV_AWS_119Powerpipe Control:
aws_compliance.control.dynamodb_table_encrypted_with_kmsProwler Check:
dynamodb_tables_kms_cmk_encryption_enabledKICS Query:
ce089fd4-1406-47bd-8aad-c259772bb294Trivy Check:
AWS-0025
Last reviewed: 2026-03-09