main.tf
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# Route53 Resolver (Inbound/Outbound) ==========================================

# Resolver Endpoints -----------------------------------------------------------
# Shared resource type for both directions; caller sets `direction` per entry.
# INBOUND endpoints let external resolvers query into this VPC.
# OUTBOUND endpoints let this VPC forward queries to external DNS servers.

resource "aws_route53_resolver_endpoint" "this" {
  for_each = var.endpoints

  direction              = each.value.direction
  resolver_endpoint_type = each.value.endpoint_type
  name                   = "${var.name_prefix}-${local.region_abbr}-resolver-ep-${each.value.endpoint_name_suffix}"
  security_group_ids     = each.value.security_group_ids

  dynamic "ip_address" {
    for_each = each.value.interfaces
    content {
      subnet_id = ip_address.value.subnet_id
      ip        = ip_address.value.ip
    }
  }

  tags = merge(var.tags, each.value.tags, local.module_tags, {
    Name          = "${var.name_prefix}-${local.region_abbr}-resolver-ep-${each.value.endpoint_name_suffix}"
    resource-type = "r53-resolver-endpoint"
  })

  lifecycle {
    precondition {
      condition     = length("${var.name_prefix}-${local.region_abbr}-resolver-ep-${each.value.endpoint_name_suffix}") <= 64
      error_message = "Assembled resolver endpoint name for '${each.key}' exceeds AWS's 64-character limit."
    }
  }
}

# Resolver Rules ===============================================================
# Only meaningful for OUTBOUND endpoints. Forwards queries for domain_name
# to the specified target IPs via the associated resolver endpoint.

resource "aws_route53_resolver_rule" "this" {
  for_each = var.rules

  domain_name          = each.value.domain_name
  rule_type            = each.value.rule_type
  resolver_endpoint_id = aws_route53_resolver_endpoint.this[each.value.endpoint_key].id
  name                 = "${var.name_prefix}-${local.region_abbr}-resolver-rule-${each.value.rule_name_suffix}"

  dynamic "target_ip" {
    for_each = each.value.target_ips
    content {
      ip   = target_ip.value.ip
      port = target_ip.value.port
    }
  }

  tags = merge(var.tags, each.value.tags, local.module_tags, {
    Name          = "${var.name_prefix}-${local.region_abbr}-resolver-rule-${each.value.rule_name_suffix}"
    resource-type = "r53-resolver-rule"
  })

  lifecycle {
    precondition {
      condition     = var.endpoints[each.value.endpoint_key].direction == "OUTBOUND"
      error_message = "endpoint_key '${each.value.endpoint_key}' for rule '${each.key}' must reference an OUTBOUND endpoint."
    }
    precondition {
      condition     = length("${var.name_prefix}-${local.region_abbr}-resolver-rule-${each.value.rule_name_suffix}") <= 64
      error_message = "Assembled resolver rule name for '${each.key}' exceeds AWS's 64-character limit."
    }
  }
}

# Rule Associations =============================================================
# Associates each rule with one or more VPCs. Flattened since a rule can
# target multiple VPCs and each association is its own resource.

resource "aws_route53_resolver_rule_association" "this" {
  for_each = { for pair in flatten([
    for rule_key, rule in var.rules : [
      for vpc_id in rule.vpc_ids : {
        key     = "${rule_key}-${vpc_id}"
        rule_id = rule_key
        vpc_id  = vpc_id
      }
    ]
  ]) : pair.key => pair }

  resolver_rule_id = aws_route53_resolver_rule.this[each.value.rule_id].id
  vpc_id           = each.value.vpc_id
}