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>
429 lines
10 KiB
HCL
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" {}
|