AWS environment: - VPC (3-AZ, public + private subnets, NAT gateways, VPC endpoints for ECR/SM/CW) - ECS Fargate service (sentryagent/agentidp) — secrets from Secrets Manager - RDS PostgreSQL 14 (Multi-AZ, encrypted, VPC-internal, storage autoscaling) - ElastiCache Redis 7 (primary + replica, at-rest + in-transit encryption) - ALB with HTTPS/443, HTTP→HTTPS redirect, ACM certificate - Route 53 alias record GCP environment: - VPC + private services access + Serverless VPC connector - Cloud Run service — secrets from Secret Manager - Cloud SQL PostgreSQL 14 (private IP, no public endpoint) - Cloud Memorystore Redis 7 (VPC-internal, AUTH enabled) Shared: - 4 reusable modules: agentidp (dual AWS/GCP), rds, redis, lb - No hardcoded secrets; all sensitive vars marked sensitive=true - terraform.tfvars.example for both environments - docs/devops/deployment.md — AWS + GCP step-by-step walkthrough, rollback procedures Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
177 lines
5.8 KiB
HCL
177 lines
5.8 KiB
HCL
################################################################################
|
|
# Module: redis
|
|
# Main — AWS ElastiCache Redis 7
|
|
#
|
|
# - Single shard (cluster mode disabled): one primary + one replica
|
|
# - Encryption at rest and in transit (TLS)
|
|
# - AUTH token required when transit encryption is enabled
|
|
# - VPC-internal only — no public access
|
|
# - Access restricted to explicitly allowed security groups (app only)
|
|
# - Slow log + engine log delivery to CloudWatch
|
|
################################################################################
|
|
|
|
terraform {
|
|
required_version = ">= 1.6.0"
|
|
|
|
required_providers {
|
|
aws = {
|
|
source = "hashicorp/aws"
|
|
version = ">= 5.40.0"
|
|
}
|
|
}
|
|
}
|
|
|
|
locals {
|
|
identifier = "${var.project}-${var.environment}"
|
|
|
|
common_tags = {
|
|
environment = var.environment
|
|
project = var.project
|
|
managed_by = "terraform"
|
|
}
|
|
}
|
|
|
|
################################################################################
|
|
# CloudWatch Log Group for Redis logs
|
|
################################################################################
|
|
|
|
resource "aws_cloudwatch_log_group" "redis" {
|
|
count = var.log_delivery_enabled ? 1 : 0
|
|
|
|
name = var.log_group_name
|
|
retention_in_days = 30
|
|
|
|
tags = local.common_tags
|
|
}
|
|
|
|
################################################################################
|
|
# Security Group — only the app SGs may connect on 6379
|
|
################################################################################
|
|
|
|
resource "aws_security_group" "redis" {
|
|
name = "${local.identifier}-redis-sg"
|
|
description = "Controls inbound access to ElastiCache Redis — allow only app SG on 6379"
|
|
vpc_id = var.vpc_id
|
|
|
|
egress {
|
|
description = "All outbound"
|
|
from_port = 0
|
|
to_port = 0
|
|
protocol = "-1"
|
|
cidr_blocks = ["0.0.0.0/0"]
|
|
}
|
|
|
|
tags = merge(local.common_tags, {
|
|
Name = "${local.identifier}-redis-sg"
|
|
})
|
|
}
|
|
|
|
resource "aws_security_group_rule" "redis_ingress_from_app" {
|
|
for_each = toset(var.allowed_security_group_ids)
|
|
|
|
type = "ingress"
|
|
description = "Redis from app security group"
|
|
from_port = 6379
|
|
to_port = 6379
|
|
protocol = "tcp"
|
|
source_security_group_id = each.value
|
|
security_group_id = aws_security_group.redis.id
|
|
}
|
|
|
|
################################################################################
|
|
# ElastiCache Subnet Group
|
|
################################################################################
|
|
|
|
resource "aws_elasticache_subnet_group" "main" {
|
|
name = "${local.identifier}-redis-subnet-group"
|
|
description = "Private subnets for AgentIdP ElastiCache Redis"
|
|
subnet_ids = var.subnet_ids
|
|
|
|
tags = local.common_tags
|
|
}
|
|
|
|
################################################################################
|
|
# ElastiCache Parameter Group — Redis 7.x defaults are fine; custom group
|
|
# allows future tuning without recreating the replication group.
|
|
################################################################################
|
|
|
|
resource "aws_elasticache_parameter_group" "main" {
|
|
name = "${local.identifier}-redis7-params"
|
|
family = "redis7"
|
|
description = "AgentIdP Redis 7 parameter group"
|
|
|
|
# Disable dangerous commands that could truncate data in production
|
|
parameter {
|
|
name = "lazyfree-lazy-eviction"
|
|
value = "yes"
|
|
}
|
|
|
|
parameter {
|
|
name = "lazyfree-lazy-expire"
|
|
value = "yes"
|
|
}
|
|
|
|
tags = local.common_tags
|
|
}
|
|
|
|
################################################################################
|
|
# ElastiCache Replication Group (cluster mode disabled)
|
|
#
|
|
# cluster_mode = 0 (disabled) gives a single-shard setup:
|
|
# - 1 primary node
|
|
# - num_cache_clusters - 1 replica nodes
|
|
# This matches the application usage: token revocation (SET/GET/DEL),
|
|
# rate limiting (INCR/EXPIRE), and monthly counters (INCR) — no sharding needed.
|
|
################################################################################
|
|
|
|
resource "aws_elasticache_replication_group" "main" {
|
|
replication_group_id = local.identifier
|
|
description = "AgentIdP Redis 7 — token revocation, rate limiting, counters"
|
|
|
|
# Engine
|
|
engine = "redis"
|
|
engine_version = var.engine_version
|
|
node_type = var.node_type
|
|
parameter_group_name = aws_elasticache_parameter_group.main.name
|
|
port = 6379
|
|
|
|
# Topology — single shard, primary + replica
|
|
num_cache_clusters = var.num_cache_clusters
|
|
automatic_failover_enabled = var.automatic_failover_enabled
|
|
multi_az_enabled = var.multi_az_enabled
|
|
|
|
# Network — VPC-internal, no public endpoints
|
|
subnet_group_name = aws_elasticache_subnet_group.main.name
|
|
security_group_ids = [aws_security_group.redis.id]
|
|
|
|
# Security
|
|
at_rest_encryption_enabled = var.at_rest_encryption_enabled
|
|
transit_encryption_enabled = var.transit_encryption_enabled
|
|
auth_token = var.transit_encryption_enabled && var.auth_token != "" ? var.auth_token : null
|
|
|
|
# Maintenance and snapshots
|
|
maintenance_window = var.maintenance_window
|
|
snapshot_retention_limit = var.snapshot_retention_limit
|
|
snapshot_window = var.snapshot_window
|
|
apply_immediately = var.apply_immediately
|
|
|
|
# Log delivery to CloudWatch
|
|
dynamic "log_delivery_configuration" {
|
|
for_each = var.log_delivery_enabled ? [
|
|
{ log_type = "slow-log", log_format = "json" },
|
|
{ log_type = "engine-log", log_format = "json" }
|
|
] : []
|
|
|
|
content {
|
|
destination = var.log_delivery_enabled ? aws_cloudwatch_log_group.redis[0].name : ""
|
|
destination_type = "cloudwatch-logs"
|
|
log_format = log_delivery_configuration.value.log_format
|
|
log_type = log_delivery_configuration.value.log_type
|
|
}
|
|
}
|
|
|
|
tags = merge(local.common_tags, {
|
|
Name = local.identifier
|
|
})
|
|
}
|