hearth/terraform/modules/storage/main.tf
Eric Garcia e78000831e Initial commit: Port infrastructure from coherence-mcp
Hearth is the infrastructure home for the letemcook ecosystem.

Ported from coherence-mcp/infra:
- Terraform modules (VPC, EKS, IAM, NLB, S3, storage)
- Kubernetes manifests (Forgejo, ingress, cert-manager, karpenter)
- Deployment scripts (phased rollout)

Status: Not deployed. EKS cluster needs to be provisioned.

Next steps:
1. Bootstrap terraform backend
2. Deploy phase 1 (foundation)
3. Deploy phase 2 (core services including Forgejo)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 06:06:13 -05:00

246 lines
5.4 KiB
HCL

# Storage Module - EBS, EFS, S3
# RFC 0039: ADR-Compliant Foundation Infrastructure
#
# Architecture:
# - EFS for shared persistent storage (git repos, files)
# - S3 for backups and blob storage
# - KMS for encryption (FIPS compliance)
# KMS Key for Storage Encryption
resource "aws_kms_key" "storage" {
description = "Storage encryption key"
deletion_window_in_days = 30
enable_key_rotation = true
tags = merge(var.tags, {
Name = "${var.name}-storage"
})
}
resource "aws_kms_alias" "storage" {
name = "alias/${var.name}-storage"
target_key_id = aws_kms_key.storage.key_id
}
# EFS File System
resource "aws_efs_file_system" "main" {
creation_token = var.name
encrypted = var.enable_encryption
kms_key_id = var.enable_encryption ? aws_kms_key.storage.arn : null
performance_mode = "generalPurpose"
throughput_mode = "bursting"
lifecycle_policy {
transition_to_ia = "AFTER_30_DAYS"
}
lifecycle_policy {
transition_to_primary_storage_class = "AFTER_1_ACCESS"
}
tags = merge(var.tags, {
Name = var.name
})
}
# EFS Mount Targets (one per AZ)
resource "aws_efs_mount_target" "main" {
count = length(var.private_subnet_ids)
file_system_id = aws_efs_file_system.main.id
subnet_id = var.private_subnet_ids[count.index]
security_groups = [aws_security_group.efs.id]
}
# EFS Security Group
resource "aws_security_group" "efs" {
name = "${var.name}-efs"
description = "EFS security group"
vpc_id = var.vpc_id
ingress {
description = "NFS from VPC"
from_port = 2049
to_port = 2049
protocol = "tcp"
cidr_blocks = [data.aws_vpc.main.cidr_block]
}
egress {
description = "All outbound"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = merge(var.tags, {
Name = "${var.name}-efs"
})
}
# EFS Access Point for CSI driver
resource "aws_efs_access_point" "main" {
file_system_id = aws_efs_file_system.main.id
posix_user {
gid = 1000
uid = 1000
}
root_directory {
path = "/data"
creation_info {
owner_gid = 1000
owner_uid = 1000
permissions = "0755"
}
}
tags = merge(var.tags, {
Name = "${var.name}-data"
})
}
# S3 Bucket for Backups
resource "aws_s3_bucket" "backups" {
bucket = "${var.name}-backups-${data.aws_caller_identity.current.account_id}"
tags = merge(var.tags, {
Name = "${var.name}-backups"
Purpose = "CockroachDB and service backups"
})
}
resource "aws_s3_bucket_versioning" "backups" {
bucket = aws_s3_bucket.backups.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "backups" {
bucket = aws_s3_bucket.backups.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = var.enable_encryption ? "aws:kms" : "AES256"
kms_master_key_id = var.enable_encryption ? aws_kms_key.storage.arn : null
}
bucket_key_enabled = true
}
}
resource "aws_s3_bucket_public_access_block" "backups" {
bucket = aws_s3_bucket.backups.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_s3_bucket_lifecycle_configuration" "backups" {
bucket = aws_s3_bucket.backups.id
rule {
id = "transition-to-ia"
status = "Enabled"
transition {
days = 30
storage_class = "STANDARD_IA"
}
transition {
days = 90
storage_class = "GLACIER"
}
noncurrent_version_transition {
noncurrent_days = 30
storage_class = "STANDARD_IA"
}
noncurrent_version_expiration {
noncurrent_days = 365
}
}
}
# S3 Bucket for Blob Storage
resource "aws_s3_bucket" "blobs" {
bucket = "${var.name}-blobs-${data.aws_caller_identity.current.account_id}"
tags = merge(var.tags, {
Name = "${var.name}-blobs"
Purpose = "Application blob storage"
})
}
resource "aws_s3_bucket_versioning" "blobs" {
bucket = aws_s3_bucket.blobs.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "blobs" {
bucket = aws_s3_bucket.blobs.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = var.enable_encryption ? "aws:kms" : "AES256"
kms_master_key_id = var.enable_encryption ? aws_kms_key.storage.arn : null
}
bucket_key_enabled = true
}
}
resource "aws_s3_bucket_public_access_block" "blobs" {
bucket = aws_s3_bucket.blobs.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_s3_bucket_cors_configuration" "blobs" {
bucket = aws_s3_bucket.blobs.id
cors_rule {
allowed_headers = ["*"]
allowed_methods = ["GET", "PUT", "POST", "DELETE", "HEAD"]
allowed_origins = ["*"] # Restrict in production
expose_headers = ["ETag"]
max_age_seconds = 3000
}
}
resource "aws_s3_bucket_lifecycle_configuration" "blobs" {
bucket = aws_s3_bucket.blobs.id
rule {
id = "intelligent-tiering"
status = "Enabled"
transition {
days = 30
storage_class = "INTELLIGENT_TIERING"
}
noncurrent_version_expiration {
noncurrent_days = 90
}
}
}
# Data sources
data "aws_vpc" "main" {
id = var.vpc_id
}
data "aws_caller_identity" "current" {}