mirror of
https://github.com/ghndrx/terraform-foundation.git
synced 2026-02-10 06:45:06 +00:00
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
419 lines
11 KiB
HCL
419 lines
11 KiB
HCL
################################################################################
|
|
# Route53 Zone Module
|
|
#
|
|
# DNS zone management:
|
|
# - Public or private hosted zones
|
|
# - Common record types (A, AAAA, CNAME, MX, TXT)
|
|
# - Alias records (CloudFront, ALB, S3, API Gateway)
|
|
# - DNSSEC signing
|
|
# - Query logging
|
|
# - Health checks
|
|
#
|
|
# Usage:
|
|
# module "dns" {
|
|
# source = "../modules/route53-zone"
|
|
#
|
|
# domain_name = "example.com"
|
|
#
|
|
# records = {
|
|
# "www" = {
|
|
# type = "CNAME"
|
|
# ttl = 300
|
|
# records = ["example.com"]
|
|
# }
|
|
# "mail" = {
|
|
# type = "MX"
|
|
# ttl = 300
|
|
# records = ["10 mail.example.com"]
|
|
# }
|
|
# }
|
|
# }
|
|
################################################################################
|
|
|
|
terraform {
|
|
required_providers {
|
|
aws = {
|
|
source = "hashicorp/aws"
|
|
version = ">= 5.0"
|
|
}
|
|
}
|
|
}
|
|
|
|
variable "domain_name" {
|
|
type = string
|
|
description = "Domain name for the hosted zone"
|
|
}
|
|
|
|
variable "comment" {
|
|
type = string
|
|
default = ""
|
|
description = "Comment for the hosted zone"
|
|
}
|
|
|
|
variable "private_zone" {
|
|
type = bool
|
|
default = false
|
|
description = "Create a private hosted zone"
|
|
}
|
|
|
|
variable "vpc_ids" {
|
|
type = list(string)
|
|
default = []
|
|
description = "VPC IDs to associate with private zone"
|
|
}
|
|
|
|
variable "enable_dnssec" {
|
|
type = bool
|
|
default = false
|
|
description = "Enable DNSSEC signing"
|
|
}
|
|
|
|
variable "enable_query_logging" {
|
|
type = bool
|
|
default = false
|
|
description = "Enable query logging to CloudWatch"
|
|
}
|
|
|
|
variable "query_log_retention_days" {
|
|
type = number
|
|
default = 30
|
|
description = "Query log retention in days"
|
|
}
|
|
|
|
variable "records" {
|
|
type = map(object({
|
|
type = string
|
|
ttl = optional(number, 300)
|
|
records = optional(list(string))
|
|
alias = optional(object({
|
|
name = string
|
|
zone_id = string
|
|
evaluate_target_health = optional(bool, false)
|
|
}))
|
|
health_check_id = optional(string)
|
|
set_identifier = optional(string)
|
|
weight = optional(number)
|
|
latency_routing_region = optional(string)
|
|
geolocation = optional(object({
|
|
continent = optional(string)
|
|
country = optional(string)
|
|
subdivision = optional(string)
|
|
}))
|
|
failover = optional(string)
|
|
}))
|
|
default = {}
|
|
description = "DNS records to create"
|
|
}
|
|
|
|
variable "alias_records" {
|
|
type = map(object({
|
|
type = optional(string, "A")
|
|
target_dns_name = string
|
|
target_zone_id = string
|
|
evaluate_target_health = optional(bool, false)
|
|
}))
|
|
default = {}
|
|
description = "Alias records (simplified syntax for CloudFront, ALB, etc.)"
|
|
}
|
|
|
|
variable "mx_records" {
|
|
type = list(object({
|
|
priority = number
|
|
server = string
|
|
}))
|
|
default = []
|
|
description = "MX records for email"
|
|
}
|
|
|
|
variable "txt_records" {
|
|
type = map(string)
|
|
default = {}
|
|
description = "TXT records (name -> value)"
|
|
}
|
|
|
|
variable "tags" {
|
|
type = map(string)
|
|
default = {}
|
|
}
|
|
|
|
data "aws_region" "current" {}
|
|
|
|
################################################################################
|
|
# Hosted Zone
|
|
################################################################################
|
|
|
|
resource "aws_route53_zone" "main" {
|
|
name = var.domain_name
|
|
comment = var.comment != "" ? var.comment : "Managed by Terraform"
|
|
|
|
dynamic "vpc" {
|
|
for_each = var.private_zone ? var.vpc_ids : []
|
|
content {
|
|
vpc_id = vpc.value
|
|
}
|
|
}
|
|
|
|
tags = merge(var.tags, { Name = var.domain_name })
|
|
}
|
|
|
|
################################################################################
|
|
# Standard Records
|
|
################################################################################
|
|
|
|
resource "aws_route53_record" "records" {
|
|
for_each = var.records
|
|
|
|
zone_id = aws_route53_zone.main.zone_id
|
|
name = each.key == "@" ? var.domain_name : "${each.key}.${var.domain_name}"
|
|
type = each.value.type
|
|
|
|
# Standard records
|
|
ttl = each.value.alias == null ? each.value.ttl : null
|
|
records = each.value.alias == null ? each.value.records : null
|
|
|
|
# Alias records
|
|
dynamic "alias" {
|
|
for_each = each.value.alias != null ? [each.value.alias] : []
|
|
content {
|
|
name = alias.value.name
|
|
zone_id = alias.value.zone_id
|
|
evaluate_target_health = alias.value.evaluate_target_health
|
|
}
|
|
}
|
|
|
|
# Routing policies
|
|
health_check_id = each.value.health_check_id
|
|
set_identifier = each.value.set_identifier
|
|
|
|
dynamic "weighted_routing_policy" {
|
|
for_each = each.value.weight != null ? [1] : []
|
|
content {
|
|
weight = each.value.weight
|
|
}
|
|
}
|
|
|
|
dynamic "latency_routing_policy" {
|
|
for_each = each.value.latency_routing_region != null ? [1] : []
|
|
content {
|
|
region = each.value.latency_routing_region
|
|
}
|
|
}
|
|
|
|
dynamic "geolocation_routing_policy" {
|
|
for_each = each.value.geolocation != null ? [each.value.geolocation] : []
|
|
content {
|
|
continent = geolocation_routing_policy.value.continent
|
|
country = geolocation_routing_policy.value.country
|
|
subdivision = geolocation_routing_policy.value.subdivision
|
|
}
|
|
}
|
|
|
|
dynamic "failover_routing_policy" {
|
|
for_each = each.value.failover != null ? [1] : []
|
|
content {
|
|
type = each.value.failover
|
|
}
|
|
}
|
|
}
|
|
|
|
################################################################################
|
|
# Simplified Alias Records
|
|
################################################################################
|
|
|
|
resource "aws_route53_record" "alias" {
|
|
for_each = var.alias_records
|
|
|
|
zone_id = aws_route53_zone.main.zone_id
|
|
name = each.key == "@" ? var.domain_name : "${each.key}.${var.domain_name}"
|
|
type = each.value.type
|
|
|
|
alias {
|
|
name = each.value.target_dns_name
|
|
zone_id = each.value.target_zone_id
|
|
evaluate_target_health = each.value.evaluate_target_health
|
|
}
|
|
}
|
|
|
|
################################################################################
|
|
# MX Records
|
|
################################################################################
|
|
|
|
resource "aws_route53_record" "mx" {
|
|
count = length(var.mx_records) > 0 ? 1 : 0
|
|
|
|
zone_id = aws_route53_zone.main.zone_id
|
|
name = var.domain_name
|
|
type = "MX"
|
|
ttl = 300
|
|
|
|
records = [for mx in var.mx_records : "${mx.priority} ${mx.server}"]
|
|
}
|
|
|
|
################################################################################
|
|
# TXT Records
|
|
################################################################################
|
|
|
|
resource "aws_route53_record" "txt" {
|
|
for_each = var.txt_records
|
|
|
|
zone_id = aws_route53_zone.main.zone_id
|
|
name = each.key == "@" ? var.domain_name : "${each.key}.${var.domain_name}"
|
|
type = "TXT"
|
|
ttl = 300
|
|
records = [each.value]
|
|
}
|
|
|
|
################################################################################
|
|
# DNSSEC
|
|
################################################################################
|
|
|
|
resource "aws_route53_key_signing_key" "main" {
|
|
count = var.enable_dnssec && !var.private_zone ? 1 : 0
|
|
|
|
hosted_zone_id = aws_route53_zone.main.id
|
|
key_management_service_arn = aws_kms_key.dnssec[0].arn
|
|
name = "${replace(var.domain_name, ".", "-")}-ksk"
|
|
}
|
|
|
|
resource "aws_kms_key" "dnssec" {
|
|
count = var.enable_dnssec && !var.private_zone ? 1 : 0
|
|
|
|
customer_master_key_spec = "ECC_NIST_P256"
|
|
deletion_window_in_days = 7
|
|
key_usage = "SIGN_VERIFY"
|
|
|
|
policy = jsonencode({
|
|
Version = "2012-10-17"
|
|
Statement = [
|
|
{
|
|
Sid = "Enable IAM User Permissions"
|
|
Effect = "Allow"
|
|
Principal = {
|
|
AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"
|
|
}
|
|
Action = "kms:*"
|
|
Resource = "*"
|
|
},
|
|
{
|
|
Sid = "Allow Route 53 DNSSEC Service"
|
|
Effect = "Allow"
|
|
Principal = {
|
|
Service = "dnssec-route53.amazonaws.com"
|
|
}
|
|
Action = [
|
|
"kms:DescribeKey",
|
|
"kms:GetPublicKey",
|
|
"kms:Sign"
|
|
]
|
|
Resource = "*"
|
|
Condition = {
|
|
StringEquals = {
|
|
"aws:SourceAccount" = data.aws_caller_identity.current.account_id
|
|
}
|
|
ArnLike = {
|
|
"aws:SourceArn" = "arn:aws:route53:::hostedzone/*"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
Sid = "Allow Route 53 DNSSEC to CreateGrant"
|
|
Effect = "Allow"
|
|
Principal = {
|
|
Service = "dnssec-route53.amazonaws.com"
|
|
}
|
|
Action = "kms:CreateGrant"
|
|
Resource = "*"
|
|
Condition = {
|
|
Bool = {
|
|
"kms:GrantIsForAWSResource" = "true"
|
|
}
|
|
}
|
|
}
|
|
]
|
|
})
|
|
|
|
tags = merge(var.tags, { Name = "${var.domain_name}-dnssec" })
|
|
}
|
|
|
|
data "aws_caller_identity" "current" {}
|
|
|
|
resource "aws_route53_hosted_zone_dnssec" "main" {
|
|
count = var.enable_dnssec && !var.private_zone ? 1 : 0
|
|
|
|
hosted_zone_id = aws_route53_zone.main.id
|
|
|
|
depends_on = [aws_route53_key_signing_key.main]
|
|
}
|
|
|
|
################################################################################
|
|
# Query Logging
|
|
################################################################################
|
|
|
|
resource "aws_cloudwatch_log_group" "query_log" {
|
|
count = var.enable_query_logging && !var.private_zone ? 1 : 0
|
|
|
|
name = "/aws/route53/${var.domain_name}"
|
|
retention_in_days = var.query_log_retention_days
|
|
|
|
tags = merge(var.tags, { Name = var.domain_name })
|
|
}
|
|
|
|
resource "aws_cloudwatch_log_resource_policy" "query_log" {
|
|
count = var.enable_query_logging && !var.private_zone ? 1 : 0
|
|
|
|
policy_name = "route53-query-logging-${replace(var.domain_name, ".", "-")}"
|
|
|
|
policy_document = jsonencode({
|
|
Version = "2012-10-17"
|
|
Statement = [{
|
|
Effect = "Allow"
|
|
Principal = {
|
|
Service = "route53.amazonaws.com"
|
|
}
|
|
Action = [
|
|
"logs:CreateLogStream",
|
|
"logs:PutLogEvents"
|
|
]
|
|
Resource = "${aws_cloudwatch_log_group.query_log[0].arn}:*"
|
|
}]
|
|
})
|
|
}
|
|
|
|
resource "aws_route53_query_log" "main" {
|
|
count = var.enable_query_logging && !var.private_zone ? 1 : 0
|
|
|
|
cloudwatch_log_group_arn = aws_cloudwatch_log_group.query_log[0].arn
|
|
zone_id = aws_route53_zone.main.zone_id
|
|
|
|
depends_on = [aws_cloudwatch_log_resource_policy.query_log]
|
|
}
|
|
|
|
################################################################################
|
|
# Outputs
|
|
################################################################################
|
|
|
|
output "zone_id" {
|
|
value = aws_route53_zone.main.zone_id
|
|
description = "Hosted zone ID"
|
|
}
|
|
|
|
output "zone_arn" {
|
|
value = aws_route53_zone.main.arn
|
|
description = "Hosted zone ARN"
|
|
}
|
|
|
|
output "name_servers" {
|
|
value = aws_route53_zone.main.name_servers
|
|
description = "Name servers for the zone (update at registrar)"
|
|
}
|
|
|
|
output "domain_name" {
|
|
value = var.domain_name
|
|
description = "Domain name"
|
|
}
|
|
|
|
output "dnssec_ds_record" {
|
|
value = var.enable_dnssec && !var.private_zone ? aws_route53_key_signing_key.main[0].ds_record : null
|
|
description = "DS record for DNSSEC (add to parent zone/registrar)"
|
|
}
|