mirror of
https://github.com/ghndrx/terraform-foundation.git
synced 2026-02-10 06:45:06 +00:00
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:
506
terraform/modules/cloudtrail/main.tf
Normal file
506
terraform/modules/cloudtrail/main.tf
Normal file
@@ -0,0 +1,506 @@
|
||||
################################################################################
|
||||
# CloudTrail Module
|
||||
#
|
||||
# Audit logging for AWS API activity:
|
||||
# - Management events (console, CLI, SDK)
|
||||
# - Data events (S3, Lambda, DynamoDB)
|
||||
# - Insights events (anomaly detection)
|
||||
# - Multi-region trail
|
||||
# - KMS encryption
|
||||
# - CloudWatch Logs integration
|
||||
# - S3 bucket with lifecycle
|
||||
#
|
||||
# Usage:
|
||||
# module "cloudtrail" {
|
||||
# source = "../modules/cloudtrail"
|
||||
# name = "org-trail"
|
||||
#
|
||||
# enable_data_events = true
|
||||
# data_event_buckets = ["my-bucket"]
|
||||
# }
|
||||
################################################################################
|
||||
|
||||
terraform {
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = ">= 5.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variable "name" {
|
||||
type = string
|
||||
description = "Trail name"
|
||||
}
|
||||
|
||||
variable "s3_bucket_name" {
|
||||
type = string
|
||||
default = ""
|
||||
description = "S3 bucket for logs (created if empty)"
|
||||
}
|
||||
|
||||
variable "is_multi_region" {
|
||||
type = bool
|
||||
default = true
|
||||
description = "Enable multi-region trail"
|
||||
}
|
||||
|
||||
variable "is_organization_trail" {
|
||||
type = bool
|
||||
default = false
|
||||
description = "Organization-wide trail (requires org management account)"
|
||||
}
|
||||
|
||||
variable "enable_log_file_validation" {
|
||||
type = bool
|
||||
default = true
|
||||
description = "Enable log file integrity validation"
|
||||
}
|
||||
|
||||
variable "include_global_service_events" {
|
||||
type = bool
|
||||
default = true
|
||||
description = "Include global service events (IAM, STS, CloudFront)"
|
||||
}
|
||||
|
||||
variable "enable_cloudwatch_logs" {
|
||||
type = bool
|
||||
default = true
|
||||
description = "Send logs to CloudWatch Logs"
|
||||
}
|
||||
|
||||
variable "cloudwatch_log_retention_days" {
|
||||
type = number
|
||||
default = 90
|
||||
description = "CloudWatch log retention in days"
|
||||
}
|
||||
|
||||
variable "enable_insights" {
|
||||
type = bool
|
||||
default = false
|
||||
description = "Enable CloudTrail Insights (additional cost)"
|
||||
}
|
||||
|
||||
variable "insight_selectors" {
|
||||
type = list(string)
|
||||
default = ["ApiCallRateInsight", "ApiErrorRateInsight"]
|
||||
description = "Insight types to enable"
|
||||
}
|
||||
|
||||
variable "enable_data_events" {
|
||||
type = bool
|
||||
default = false
|
||||
description = "Enable data events logging"
|
||||
}
|
||||
|
||||
variable "data_event_s3_buckets" {
|
||||
type = list(string)
|
||||
default = []
|
||||
description = "S3 bucket ARNs for data events (empty = all buckets)"
|
||||
}
|
||||
|
||||
variable "data_event_lambda_functions" {
|
||||
type = list(string)
|
||||
default = []
|
||||
description = "Lambda function ARNs for data events (empty = all functions)"
|
||||
}
|
||||
|
||||
variable "data_event_dynamodb_tables" {
|
||||
type = list(string)
|
||||
default = []
|
||||
description = "DynamoDB table ARNs for data events"
|
||||
}
|
||||
|
||||
variable "kms_key_arn" {
|
||||
type = string
|
||||
default = ""
|
||||
description = "KMS key ARN for encryption (created if empty)"
|
||||
}
|
||||
|
||||
variable "s3_log_retention_days" {
|
||||
type = number
|
||||
default = 365
|
||||
description = "S3 log retention in days"
|
||||
}
|
||||
|
||||
variable "s3_transition_to_glacier_days" {
|
||||
type = number
|
||||
default = 90
|
||||
description = "Days before transitioning logs to Glacier"
|
||||
}
|
||||
|
||||
variable "tags" {
|
||||
type = map(string)
|
||||
default = {}
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Data Sources
|
||||
################################################################################
|
||||
|
||||
data "aws_caller_identity" "current" {}
|
||||
data "aws_region" "current" {}
|
||||
data "aws_partition" "current" {}
|
||||
|
||||
locals {
|
||||
bucket_name = var.s3_bucket_name != "" ? var.s3_bucket_name : "${var.name}-cloudtrail-${data.aws_caller_identity.current.account_id}"
|
||||
create_bucket = var.s3_bucket_name == ""
|
||||
create_kms = var.kms_key_arn == ""
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# KMS Key
|
||||
################################################################################
|
||||
|
||||
resource "aws_kms_key" "cloudtrail" {
|
||||
count = local.create_kms ? 1 : 0
|
||||
|
||||
description = "CloudTrail encryption key for ${var.name}"
|
||||
deletion_window_in_days = 30
|
||||
enable_key_rotation = true
|
||||
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [
|
||||
{
|
||||
Sid = "Enable IAM policies"
|
||||
Effect = "Allow"
|
||||
Principal = {
|
||||
AWS = "arn:${data.aws_partition.current.partition}:iam::${data.aws_caller_identity.current.account_id}:root"
|
||||
}
|
||||
Action = "kms:*"
|
||||
Resource = "*"
|
||||
},
|
||||
{
|
||||
Sid = "Allow CloudTrail to encrypt logs"
|
||||
Effect = "Allow"
|
||||
Principal = {
|
||||
Service = "cloudtrail.amazonaws.com"
|
||||
}
|
||||
Action = [
|
||||
"kms:GenerateDataKey*",
|
||||
"kms:DescribeKey"
|
||||
]
|
||||
Resource = "*"
|
||||
Condition = {
|
||||
StringEquals = {
|
||||
"aws:SourceArn" = "arn:${data.aws_partition.current.partition}:cloudtrail:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:trail/${var.name}"
|
||||
}
|
||||
StringLike = {
|
||||
"kms:EncryptionContext:aws:cloudtrail:arn" = "arn:${data.aws_partition.current.partition}:cloudtrail:*:${data.aws_caller_identity.current.account_id}:trail/*"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
Sid = "Allow CloudTrail to describe key"
|
||||
Effect = "Allow"
|
||||
Principal = {
|
||||
Service = "cloudtrail.amazonaws.com"
|
||||
}
|
||||
Action = "kms:DescribeKey"
|
||||
Resource = "*"
|
||||
},
|
||||
{
|
||||
Sid = "Allow log decryption"
|
||||
Effect = "Allow"
|
||||
Principal = {
|
||||
AWS = "arn:${data.aws_partition.current.partition}:iam::${data.aws_caller_identity.current.account_id}:root"
|
||||
}
|
||||
Action = [
|
||||
"kms:Decrypt",
|
||||
"kms:ReEncryptFrom"
|
||||
]
|
||||
Resource = "*"
|
||||
Condition = {
|
||||
StringEquals = {
|
||||
"kms:CallerAccount" = data.aws_caller_identity.current.account_id
|
||||
}
|
||||
StringLike = {
|
||||
"kms:EncryptionContext:aws:cloudtrail:arn" = "arn:${data.aws_partition.current.partition}:cloudtrail:*:${data.aws_caller_identity.current.account_id}:trail/*"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
tags = merge(var.tags, { Name = "${var.name}-cloudtrail" })
|
||||
}
|
||||
|
||||
resource "aws_kms_alias" "cloudtrail" {
|
||||
count = local.create_kms ? 1 : 0
|
||||
name = "alias/${var.name}-cloudtrail"
|
||||
target_key_id = aws_kms_key.cloudtrail[0].key_id
|
||||
}
|
||||
|
||||
locals {
|
||||
kms_key_arn = local.create_kms ? aws_kms_key.cloudtrail[0].arn : var.kms_key_arn
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# S3 Bucket
|
||||
################################################################################
|
||||
|
||||
resource "aws_s3_bucket" "cloudtrail" {
|
||||
count = local.create_bucket ? 1 : 0
|
||||
bucket = local.bucket_name
|
||||
|
||||
tags = merge(var.tags, { Name = local.bucket_name })
|
||||
}
|
||||
|
||||
resource "aws_s3_bucket_versioning" "cloudtrail" {
|
||||
count = local.create_bucket ? 1 : 0
|
||||
bucket = aws_s3_bucket.cloudtrail[0].id
|
||||
|
||||
versioning_configuration {
|
||||
status = "Enabled"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_s3_bucket_server_side_encryption_configuration" "cloudtrail" {
|
||||
count = local.create_bucket ? 1 : 0
|
||||
bucket = aws_s3_bucket.cloudtrail[0].id
|
||||
|
||||
rule {
|
||||
apply_server_side_encryption_by_default {
|
||||
sse_algorithm = "aws:kms"
|
||||
kms_master_key_id = local.kms_key_arn
|
||||
}
|
||||
bucket_key_enabled = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_s3_bucket_public_access_block" "cloudtrail" {
|
||||
count = local.create_bucket ? 1 : 0
|
||||
bucket = aws_s3_bucket.cloudtrail[0].id
|
||||
|
||||
block_public_acls = true
|
||||
block_public_policy = true
|
||||
ignore_public_acls = true
|
||||
restrict_public_buckets = true
|
||||
}
|
||||
|
||||
resource "aws_s3_bucket_lifecycle_configuration" "cloudtrail" {
|
||||
count = local.create_bucket ? 1 : 0
|
||||
bucket = aws_s3_bucket.cloudtrail[0].id
|
||||
|
||||
rule {
|
||||
id = "archive-and-expire"
|
||||
status = "Enabled"
|
||||
|
||||
transition {
|
||||
days = var.s3_transition_to_glacier_days
|
||||
storage_class = "GLACIER"
|
||||
}
|
||||
|
||||
expiration {
|
||||
days = var.s3_log_retention_days
|
||||
}
|
||||
|
||||
noncurrent_version_expiration {
|
||||
noncurrent_days = 30
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_s3_bucket_policy" "cloudtrail" {
|
||||
count = local.create_bucket ? 1 : 0
|
||||
bucket = aws_s3_bucket.cloudtrail[0].id
|
||||
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [
|
||||
{
|
||||
Sid = "AWSCloudTrailAclCheck"
|
||||
Effect = "Allow"
|
||||
Principal = {
|
||||
Service = "cloudtrail.amazonaws.com"
|
||||
}
|
||||
Action = "s3:GetBucketAcl"
|
||||
Resource = aws_s3_bucket.cloudtrail[0].arn
|
||||
Condition = {
|
||||
StringEquals = {
|
||||
"aws:SourceArn" = "arn:${data.aws_partition.current.partition}:cloudtrail:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:trail/${var.name}"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
Sid = "AWSCloudTrailWrite"
|
||||
Effect = "Allow"
|
||||
Principal = {
|
||||
Service = "cloudtrail.amazonaws.com"
|
||||
}
|
||||
Action = "s3:PutObject"
|
||||
Resource = "${aws_s3_bucket.cloudtrail[0].arn}/*"
|
||||
Condition = {
|
||||
StringEquals = {
|
||||
"s3:x-amz-acl" = "bucket-owner-full-control"
|
||||
"aws:SourceArn" = "arn:${data.aws_partition.current.partition}:cloudtrail:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:trail/${var.name}"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
Sid = "DenyInsecureTransport"
|
||||
Effect = "Deny"
|
||||
Principal = "*"
|
||||
Action = "s3:*"
|
||||
Resource = [
|
||||
aws_s3_bucket.cloudtrail[0].arn,
|
||||
"${aws_s3_bucket.cloudtrail[0].arn}/*"
|
||||
]
|
||||
Condition = {
|
||||
Bool = {
|
||||
"aws:SecureTransport" = "false"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# CloudWatch Logs
|
||||
################################################################################
|
||||
|
||||
resource "aws_cloudwatch_log_group" "cloudtrail" {
|
||||
count = var.enable_cloudwatch_logs ? 1 : 0
|
||||
name = "/aws/cloudtrail/${var.name}"
|
||||
retention_in_days = var.cloudwatch_log_retention_days
|
||||
|
||||
tags = merge(var.tags, { Name = var.name })
|
||||
}
|
||||
|
||||
resource "aws_iam_role" "cloudtrail_cloudwatch" {
|
||||
count = var.enable_cloudwatch_logs ? 1 : 0
|
||||
name = "${var.name}-cloudtrail-cloudwatch"
|
||||
|
||||
assume_role_policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [{
|
||||
Effect = "Allow"
|
||||
Action = "sts:AssumeRole"
|
||||
Principal = { Service = "cloudtrail.amazonaws.com" }
|
||||
}]
|
||||
})
|
||||
|
||||
tags = merge(var.tags, { Name = "${var.name}-cloudtrail-cloudwatch" })
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy" "cloudtrail_cloudwatch" {
|
||||
count = var.enable_cloudwatch_logs ? 1 : 0
|
||||
name = "cloudwatch-logs"
|
||||
role = aws_iam_role.cloudtrail_cloudwatch[0].id
|
||||
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [{
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"logs:CreateLogStream",
|
||||
"logs:PutLogEvents"
|
||||
]
|
||||
Resource = "${aws_cloudwatch_log_group.cloudtrail[0].arn}:*"
|
||||
}]
|
||||
})
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# CloudTrail
|
||||
################################################################################
|
||||
|
||||
resource "aws_cloudtrail" "main" {
|
||||
name = var.name
|
||||
s3_bucket_name = local.create_bucket ? aws_s3_bucket.cloudtrail[0].id : var.s3_bucket_name
|
||||
include_global_service_events = var.include_global_service_events
|
||||
is_multi_region_trail = var.is_multi_region
|
||||
is_organization_trail = var.is_organization_trail
|
||||
enable_log_file_validation = var.enable_log_file_validation
|
||||
kms_key_id = local.kms_key_arn
|
||||
|
||||
cloud_watch_logs_group_arn = var.enable_cloudwatch_logs ? "${aws_cloudwatch_log_group.cloudtrail[0].arn}:*" : null
|
||||
cloud_watch_logs_role_arn = var.enable_cloudwatch_logs ? aws_iam_role.cloudtrail_cloudwatch[0].arn : null
|
||||
|
||||
# Insights
|
||||
dynamic "insight_selector" {
|
||||
for_each = var.enable_insights ? var.insight_selectors : []
|
||||
content {
|
||||
insight_type = insight_selector.value
|
||||
}
|
||||
}
|
||||
|
||||
# Data events
|
||||
dynamic "event_selector" {
|
||||
for_each = var.enable_data_events ? [1] : []
|
||||
content {
|
||||
read_write_type = "All"
|
||||
include_management_events = true
|
||||
|
||||
# S3 data events
|
||||
dynamic "data_resource" {
|
||||
for_each = length(var.data_event_s3_buckets) > 0 ? [1] : (var.enable_data_events ? [1] : [])
|
||||
content {
|
||||
type = "AWS::S3::Object"
|
||||
values = length(var.data_event_s3_buckets) > 0 ? var.data_event_s3_buckets : ["arn:aws:s3"]
|
||||
}
|
||||
}
|
||||
|
||||
# Lambda data events
|
||||
dynamic "data_resource" {
|
||||
for_each = length(var.data_event_lambda_functions) > 0 ? [1] : []
|
||||
content {
|
||||
type = "AWS::Lambda::Function"
|
||||
values = var.data_event_lambda_functions
|
||||
}
|
||||
}
|
||||
|
||||
# DynamoDB data events
|
||||
dynamic "data_resource" {
|
||||
for_each = length(var.data_event_dynamodb_tables) > 0 ? [1] : []
|
||||
content {
|
||||
type = "AWS::DynamoDB::Table"
|
||||
values = var.data_event_dynamodb_tables
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tags = merge(var.tags, { Name = var.name })
|
||||
|
||||
depends_on = [
|
||||
aws_s3_bucket_policy.cloudtrail,
|
||||
]
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Outputs
|
||||
################################################################################
|
||||
|
||||
output "trail_arn" {
|
||||
value = aws_cloudtrail.main.arn
|
||||
description = "CloudTrail ARN"
|
||||
}
|
||||
|
||||
output "trail_name" {
|
||||
value = aws_cloudtrail.main.name
|
||||
description = "CloudTrail name"
|
||||
}
|
||||
|
||||
output "s3_bucket" {
|
||||
value = local.create_bucket ? aws_s3_bucket.cloudtrail[0].id : var.s3_bucket_name
|
||||
description = "S3 bucket for CloudTrail logs"
|
||||
}
|
||||
|
||||
output "kms_key_arn" {
|
||||
value = local.kms_key_arn
|
||||
description = "KMS key ARN for encryption"
|
||||
}
|
||||
|
||||
output "cloudwatch_log_group" {
|
||||
value = var.enable_cloudwatch_logs ? aws_cloudwatch_log_group.cloudtrail[0].name : null
|
||||
description = "CloudWatch Logs group"
|
||||
}
|
||||
|
||||
output "home_region" {
|
||||
value = aws_cloudtrail.main.home_region
|
||||
description = "Trail home region"
|
||||
}
|
||||
Reference in New Issue
Block a user