islamelkadi/terraform-aws-dynamodb
Production-ready Terraform module for AWS DynamoDB with multi-framework compliance support, built on real-world enterprise experience as a Forward Deployed Engineer.
Terraform AWS DynamoDB Module
A reusable Terraform module for creating AWS DynamoDB tables with AWS Security Hub compliance (FSBP, CIS, NIST 800-53, NIST 800-171, PCI DSS), flexible security control overrides, and comprehensive configuration options.
Table of Contents
Prerequisites
This module is designed for macOS. The following must already be installed on your machine:
To install the remaining development tools, run:
make bootstrapThis will install/upgrade: tfenv, Terraform (via tfenv), tflint, terraform-docs, checkov, and pre-commit.
Security
Security Controls
This module implements AWS Security Hub compliance with an extensible override system. Security controls are enforced by default when security_controls is provided from the metadata module.
Available Security Control Overrides
| Override Flag | Description | Common Justification |
|---|---|---|
disable_kms_requirement |
Allows AWS-managed encryption | "Development table, no sensitive data" |
disable_pitr_requirement |
Disables point-in-time recovery | "Ephemeral data, disposable table" |
disable_deletion_protection |
Allows table deletion | "Development table, easy teardown needed" |
Security Best Practices
Production Tables:
- Use KMS customer-managed keys for encryption
- Enable point-in-time recovery (PITR) for data protection
- Enable deletion protection to prevent accidental deletion
- Use on-demand billing or provisioned with auto-scaling
- Enable DynamoDB Streams for change data capture
- Set up CloudWatch alarms for throttling and errors
Development Tables:
- KMS encryption still recommended (minimal cost)
- PITR optional for cost savings
- Deletion protection can be disabled with justification
Environment-Based Security Controls
Security controls are automatically applied based on the environment through the terraform-aws-metadata module's security profiles:
| Control | Dev | Staging | Prod |
|---|---|---|---|
| KMS customer-managed keys | Optional | Required | Required |
| Point-in-time recovery | Optional | Required | Required |
| Deletion protection | Disabled | Enabled | Enabled |
| DynamoDB Streams | Optional | Recommended | Recommended |
For full details on security profiles and how controls vary by environment, see the Security Profiles documentation.
Security Scan Suppressions
This module suppresses certain Checkov security checks that are either not applicable to example/demo code or represent optional features. The following checks are suppressed in .checkov.yaml:
Module Source Versioning (CKV_TF_1, CKV_TF_2)
- Suppressed because we use semantic version tags (
?ref=v1.0.0) instead of commit hashes for better maintainability and readability - Semantic versioning is a valid and widely-accepted versioning strategy for stable releases
KMS IAM Policies (CKV_AWS_111, CKV_AWS_356, CKV_AWS_109)
- Suppressed in example code where KMS modules use flexible IAM policies for demonstration purposes
- Production deployments should customize KMS policies based on specific security requirements and apply least privilege principles
Features
- DynamoDB table with on-demand billing mode
- KMS encryption with customer-managed keys
- Point-in-time recovery (PITR) for data protection
- Deletion protection option
- DynamoDB Streams support
- TTL (Time To Live) configuration
- Global and local secondary indexes
- Security controls integration with extensible override system
- Terraform validation checks for compliance
Usage Examples
Basic Example
module "dynamodb_table" {
source = "github.com/islamelkadi/terraform-aws-dynamodb"
namespace = "example"
environment = "prod"
name = "events"
region = "us-east-1"
# Table schema
hash_key = "event_id"
range_key = "timestamp"
attributes = [
{
name = "event_id"
type = "S"
},
{
name = "timestamp"
type = "N"
}
]
# Encryption
kms_key_arn = module.kms.key_arn
tags = {
Project = "CorporateActions"
}
}Production Table with Security Controls
module "dynamodb_table" {
source = "github.com/islamelkadi/terraform-aws-dynamodb"
security_controls = module.metadata.security_controls
namespace = "example"
environment = "prod"
name = "corporate-actions-events"
region = "us-east-1"
# Table schema
hash_key = "event_id"
range_key = "timestamp"
attributes = [
{
name = "event_id"
type = "S"
},
{
name = "timestamp"
type = "N"
},
{
name = "cusip"
type = "S"
}
]
# Global secondary index
global_secondary_indexes = [
{
name = "cusip-timestamp-index"
hash_key = "cusip"
range_key = "timestamp"
projection_type = "ALL"
}
]
# KMS encryption (required by security controls)
kms_key_arn = module.kms.key_arn
# Point-in-time recovery (required by security controls)
enable_point_in_time_recovery = true
# Deletion protection (required by security controls)
deletion_protection_enabled = true
# DynamoDB Streams for change data capture
stream_enabled = true
stream_view_type = "NEW_AND_OLD_IMAGES"
# TTL for automatic data expiration
ttl_enabled = true
ttl_attribute_name = "expires_at"
tags = {
Project = "CorporateActions"
Compliance = "PCI-DSS"
DataClass = "Confidential"
}
}Development Table with Overrides
module "dynamodb_table" {
source = "github.com/islamelkadi/terraform-aws-dynamodb"
security_controls = module.metadata.security_controls
# Override security controls for development
security_control_overrides = {
disable_pitr_requirement = true
disable_deletion_protection = true
justification = "Development table for testing. Data is disposable and recreated from seed scripts. PITR adds unnecessary cost. Deletion protection disabled for easy environment teardown."
}
namespace = "example"
environment = "dev"
name = "test-events"
region = "us-east-1"
hash_key = "id"
attributes = [
{
name = "id"
type = "S"
}
]
# Still use KMS encryption
kms_key_arn = module.kms.key_arn
# Overrides allow these to be disabled
enable_point_in_time_recovery = false
deletion_protection_enabled = false
tags = {
Project = "CorporateActions"
Environment = "Development"
}
}Table with Streams for Event Processing
module "events_table" {
source = "github.com/islamelkadi/terraform-aws-dynamodb"
security_controls = module.metadata.security_controls
namespace = "example"
environment = "prod"
name = "events"
region = "us-east-1"
hash_key = "event_id"
range_key = "timestamp"
attributes = [
{
name = "event_id"
type = "S"
},
{
name = "timestamp"
type = "N"
}
]
kms_key_arn = module.kms.key_arn
enable_point_in_time_recovery = true
deletion_protection_enabled = true
# Enable streams for Lambda processing
stream_enabled = true
stream_view_type = "NEW_AND_OLD_IMAGES"
tags = {
Project = "CorporateActions"
Purpose = "EventSourcing"
}
}
# Lambda to process DynamoDB stream
module "stream_processor" {
source = "github.com/islamelkadi/terraform-aws-dynamodb"
namespace = "example"
environment = "prod"
name = "events-stream-processor"
region = "us-east-1"
runtime = "python3.13"
handler = "index.handler"
filename = "processor.zip"
# Grant Lambda permission to read from stream
managed_policy_arns = [
"arn:aws:iam::aws:policy/service-role/AWSLambdaDynamoDBExecutionRole"
]
}
# Event source mapping
resource "aws_lambda_event_source_mapping" "dynamodb_stream" {
event_source_arn = module.events_table.stream_arn
function_name = module.stream_processor.function_name
starting_position = "LATEST"
batch_size = 100
maximum_batching_window_in_seconds = 5
}MCP Servers
This module includes two Model Context Protocol (MCP) servers configured in .kiro/settings/mcp.json for use with Kiro:
| Server | Package | Description |
|---|---|---|
aws-docs |
awslabs.aws-documentation-mcp-server@latest |
Provides access to AWS documentation for contextual lookups of service features, API references, and best practices. |
terraform |
awslabs.terraform-mcp-server@latest |
Enables Terraform operations (init, validate, plan, fmt, tflint) directly from the IDE with auto-approved commands for common workflows. |
Both servers run via uvx and require no additional installation beyond the bootstrap step.
Usage
# DynamoDB Module Example
# Demonstrates table creation with GSI, TTL, and streams
module "dynamodb_table" {
source = "github.com/islamelkadi/terraform-aws-dynamodb"
namespace = var.namespace
environment = var.environment
name = var.name
region = var.region
# Composite key
hash_key = var.hash_key
range_key = var.range_key
# Attributes
attributes = var.attributes
# Global Secondary Indexes
global_secondary_indexes = var.global_secondary_indexes
# TTL configuration
ttl_enabled = var.ttl_enabled
ttl_attribute_name = var.ttl_attribute_name
# Streams configuration
stream_enabled = var.stream_enabled
stream_view_type = var.stream_view_type
# Recovery and protection
enable_point_in_time_recovery = var.enable_point_in_time_recovery
deletion_protection_enabled = var.deletion_protection_enabled
# Encryption
kms_key_arn = var.kms_key_arn
tags = var.tags
}Requirements
| Name | Version |
|---|---|
| terraform | >= 1.14.3 |
| aws | >= 6.34 |
Providers
| Name | Version |
|---|---|
| aws | >= 6.34 |
Modules
| Name | Source | Version |
|---|---|---|
| metadata | github.com/islamelkadi/terraform-aws-metadata | v1.1.0 |
Resources
| Name | Type |
|---|---|
| aws_dynamodb_table.this | resource |
Inputs
| Name | Description | Type | Default | Required |
|---|---|---|---|---|
| attributes | List of attribute definitions. Each attribute must have 'name' and 'type' (S, N, or B) | list(object({ |
n/a | yes |
| deletion_protection_enabled | Enable deletion protection for the table | bool |
false |
no |
| delimiter | Delimiter to use between name components | string |
"-" |
no |
| enable_point_in_time_recovery | Enable point-in-time recovery for the table | bool |
true |
no |
| environment | Environment name (dev, staging, prod) | string |
n/a | yes |
| global_secondary_indexes | List of global secondary index configurations | list(object({ |
[] |
no |
| hash_key | Attribute to use as the hash (partition) key | string |
n/a | yes |
| kms_key_arn | ARN of KMS key for table encryption | string |
n/a | yes |
| local_secondary_indexes | List of local secondary index configurations | list(object({ |
[] |
no |
| name | Name of the DynamoDB table | string |
n/a | yes |
| namespace | Namespace (organization/team name) | string |
n/a | yes |
| naming_attributes | Additional attributes for naming | list(string) |
[] |
no |
| range_key | Attribute to use as the range (sort) key. Optional | string |
null |
no |
| region | AWS region where resources will be created | string |
n/a | yes |
| security_control_overrides | Override specific security controls with documented justification | object({ |
{ |
no |
| security_controls | Security controls configuration from metadata module | object({ |
null |
no |
| stream_enabled | Enable DynamoDB Streams | bool |
false |
no |
| stream_view_type | Stream view type (KEYS_ONLY, NEW_IMAGE, OLD_IMAGE, NEW_AND_OLD_IMAGES) | string |
"NEW_AND_OLD_IMAGES" |
no |
| tags | Additional tags to apply to resources | map(string) |
{} |
no |
| ttl_attribute_name | Name of the table attribute to use for TTL. Set to null to disable TTL | string |
"ttl" |
no |
| ttl_enabled | Enable TTL for the table | bool |
true |
no |
Outputs
| Name | Description |
|---|---|
| hash_key | Hash key attribute name |
| range_key | Range key attribute name |
| stream_arn | DynamoDB table stream ARN |
| stream_label | DynamoDB table stream label |
| table_arn | DynamoDB table ARN |
| table_id | DynamoDB table ID (same as table name) |
| table_name | DynamoDB table name |
| tags | Tags applied to the DynamoDB table |
Examples
See example/ for a complete working example with GSI, TTL, and streams.