feat: Terraform Foundation - AWS Landing Zone

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
This commit is contained in:
2026-02-01 20:06:28 +00:00
commit 6136cde9bb
145 changed files with 30832 additions and 0 deletions

View File

@@ -0,0 +1,458 @@
################################################################################
# Workload: OpenSearch (Elasticsearch)
#
# Search and analytics with:
# - Serverless or provisioned clusters
# - Fine-grained access control
# - VPC or public access
# - Cognito authentication
# - UltraWarm for cost-effective storage
# - Cross-cluster search
#
# Use cases: Log analytics, full-text search, observability
################################################################################
terraform {
required_version = ">= 1.5"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.0"
}
}
backend "s3" {
key = "05-workloads/<TENANT>-<NAME>-opensearch/terraform.tfstate"
}
}
################################################################################
# Configuration - UPDATE THESE
################################################################################
locals {
# Naming
tenant = "<TENANT>"
name = "<NAME>"
env = "prod"
domain_name = "${local.tenant}-${local.name}-${local.env}"
# Engine
engine_version = "OpenSearch_2.11"
# Cluster sizing
cluster = {
# Data nodes
instance_type = "t3.medium.search" # t3.small.search for dev
instance_count = 2
# Dedicated master nodes (recommended for production)
dedicated_master_enabled = local.env == "prod"
dedicated_master_type = "t3.medium.search"
dedicated_master_count = 3
# Multi-AZ
zone_awareness_enabled = local.env == "prod"
availability_zone_count = local.env == "prod" ? 2 : 1
}
# Storage
storage = {
type = "gp3"
size_gb = 100
iops = 3000
throughput = 125
}
# UltraWarm (cost-effective warm storage)
ultrawarm = {
enabled = false
type = "ultrawarm1.medium.search"
count = 2
}
# Network
# Option 1: VPC (private, more secure)
vpc_enabled = true
vpc_id = "" # data.terraform_remote_state.network.outputs.vpc_id
private_subnet_ids = [] # data.terraform_remote_state.network.outputs.private_subnet_ids
# Option 2: Public (set vpc_enabled = false)
# Uses IP-based access policy
# Access control
enable_fine_grained_access = true
master_user_name = "admin"
# Cognito authentication (optional, for Dashboards)
cognito = {
enabled = false
user_pool_id = ""
identity_pool_id = ""
role_arn = ""
}
# Encryption
encrypt_at_rest = true
node_to_node_encryption = true
# Logging
log_types = ["INDEX_SLOW_LOGS", "SEARCH_SLOW_LOGS", "ES_APPLICATION_LOGS"]
# Auto-tune
auto_tune_enabled = true
}
################################################################################
# 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" {}
################################################################################
# Random Password for Master User
################################################################################
resource "random_password" "master" {
count = local.enable_fine_grained_access ? 1 : 0
length = 24
special = true
override_special = "!#$%&*()-_=+[]{}<>:?"
}
################################################################################
# Secrets Manager
################################################################################
resource "aws_secretsmanager_secret" "opensearch" {
count = local.enable_fine_grained_access ? 1 : 0
name = "${local.tenant}/${local.env}/${local.name}/opensearch"
description = "OpenSearch master credentials"
tags = { Name = "${local.domain_name}-credentials" }
}
resource "aws_secretsmanager_secret_version" "opensearch" {
count = local.enable_fine_grained_access ? 1 : 0
secret_id = aws_secretsmanager_secret.opensearch[0].id
secret_string = jsonencode({
username = local.master_user_name
password = random_password.master[0].result
endpoint = aws_opensearch_domain.main.endpoint
})
}
################################################################################
# CloudWatch Log Groups
################################################################################
resource "aws_cloudwatch_log_group" "opensearch" {
for_each = toset(local.log_types)
name = "/aws/opensearch/${local.domain_name}/${lower(each.key)}"
retention_in_days = 30
tags = { Name = "${local.domain_name}-${lower(each.key)}" }
}
resource "aws_cloudwatch_log_resource_policy" "opensearch" {
policy_name = "${local.domain_name}-logs"
policy_document = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = {
Service = "es.amazonaws.com"
}
Action = [
"logs:PutLogEvents",
"logs:CreateLogStream"
]
Resource = "arn:aws:logs:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:log-group:/aws/opensearch/${local.domain_name}/*"
}]
})
}
################################################################################
# Security Group (VPC mode)
################################################################################
resource "aws_security_group" "opensearch" {
count = local.vpc_enabled && length(local.vpc_id) > 0 ? 1 : 0
name = "${local.domain_name}-opensearch"
vpc_id = local.vpc_id
ingress {
description = "HTTPS from VPC"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["10.0.0.0/8"]
}
egress {
description = "All outbound"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = { Name = "${local.domain_name}-opensearch" }
}
################################################################################
# IAM Service-Linked Role
################################################################################
resource "aws_iam_service_linked_role" "opensearch" {
count = local.vpc_enabled ? 1 : 0
aws_service_name = "opensearchservice.amazonaws.com"
}
################################################################################
# OpenSearch Domain
################################################################################
resource "aws_opensearch_domain" "main" {
domain_name = local.domain_name
engine_version = local.engine_version
# Cluster configuration
cluster_config {
instance_type = local.cluster.instance_type
instance_count = local.cluster.instance_count
dedicated_master_enabled = local.cluster.dedicated_master_enabled
dedicated_master_type = local.cluster.dedicated_master_enabled ? local.cluster.dedicated_master_type : null
dedicated_master_count = local.cluster.dedicated_master_enabled ? local.cluster.dedicated_master_count : null
zone_awareness_enabled = local.cluster.zone_awareness_enabled
dynamic "zone_awareness_config" {
for_each = local.cluster.zone_awareness_enabled ? [1] : []
content {
availability_zone_count = local.cluster.availability_zone_count
}
}
# UltraWarm
warm_enabled = local.ultrawarm.enabled
warm_type = local.ultrawarm.enabled ? local.ultrawarm.type : null
warm_count = local.ultrawarm.enabled ? local.ultrawarm.count : null
}
# Storage
ebs_options {
ebs_enabled = true
volume_type = local.storage.type
volume_size = local.storage.size_gb
iops = local.storage.type == "gp3" ? local.storage.iops : null
throughput = local.storage.type == "gp3" ? local.storage.throughput : null
}
# VPC configuration
dynamic "vpc_options" {
for_each = local.vpc_enabled && length(local.private_subnet_ids) > 0 ? [1] : []
content {
subnet_ids = slice(local.private_subnet_ids, 0, local.cluster.availability_zone_count)
security_group_ids = [aws_security_group.opensearch[0].id]
}
}
# Encryption
encrypt_at_rest {
enabled = local.encrypt_at_rest
}
node_to_node_encryption {
enabled = local.node_to_node_encryption
}
domain_endpoint_options {
enforce_https = true
tls_security_policy = "Policy-Min-TLS-1-2-2019-07"
}
# Fine-grained access control
advanced_security_options {
enabled = local.enable_fine_grained_access
internal_user_database_enabled = local.enable_fine_grained_access
dynamic "master_user_options" {
for_each = local.enable_fine_grained_access ? [1] : []
content {
master_user_name = local.master_user_name
master_user_password = random_password.master[0].result
}
}
}
# Cognito authentication
dynamic "cognito_options" {
for_each = local.cognito.enabled ? [1] : []
content {
enabled = true
user_pool_id = local.cognito.user_pool_id
identity_pool_id = local.cognito.identity_pool_id
role_arn = local.cognito.role_arn
}
}
# Logging
dynamic "log_publishing_options" {
for_each = local.log_types
content {
cloudwatch_log_group_arn = aws_cloudwatch_log_group.opensearch[log_publishing_options.value].arn
log_type = log_publishing_options.value
}
}
# Auto-tune
auto_tune_options {
desired_state = local.auto_tune_enabled ? "ENABLED" : "DISABLED"
rollback_on_disable = "NO_ROLLBACK"
}
# Access policy (for non-VPC or fine-grained access)
access_policies = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = {
AWS = "*"
}
Action = "es:*"
Resource = "arn:aws:es:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:domain/${local.domain_name}/*"
Condition = local.vpc_enabled ? {} : {
IpAddress = {
"aws:SourceIp" = ["0.0.0.0/0"] # Restrict in production!
}
}
}
]
})
tags = { Name = local.domain_name }
depends_on = [
aws_iam_service_linked_role.opensearch,
aws_cloudwatch_log_resource_policy.opensearch
]
}
################################################################################
# IAM Policy for Application Access
################################################################################
resource "aws_iam_policy" "opensearch_access" {
name = "${local.domain_name}-access"
description = "Access to ${local.domain_name} OpenSearch domain"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "OpenSearchAccess"
Effect = "Allow"
Action = [
"es:ESHttpGet",
"es:ESHttpHead",
"es:ESHttpPost",
"es:ESHttpPut",
"es:ESHttpDelete"
]
Resource = "${aws_opensearch_domain.main.arn}/*"
}
]
})
tags = { Name = "${local.domain_name}-access" }
}
################################################################################
# Outputs
################################################################################
output "domain_endpoint" {
value = aws_opensearch_domain.main.endpoint
description = "OpenSearch domain endpoint"
}
output "dashboard_endpoint" {
value = aws_opensearch_domain.main.dashboard_endpoint
description = "OpenSearch Dashboards endpoint"
}
output "domain_arn" {
value = aws_opensearch_domain.main.arn
description = "Domain ARN"
}
output "domain_id" {
value = aws_opensearch_domain.main.domain_id
description = "Domain ID"
}
output "secret_arn" {
value = length(aws_secretsmanager_secret.opensearch) > 0 ? aws_secretsmanager_secret.opensearch[0].arn : null
description = "Secrets Manager ARN for credentials"
}
output "access_policy_arn" {
value = aws_iam_policy.opensearch_access.arn
description = "IAM policy for application access"
}
output "kibana_url" {
value = "https://${aws_opensearch_domain.main.dashboard_endpoint}/_dashboards"
description = "OpenSearch Dashboards URL"
}
output "curl_example" {
value = local.enable_fine_grained_access ? <<-EOF
# Get credentials from Secrets Manager
SECRET=$(aws secretsmanager get-secret-value --secret-id ${aws_secretsmanager_secret.opensearch[0].arn} --query SecretString --output text)
USER=$(echo $SECRET | jq -r .username)
PASS=$(echo $SECRET | jq -r .password)
# Query cluster health
curl -u "$USER:$PASS" "https://${aws_opensearch_domain.main.endpoint}/_cluster/health?pretty"
EOF
: null
description = "Example curl commands"
}