Files
Greg Hendrickson 6136cde9bb 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
2026-02-02 02:57:23 +00:00

335 lines
8.6 KiB
HCL

################################################################################
# Security Baseline Module
#
# Enables core AWS security services:
# - GuardDuty (threat detection)
# - Security Hub (security posture)
# - AWS Config (configuration compliance)
# - IAM Access Analyzer
#
# For multi-account: Deploy in management account, then enable delegated admin
################################################################################
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.0"
}
}
}
variable "name" {
type = string
description = "Name prefix for resources"
}
variable "enable_guardduty" {
type = bool
default = true
}
variable "enable_securityhub" {
type = bool
default = true
}
variable "enable_config" {
type = bool
default = true
}
variable "enable_access_analyzer" {
type = bool
default = true
}
variable "enable_macie" {
type = bool
default = false
description = "Macie for S3 data classification (additional cost)"
}
variable "config_bucket_name" {
type = string
description = "S3 bucket for AWS Config recordings"
}
variable "guardduty_finding_publishing_frequency" {
type = string
default = "FIFTEEN_MINUTES"
validation {
condition = contains(["FIFTEEN_MINUTES", "ONE_HOUR", "SIX_HOURS"], var.guardduty_finding_publishing_frequency)
error_message = "Must be FIFTEEN_MINUTES, ONE_HOUR, or SIX_HOURS"
}
}
variable "securityhub_standards" {
type = list(string)
default = [
"aws-foundational-security-best-practices/v/1.0.0",
"cis-aws-foundations-benchmark/v/1.4.0",
]
description = "Security Hub standards to enable"
}
variable "config_rules" {
type = list(string)
default = []
description = "Additional AWS Config managed rule identifiers to enable"
}
variable "tags" {
type = map(string)
default = {}
}
data "aws_caller_identity" "current" {}
data "aws_region" "current" {}
################################################################################
# GuardDuty
################################################################################
resource "aws_guardduty_detector" "main" {
count = var.enable_guardduty ? 1 : 0
enable = true
finding_publishing_frequency = var.guardduty_finding_publishing_frequency
datasources {
s3_logs {
enable = true
}
kubernetes {
audit_logs {
enable = true
}
}
malware_protection {
scan_ec2_instance_with_findings {
ebs_volumes {
enable = true
}
}
}
}
tags = merge(var.tags, { Name = "${var.name}-guardduty" })
}
################################################################################
# Security Hub
################################################################################
resource "aws_securityhub_account" "main" {
count = var.enable_securityhub ? 1 : 0
enable_default_standards = false
auto_enable_controls = true
depends_on = [aws_guardduty_detector.main]
}
resource "aws_securityhub_standards_subscription" "standards" {
for_each = var.enable_securityhub ? toset(var.securityhub_standards) : []
standards_arn = "arn:aws:securityhub:${data.aws_region.current.name}::standards/${each.value}"
depends_on = [aws_securityhub_account.main]
}
################################################################################
# AWS Config
################################################################################
resource "aws_config_configuration_recorder" "main" {
count = var.enable_config ? 1 : 0
name = var.name
role_arn = aws_iam_role.config[0].arn
recording_group {
all_supported = true
include_global_resource_types = true
}
recording_mode {
recording_frequency = "CONTINUOUS"
}
}
resource "aws_config_delivery_channel" "main" {
count = var.enable_config ? 1 : 0
name = var.name
s3_bucket_name = var.config_bucket_name
s3_key_prefix = "config"
snapshot_delivery_properties {
delivery_frequency = "TwentyFour_Hours"
}
depends_on = [aws_config_configuration_recorder.main]
}
resource "aws_config_configuration_recorder_status" "main" {
count = var.enable_config ? 1 : 0
name = aws_config_configuration_recorder.main[0].name
is_enabled = true
depends_on = [aws_config_delivery_channel.main]
}
# IAM Role for Config
resource "aws_iam_role" "config" {
count = var.enable_config ? 1 : 0
name = "${var.name}-config"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = "sts:AssumeRole"
Principal = { Service = "config.amazonaws.com" }
}]
})
tags = merge(var.tags, { Name = "${var.name}-config" })
}
resource "aws_iam_role_policy_attachment" "config" {
count = var.enable_config ? 1 : 0
role = aws_iam_role.config[0].name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWS_ConfigRole"
}
resource "aws_iam_role_policy" "config_s3" {
count = var.enable_config ? 1 : 0
name = "s3-delivery"
role = aws_iam_role.config[0].id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = ["s3:PutObject", "s3:PutObjectAcl"]
Resource = "arn:aws:s3:::${var.config_bucket_name}/config/*"
Condition = {
StringEquals = {
"s3:x-amz-acl" = "bucket-owner-full-control"
}
}
}, {
Effect = "Allow"
Action = ["s3:GetBucketAcl"]
Resource = "arn:aws:s3:::${var.config_bucket_name}"
}]
})
}
################################################################################
# AWS Config Rules - Security Best Practices
################################################################################
locals {
default_config_rules = [
"ENCRYPTED_VOLUMES",
"RDS_STORAGE_ENCRYPTED",
"S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED",
"S3_BUCKET_SSL_REQUESTS_ONLY",
"S3_BUCKET_PUBLIC_READ_PROHIBITED",
"S3_BUCKET_PUBLIC_WRITE_PROHIBITED",
"RESTRICTED_SSH",
"VPC_DEFAULT_SECURITY_GROUP_CLOSED",
"VPC_FLOW_LOGS_ENABLED",
"CLOUD_TRAIL_ENABLED",
"CLOUD_TRAIL_ENCRYPTION_ENABLED",
"CLOUD_TRAIL_LOG_FILE_VALIDATION_ENABLED",
"IAM_ROOT_ACCESS_KEY_CHECK",
"IAM_USER_MFA_ENABLED",
"MFA_ENABLED_FOR_IAM_CONSOLE_ACCESS",
"ROOT_ACCOUNT_MFA_ENABLED",
"RDS_INSTANCE_PUBLIC_ACCESS_CHECK",
"GUARDDUTY_ENABLED_CENTRALIZED",
"SECURITYHUB_ENABLED",
"EBS_OPTIMIZED_INSTANCE",
"EC2_IMDSV2_CHECK",
"EKS_SECRETS_ENCRYPTED",
"LAMBDA_FUNCTION_PUBLIC_ACCESS_PROHIBITED",
"LAMBDA_INSIDE_VPC",
]
all_config_rules = distinct(concat(local.default_config_rules, var.config_rules))
}
resource "aws_config_config_rule" "rules" {
for_each = var.enable_config ? toset(local.all_config_rules) : []
name = lower(replace(each.value, "_", "-"))
source {
owner = "AWS"
source_identifier = each.value
}
depends_on = [aws_config_configuration_recorder_status.main]
tags = merge(var.tags, { Name = lower(replace(each.value, "_", "-")) })
}
################################################################################
# IAM Access Analyzer
################################################################################
resource "aws_accessanalyzer_analyzer" "main" {
count = var.enable_access_analyzer ? 1 : 0
analyzer_name = var.name
type = "ACCOUNT"
tags = merge(var.tags, { Name = "${var.name}-access-analyzer" })
}
################################################################################
# Macie (Optional)
################################################################################
resource "aws_macie2_account" "main" {
count = var.enable_macie ? 1 : 0
finding_publishing_frequency = "FIFTEEN_MINUTES"
status = "ENABLED"
}
################################################################################
# Outputs
################################################################################
output "guardduty_detector_id" {
value = var.enable_guardduty ? aws_guardduty_detector.main[0].id : null
}
output "securityhub_account_id" {
value = var.enable_securityhub ? aws_securityhub_account.main[0].id : null
}
output "config_recorder_id" {
value = var.enable_config ? aws_config_configuration_recorder.main[0].id : null
}
output "access_analyzer_arn" {
value = var.enable_access_analyzer ? aws_accessanalyzer_analyzer.main[0].arn : null
}
output "enabled_services" {
value = {
guardduty = var.enable_guardduty
securityhub = var.enable_securityhub
config = var.enable_config
access_analyzer = var.enable_access_analyzer
macie = var.enable_macie
}
}