mirror of
https://github.com/ghndrx/terraform-foundation.git
synced 2026-02-10 14:54:56 +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:
57
terraform/modules/route53-zone/README.md
Normal file
57
terraform/modules/route53-zone/README.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# route53-zone
|
||||
|
||||
Route53 Zone Module
|
||||
|
||||
## Usage
|
||||
|
||||
```hcl
|
||||
module "route53_zone" {
|
||||
source = "../modules/route53-zone"
|
||||
|
||||
# Required variables
|
||||
domain_name = ""
|
||||
records = ""
|
||||
alias_records = ""
|
||||
mx_records = ""
|
||||
|
||||
# Optional: see variables.tf for all options
|
||||
}
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
| Name | Version |
|
||||
|------|---------|
|
||||
| terraform | >= 1.5.0 |
|
||||
| aws | >= 5.0 |
|
||||
|
||||
## Inputs
|
||||
|
||||
| Name | Description | Type | Required |
|
||||
|------|-------------|------|----------|
|
||||
| domain_name | Domain name for the hosted zone | `string` | yes |
|
||||
| comment | Comment for the hosted zone | `string` | no |
|
||||
| private_zone | Create a private hosted zone | `bool` | no |
|
||||
| vpc_ids | VPC IDs to associate with private zone | `list(string)` | no |
|
||||
| enable_dnssec | Enable DNSSEC signing | `bool` | no |
|
||||
| enable_query_logging | Enable query logging to CloudWatch | `bool` | no |
|
||||
| query_log_retention_days | Query log retention in days | `number` | no |
|
||||
| records | | `map(object({` | yes |
|
||||
| alias_records | | `map(object({` | yes |
|
||||
| mx_records | | `list(object({` | yes |
|
||||
| txt_records | | `map(string)` | no |
|
||||
| tags | | `map(string)` | no |
|
||||
|
||||
## Outputs
|
||||
|
||||
| Name | Description |
|
||||
|------|-------------|
|
||||
| zone_id | Hosted zone ID |
|
||||
| zone_arn | Hosted zone ARN |
|
||||
| name_servers | Name servers for the zone (update at registrar) |
|
||||
| domain_name | Domain name |
|
||||
| dnssec_ds_record | DS record for DNSSEC (add to parent zone/registrar) |
|
||||
|
||||
## License
|
||||
|
||||
Apache 2.0 - See LICENSE for details.
|
||||
418
terraform/modules/route53-zone/main.tf
Normal file
418
terraform/modules/route53-zone/main.tf
Normal file
@@ -0,0 +1,418 @@
|
||||
################################################################################
|
||||
# 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)"
|
||||
}
|
||||
Reference in New Issue
Block a user