mirror of
https://github.com/ghndrx/terraform-foundation.git
synced 2026-02-10 06:45:06 +00:00
Enterprise-grade multi-tenant AWS cloud foundation. Modules: - GitHub OIDC for keyless CI/CD authentication - IAM account settings and security baseline - AWS Config Rules for compliance - ABAC (Attribute-Based Access Control) - SCPs (Service Control Policies) Features: - Multi-account architecture - Cost optimization patterns - Security best practices - Comprehensive documentation Tech: Terraform, AWS Organizations, IAM Identity Center
440 lines
12 KiB
HCL
440 lines
12 KiB
HCL
################################################################################
|
|
# Workload: DynamoDB Table
|
|
#
|
|
# Deploys a NoSQL database table:
|
|
# - On-demand or provisioned capacity
|
|
# - Encryption at rest with KMS
|
|
# - Point-in-time recovery
|
|
# - TTL support
|
|
# - Global Secondary Indexes
|
|
# - Streams for event-driven patterns
|
|
#
|
|
# Usage:
|
|
# Copy this folder to 05-workloads/<tenant>-<table-name>/
|
|
# Update locals and variables
|
|
# terraform init -backend-config=../../00-bootstrap/backend.hcl
|
|
# terraform apply
|
|
################################################################################
|
|
|
|
terraform {
|
|
required_version = ">= 1.5"
|
|
|
|
required_providers {
|
|
aws = {
|
|
source = "hashicorp/aws"
|
|
version = ">= 5.0"
|
|
}
|
|
}
|
|
|
|
backend "s3" {
|
|
key = "05-workloads/<TENANT>-<NAME>-table/terraform.tfstate"
|
|
}
|
|
}
|
|
|
|
################################################################################
|
|
# Configuration - UPDATE THESE
|
|
################################################################################
|
|
|
|
locals {
|
|
# Naming
|
|
tenant = "<TENANT>"
|
|
name = "<NAME>"
|
|
env = "prod"
|
|
|
|
table_name = "${local.tenant}-${local.name}-${local.env}"
|
|
|
|
# Capacity mode: "PAY_PER_REQUEST" (on-demand) or "PROVISIONED"
|
|
billing_mode = "PAY_PER_REQUEST"
|
|
|
|
# Provisioned capacity (only used if billing_mode = "PROVISIONED")
|
|
read_capacity = 5
|
|
write_capacity = 5
|
|
|
|
# Auto-scaling for provisioned mode
|
|
enable_autoscaling = local.billing_mode == "PROVISIONED"
|
|
autoscaling_min_read = 5
|
|
autoscaling_max_read = 100
|
|
autoscaling_min_write = 5
|
|
autoscaling_max_write = 100
|
|
autoscaling_target_utilization = 70
|
|
|
|
# Primary key
|
|
hash_key = "pk" # Partition key
|
|
hash_key_type = "S" # S = String, N = Number, B = Binary
|
|
range_key = "sk" # Sort key (optional, set to null to disable)
|
|
range_key_type = "S"
|
|
|
|
# TTL (set to null to disable)
|
|
ttl_attribute = "ttl"
|
|
|
|
# Streams (set to null to disable)
|
|
# Options: KEYS_ONLY, NEW_IMAGE, OLD_IMAGE, NEW_AND_OLD_IMAGES
|
|
stream_view_type = null
|
|
|
|
# Point-in-time recovery
|
|
point_in_time_recovery = true
|
|
|
|
# Global Secondary Indexes (GSI)
|
|
global_secondary_indexes = [
|
|
# {
|
|
# name = "gsi1"
|
|
# hash_key = "gsi1pk"
|
|
# range_key = "gsi1sk"
|
|
# projection_type = "ALL" # ALL, KEYS_ONLY, or INCLUDE
|
|
# non_key_attributes = [] # Only for INCLUDE
|
|
# }
|
|
]
|
|
|
|
# Local Secondary Indexes (LSI) - must be defined at table creation
|
|
local_secondary_indexes = [
|
|
# {
|
|
# name = "lsi1"
|
|
# range_key = "lsi1sk"
|
|
# projection_type = "ALL"
|
|
# non_key_attributes = []
|
|
# }
|
|
]
|
|
|
|
# Table class: STANDARD or STANDARD_INFREQUENT_ACCESS
|
|
table_class = "STANDARD"
|
|
}
|
|
|
|
################################################################################
|
|
# Variables
|
|
################################################################################
|
|
|
|
variable "region" {
|
|
type = string
|
|
default = "us-east-1"
|
|
}
|
|
|
|
variable "state_bucket" {
|
|
type = string
|
|
}
|
|
|
|
################################################################################
|
|
# Provider
|
|
################################################################################
|
|
|
|
provider "aws" {
|
|
region = var.region
|
|
|
|
default_tags {
|
|
tags = {
|
|
Tenant = local.tenant
|
|
App = local.name
|
|
Environment = local.env
|
|
ManagedBy = "terraform"
|
|
}
|
|
}
|
|
}
|
|
|
|
################################################################################
|
|
# Data Sources
|
|
################################################################################
|
|
|
|
data "aws_caller_identity" "current" {}
|
|
data "aws_region" "current" {}
|
|
|
|
################################################################################
|
|
# KMS Key
|
|
################################################################################
|
|
|
|
resource "aws_kms_key" "table" {
|
|
description = "KMS key for ${local.table_name} DynamoDB encryption"
|
|
deletion_window_in_days = 7
|
|
enable_key_rotation = true
|
|
|
|
tags = { Name = "${local.table_name}-dynamodb" }
|
|
}
|
|
|
|
resource "aws_kms_alias" "table" {
|
|
name = "alias/${local.table_name}-dynamodb"
|
|
target_key_id = aws_kms_key.table.key_id
|
|
}
|
|
|
|
################################################################################
|
|
# DynamoDB Table
|
|
################################################################################
|
|
|
|
resource "aws_dynamodb_table" "main" {
|
|
name = local.table_name
|
|
billing_mode = local.billing_mode
|
|
table_class = local.table_class
|
|
|
|
# Capacity (only for PROVISIONED)
|
|
read_capacity = local.billing_mode == "PROVISIONED" ? local.read_capacity : null
|
|
write_capacity = local.billing_mode == "PROVISIONED" ? local.write_capacity : null
|
|
|
|
# Primary key
|
|
hash_key = local.hash_key
|
|
range_key = local.range_key
|
|
|
|
# Key schema
|
|
attribute {
|
|
name = local.hash_key
|
|
type = local.hash_key_type
|
|
}
|
|
|
|
dynamic "attribute" {
|
|
for_each = local.range_key != null ? [1] : []
|
|
content {
|
|
name = local.range_key
|
|
type = local.range_key_type
|
|
}
|
|
}
|
|
|
|
# GSI attributes
|
|
dynamic "attribute" {
|
|
for_each = local.global_secondary_indexes
|
|
content {
|
|
name = attribute.value.hash_key
|
|
type = "S"
|
|
}
|
|
}
|
|
|
|
dynamic "attribute" {
|
|
for_each = [for gsi in local.global_secondary_indexes : gsi if gsi.range_key != null]
|
|
content {
|
|
name = attribute.value.range_key
|
|
type = "S"
|
|
}
|
|
}
|
|
|
|
# LSI attributes
|
|
dynamic "attribute" {
|
|
for_each = local.local_secondary_indexes
|
|
content {
|
|
name = attribute.value.range_key
|
|
type = "S"
|
|
}
|
|
}
|
|
|
|
# Global Secondary Indexes
|
|
dynamic "global_secondary_index" {
|
|
for_each = local.global_secondary_indexes
|
|
content {
|
|
name = global_secondary_index.value.name
|
|
hash_key = global_secondary_index.value.hash_key
|
|
range_key = lookup(global_secondary_index.value, "range_key", null)
|
|
projection_type = global_secondary_index.value.projection_type
|
|
non_key_attributes = global_secondary_index.value.projection_type == "INCLUDE" ? global_secondary_index.value.non_key_attributes : null
|
|
|
|
# Capacity for provisioned mode
|
|
read_capacity = local.billing_mode == "PROVISIONED" ? local.read_capacity : null
|
|
write_capacity = local.billing_mode == "PROVISIONED" ? local.write_capacity : null
|
|
}
|
|
}
|
|
|
|
# Local Secondary Indexes
|
|
dynamic "local_secondary_index" {
|
|
for_each = local.local_secondary_indexes
|
|
content {
|
|
name = local_secondary_index.value.name
|
|
range_key = local_secondary_index.value.range_key
|
|
projection_type = local_secondary_index.value.projection_type
|
|
non_key_attributes = local_secondary_index.value.projection_type == "INCLUDE" ? local_secondary_index.value.non_key_attributes : null
|
|
}
|
|
}
|
|
|
|
# TTL
|
|
dynamic "ttl" {
|
|
for_each = local.ttl_attribute != null ? [1] : []
|
|
content {
|
|
attribute_name = local.ttl_attribute
|
|
enabled = true
|
|
}
|
|
}
|
|
|
|
# Streams
|
|
stream_enabled = local.stream_view_type != null
|
|
stream_view_type = local.stream_view_type
|
|
|
|
# Encryption
|
|
server_side_encryption {
|
|
enabled = true
|
|
kms_key_arn = aws_kms_key.table.arn
|
|
}
|
|
|
|
# Point-in-time recovery
|
|
point_in_time_recovery {
|
|
enabled = local.point_in_time_recovery
|
|
}
|
|
|
|
# Deletion protection for prod
|
|
deletion_protection_enabled = local.env == "prod"
|
|
|
|
tags = {
|
|
Name = local.table_name
|
|
Backup = "true"
|
|
}
|
|
|
|
lifecycle {
|
|
prevent_destroy = false # Set to true for production
|
|
}
|
|
}
|
|
|
|
################################################################################
|
|
# Auto Scaling (Provisioned Mode Only)
|
|
################################################################################
|
|
|
|
resource "aws_appautoscaling_target" "read" {
|
|
count = local.enable_autoscaling ? 1 : 0
|
|
max_capacity = local.autoscaling_max_read
|
|
min_capacity = local.autoscaling_min_read
|
|
resource_id = "table/${aws_dynamodb_table.main.name}"
|
|
scalable_dimension = "dynamodb:table:ReadCapacityUnits"
|
|
service_namespace = "dynamodb"
|
|
}
|
|
|
|
resource "aws_appautoscaling_policy" "read" {
|
|
count = local.enable_autoscaling ? 1 : 0
|
|
name = "${local.table_name}-read-autoscaling"
|
|
policy_type = "TargetTrackingScaling"
|
|
resource_id = aws_appautoscaling_target.read[0].resource_id
|
|
scalable_dimension = aws_appautoscaling_target.read[0].scalable_dimension
|
|
service_namespace = aws_appautoscaling_target.read[0].service_namespace
|
|
|
|
target_tracking_scaling_policy_configuration {
|
|
predefined_metric_specification {
|
|
predefined_metric_type = "DynamoDBReadCapacityUtilization"
|
|
}
|
|
target_value = local.autoscaling_target_utilization
|
|
}
|
|
}
|
|
|
|
resource "aws_appautoscaling_target" "write" {
|
|
count = local.enable_autoscaling ? 1 : 0
|
|
max_capacity = local.autoscaling_max_write
|
|
min_capacity = local.autoscaling_min_write
|
|
resource_id = "table/${aws_dynamodb_table.main.name}"
|
|
scalable_dimension = "dynamodb:table:WriteCapacityUnits"
|
|
service_namespace = "dynamodb"
|
|
}
|
|
|
|
resource "aws_appautoscaling_policy" "write" {
|
|
count = local.enable_autoscaling ? 1 : 0
|
|
name = "${local.table_name}-write-autoscaling"
|
|
policy_type = "TargetTrackingScaling"
|
|
resource_id = aws_appautoscaling_target.write[0].resource_id
|
|
scalable_dimension = aws_appautoscaling_target.write[0].scalable_dimension
|
|
service_namespace = aws_appautoscaling_target.write[0].service_namespace
|
|
|
|
target_tracking_scaling_policy_configuration {
|
|
predefined_metric_specification {
|
|
predefined_metric_type = "DynamoDBWriteCapacityUtilization"
|
|
}
|
|
target_value = local.autoscaling_target_utilization
|
|
}
|
|
}
|
|
|
|
################################################################################
|
|
# CloudWatch Alarms
|
|
################################################################################
|
|
|
|
resource "aws_cloudwatch_metric_alarm" "throttled_requests" {
|
|
alarm_name = "${local.table_name}-throttled-requests"
|
|
comparison_operator = "GreaterThanThreshold"
|
|
evaluation_periods = 2
|
|
metric_name = "ThrottledRequests"
|
|
namespace = "AWS/DynamoDB"
|
|
period = 300
|
|
statistic = "Sum"
|
|
threshold = 0
|
|
alarm_description = "DynamoDB throttled requests detected"
|
|
|
|
dimensions = {
|
|
TableName = aws_dynamodb_table.main.name
|
|
}
|
|
|
|
tags = { Name = "${local.table_name}-throttled" }
|
|
}
|
|
|
|
resource "aws_cloudwatch_metric_alarm" "system_errors" {
|
|
alarm_name = "${local.table_name}-system-errors"
|
|
comparison_operator = "GreaterThanThreshold"
|
|
evaluation_periods = 2
|
|
metric_name = "SystemErrors"
|
|
namespace = "AWS/DynamoDB"
|
|
period = 300
|
|
statistic = "Sum"
|
|
threshold = 0
|
|
alarm_description = "DynamoDB system errors detected"
|
|
|
|
dimensions = {
|
|
TableName = aws_dynamodb_table.main.name
|
|
}
|
|
|
|
tags = { Name = "${local.table_name}-errors" }
|
|
}
|
|
|
|
################################################################################
|
|
# IAM Policy Document (for application access)
|
|
################################################################################
|
|
|
|
data "aws_iam_policy_document" "table_access" {
|
|
statement {
|
|
sid = "AllowTableOperations"
|
|
effect = "Allow"
|
|
|
|
actions = [
|
|
"dynamodb:BatchGetItem",
|
|
"dynamodb:BatchWriteItem",
|
|
"dynamodb:DeleteItem",
|
|
"dynamodb:GetItem",
|
|
"dynamodb:PutItem",
|
|
"dynamodb:Query",
|
|
"dynamodb:Scan",
|
|
"dynamodb:UpdateItem",
|
|
"dynamodb:DescribeTable",
|
|
]
|
|
|
|
resources = [
|
|
aws_dynamodb_table.main.arn,
|
|
"${aws_dynamodb_table.main.arn}/index/*",
|
|
]
|
|
}
|
|
|
|
statement {
|
|
sid = "AllowKMSDecrypt"
|
|
effect = "Allow"
|
|
|
|
actions = [
|
|
"kms:Decrypt",
|
|
"kms:Encrypt",
|
|
"kms:GenerateDataKey",
|
|
]
|
|
|
|
resources = [aws_kms_key.table.arn]
|
|
}
|
|
}
|
|
|
|
################################################################################
|
|
# Outputs
|
|
################################################################################
|
|
|
|
output "table_name" {
|
|
value = aws_dynamodb_table.main.name
|
|
}
|
|
|
|
output "table_arn" {
|
|
value = aws_dynamodb_table.main.arn
|
|
}
|
|
|
|
output "table_id" {
|
|
value = aws_dynamodb_table.main.id
|
|
}
|
|
|
|
output "stream_arn" {
|
|
value = aws_dynamodb_table.main.stream_arn
|
|
}
|
|
|
|
output "kms_key_arn" {
|
|
value = aws_kms_key.table.arn
|
|
}
|
|
|
|
output "access_policy_json" {
|
|
value = data.aws_iam_policy_document.table_access.json
|
|
description = "IAM policy document for application access to this table"
|
|
}
|