hearth/terraform/modules/s3/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

429 lines
10 KiB
HCL

# S3 Module - Blob Storage Buckets
# RFC 0039: ADR-Compliant Foundation Infrastructure
#
# Additional S3 buckets for:
# - Email blob storage (Stalwart attachments)
# - Loki log chunks
# - Tempo trace storage
# - Git LFS objects (Forgejo)
# KMS Key for S3 Encryption
resource "aws_kms_key" "s3" {
description = "S3 bucket encryption key"
deletion_window_in_days = 30
enable_key_rotation = true
tags = merge(var.tags, {
Name = "${var.name}-s3"
})
}
resource "aws_kms_alias" "s3" {
name = "alias/${var.name}-s3"
target_key_id = aws_kms_key.s3.key_id
}
# ============================================================================
# EMAIL BLOB STORAGE
# ============================================================================
resource "aws_s3_bucket" "email_blobs" {
bucket = "${var.name}-email-blobs-${data.aws_caller_identity.current.account_id}"
tags = merge(var.tags, {
Name = "${var.name}-email-blobs"
Purpose = "Stalwart email attachments and large messages"
Service = "stalwart"
})
}
resource "aws_s3_bucket_versioning" "email_blobs" {
bucket = aws_s3_bucket.email_blobs.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "email_blobs" {
bucket = aws_s3_bucket.email_blobs.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
kms_master_key_id = aws_kms_key.s3.arn
}
bucket_key_enabled = true
}
}
resource "aws_s3_bucket_public_access_block" "email_blobs" {
bucket = aws_s3_bucket.email_blobs.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_s3_bucket_lifecycle_configuration" "email_blobs" {
bucket = aws_s3_bucket.email_blobs.id
rule {
id = "archive-old-attachments"
status = "Enabled"
transition {
days = 90
storage_class = "STANDARD_IA"
}
transition {
days = 365
storage_class = "GLACIER"
}
noncurrent_version_expiration {
noncurrent_days = 30
}
}
}
# ============================================================================
# LOKI LOG CHUNKS
# ============================================================================
resource "aws_s3_bucket" "loki_chunks" {
bucket = "${var.name}-loki-chunks-${data.aws_caller_identity.current.account_id}"
tags = merge(var.tags, {
Name = "${var.name}-loki-chunks"
Purpose = "Loki log storage chunks"
Service = "loki"
})
}
resource "aws_s3_bucket_versioning" "loki_chunks" {
bucket = aws_s3_bucket.loki_chunks.id
versioning_configuration {
status = "Suspended" # Not needed for Loki chunks
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "loki_chunks" {
bucket = aws_s3_bucket.loki_chunks.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
kms_master_key_id = aws_kms_key.s3.arn
}
bucket_key_enabled = true
}
}
resource "aws_s3_bucket_public_access_block" "loki_chunks" {
bucket = aws_s3_bucket.loki_chunks.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_s3_bucket_lifecycle_configuration" "loki_chunks" {
bucket = aws_s3_bucket.loki_chunks.id
rule {
id = "expire-old-logs"
status = "Enabled"
# Delete logs older than retention period
expiration {
days = var.log_retention_days
}
# Move to cheaper storage after 30 days
transition {
days = 30
storage_class = "STANDARD_IA"
}
}
}
# ============================================================================
# TEMPO TRACE STORAGE
# ============================================================================
resource "aws_s3_bucket" "tempo_traces" {
bucket = "${var.name}-tempo-traces-${data.aws_caller_identity.current.account_id}"
tags = merge(var.tags, {
Name = "${var.name}-tempo-traces"
Purpose = "Tempo distributed trace storage"
Service = "tempo"
})
}
resource "aws_s3_bucket_versioning" "tempo_traces" {
bucket = aws_s3_bucket.tempo_traces.id
versioning_configuration {
status = "Suspended"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "tempo_traces" {
bucket = aws_s3_bucket.tempo_traces.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
kms_master_key_id = aws_kms_key.s3.arn
}
bucket_key_enabled = true
}
}
resource "aws_s3_bucket_public_access_block" "tempo_traces" {
bucket = aws_s3_bucket.tempo_traces.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_s3_bucket_lifecycle_configuration" "tempo_traces" {
bucket = aws_s3_bucket.tempo_traces.id
rule {
id = "expire-old-traces"
status = "Enabled"
expiration {
days = var.trace_retention_days
}
transition {
days = 7
storage_class = "STANDARD_IA"
}
}
}
# ============================================================================
# GIT LFS STORAGE
# ============================================================================
resource "aws_s3_bucket" "git_lfs" {
bucket = "${var.name}-git-lfs-${data.aws_caller_identity.current.account_id}"
tags = merge(var.tags, {
Name = "${var.name}-git-lfs"
Purpose = "Git LFS object storage for Forgejo"
Service = "forgejo"
})
}
resource "aws_s3_bucket_versioning" "git_lfs" {
bucket = aws_s3_bucket.git_lfs.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "git_lfs" {
bucket = aws_s3_bucket.git_lfs.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
kms_master_key_id = aws_kms_key.s3.arn
}
bucket_key_enabled = true
}
}
resource "aws_s3_bucket_public_access_block" "git_lfs" {
bucket = aws_s3_bucket.git_lfs.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_s3_bucket_lifecycle_configuration" "git_lfs" {
bucket = aws_s3_bucket.git_lfs.id
rule {
id = "intelligent-tiering"
status = "Enabled"
transition {
days = 30
storage_class = "INTELLIGENT_TIERING"
}
noncurrent_version_expiration {
noncurrent_days = 90
}
}
}
# ============================================================================
# IAM POLICIES FOR SERVICE ACCESS
# ============================================================================
# Policy for Loki to access its bucket
resource "aws_iam_policy" "loki_s3" {
name = "${var.name}-loki-s3-access"
description = "Allow Loki to access S3 bucket for log storage"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject",
"s3:ListBucket"
]
Resource = [
aws_s3_bucket.loki_chunks.arn,
"${aws_s3_bucket.loki_chunks.arn}/*"
]
},
{
Effect = "Allow"
Action = [
"kms:Encrypt",
"kms:Decrypt",
"kms:GenerateDataKey"
]
Resource = [aws_kms_key.s3.arn]
}
]
})
tags = var.tags
}
# Policy for Tempo to access its bucket
resource "aws_iam_policy" "tempo_s3" {
name = "${var.name}-tempo-s3-access"
description = "Allow Tempo to access S3 bucket for trace storage"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject",
"s3:ListBucket"
]
Resource = [
aws_s3_bucket.tempo_traces.arn,
"${aws_s3_bucket.tempo_traces.arn}/*"
]
},
{
Effect = "Allow"
Action = [
"kms:Encrypt",
"kms:Decrypt",
"kms:GenerateDataKey"
]
Resource = [aws_kms_key.s3.arn]
}
]
})
tags = var.tags
}
# Policy for Stalwart to access email blobs
resource "aws_iam_policy" "stalwart_s3" {
name = "${var.name}-stalwart-s3-access"
description = "Allow Stalwart to access S3 bucket for email blob storage"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject",
"s3:ListBucket"
]
Resource = [
aws_s3_bucket.email_blobs.arn,
"${aws_s3_bucket.email_blobs.arn}/*"
]
},
{
Effect = "Allow"
Action = [
"kms:Encrypt",
"kms:Decrypt",
"kms:GenerateDataKey"
]
Resource = [aws_kms_key.s3.arn]
}
]
})
tags = var.tags
}
# Policy for Forgejo to access Git LFS bucket
resource "aws_iam_policy" "forgejo_s3" {
name = "${var.name}-forgejo-s3-access"
description = "Allow Forgejo to access S3 bucket for Git LFS storage"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject",
"s3:ListBucket"
]
Resource = [
aws_s3_bucket.git_lfs.arn,
"${aws_s3_bucket.git_lfs.arn}/*"
]
},
{
Effect = "Allow"
Action = [
"kms:Encrypt",
"kms:Decrypt",
"kms:GenerateDataKey"
]
Resource = [aws_kms_key.s3.arn]
}
]
})
tags = var.tags
}
# ============================================================================
# DATA SOURCES
# ============================================================================
data "aws_caller_identity" "current" {}