From 36151319b2eb0ea933d043c2eb1c57a403e43095 Mon Sep 17 00:00:00 2001 From: Javier Ramos Date: Tue, 3 Sep 2024 22:32:01 +0200 Subject: [PATCH] feat: Add zone-cross-account-vpc-association submodule (#109) Co-authored-by: Anton Babenko --- .pre-commit-config.yaml | 2 +- README.md | 1 + examples/complete/README.md | 9 +- examples/complete/main.tf | 85 +++++++++++++++++-- modules/delegation-sets/README.md | 4 +- modules/delegation-sets/main.tf | 2 +- modules/records/README.md | 4 +- modules/resolver-endpoints/README.md | 4 +- modules/resolver-endpoints/main.tf | 4 +- modules/resolver-rule-associations/README.md | 4 +- .../README.md | 78 +++++++++++++++++ .../main.tf | 19 +++++ .../outputs.tf | 14 +++ .../variables.tf | 15 ++++ .../versions.tf | 19 +++++ modules/zones/README.md | 4 +- 16 files changed, 246 insertions(+), 22 deletions(-) create mode 100644 modules/zone-cross-account-vpc-association/README.md create mode 100644 modules/zone-cross-account-vpc-association/main.tf create mode 100644 modules/zone-cross-account-vpc-association/outputs.tf create mode 100644 modules/zone-cross-account-vpc-association/variables.tf create mode 100644 modules/zone-cross-account-vpc-association/versions.tf diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9edd7d3..2bfe8ce 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/antonbabenko/pre-commit-terraform - rev: v1.91.0 + rev: v1.94.1 hooks: - id: terraform_fmt - id: terraform_docs diff --git a/README.md b/README.md index afa2c24..3eaabd4 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ There are independent submodules: - [delegation-sets](https://github.com/terraform-aws-modules/terraform-aws-route53/tree/master/modules/delegation-sets) - to manage Route53 delegation sets - [resolver-endpoints](https://github.com/terraform-aws-modules/terraform-aws-route53/tree/master/modules/resolver-endpoints) - to manage Route53 resolver endpoints - [resolver-rule-associations](https://github.com/terraform-aws-modules/terraform-aws-route53/tree/master/modules/resolver-rule-associations) - to manage Route53 resolver rule associations +- [zone-cross-account-vpc-association](https://github.com/terraform-aws-modules/terraform-aws-route53/tree/master/modules/zone-cross-account-vpc-association) - to associate Route53 zones with VPCs from different AWS accounts ## Usage diff --git a/examples/complete/README.md b/examples/complete/README.md index dbf6259..75edba1 100644 --- a/examples/complete/README.md +++ b/examples/complete/README.md @@ -16,7 +16,7 @@ $ terraform apply Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. - + ## Requirements | Name | Version | @@ -29,6 +29,7 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Version | |------|---------| | [aws](#provider\_aws) | >= 5.37 | +| [aws.second\_account](#provider\_aws.second\_account) | >= 5.37 | ## Modules @@ -38,6 +39,7 @@ Note that this example may create resources which cost money. Run `terraform des | [delegation\_sets](#module\_delegation\_sets) | ../../modules/delegation-sets | n/a | | [disabled\_records](#module\_disabled\_records) | ../../modules/records | n/a | | [disabled\_resolver\_endpoints](#module\_disabled\_resolver\_endpoints) | ../../modules/resolver-endpoints | n/a | +| [disabled\_zone\_cross\_account\_vpc\_association](#module\_disabled\_zone\_cross\_account\_vpc\_association) | ../../modules/zone-cross-account-vpc-association | n/a | | [inbound\_resolver\_endpoints](#module\_inbound\_resolver\_endpoints) | ../../modules/resolver-endpoints | n/a | | [outbound\_resolver\_endpoints](#module\_outbound\_resolver\_endpoints) | ../../modules/resolver-endpoints | n/a | | [records](#module\_records) | ../../modules/records | n/a | @@ -47,6 +49,8 @@ Note that this example may create resources which cost money. Run `terraform des | [terragrunt](#module\_terragrunt) | ../../modules/records | n/a | | [vpc1](#module\_vpc1) | terraform-aws-modules/vpc/aws | ~> 5.0 | | [vpc2](#module\_vpc2) | terraform-aws-modules/vpc/aws | ~> 5.0 | +| [vpc\_otheraccount](#module\_vpc\_otheraccount) | terraform-aws-modules/vpc/aws | ~> 5.0 | +| [zone\_cross\_account\_vpc\_association](#module\_zone\_cross\_account\_vpc\_association) | ../../modules/zone-cross-account-vpc-association | n/a | | [zones](#module\_zones) | ../../modules/zones | n/a | ## Resources @@ -56,6 +60,7 @@ Note that this example may create resources which cost money. Run `terraform des | [aws_route53_health_check.failover](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_health_check) | resource | | [aws_route53_resolver_rule.sys](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_resolver_rule) | resource | | [aws_availability_zones.available](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source | +| [aws_region.second_account_current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source | ## Inputs @@ -77,4 +82,4 @@ No inputs. | [route53\_zone\_name\_servers](#output\_route53\_zone\_name\_servers) | Name servers of Route53 zone | | [route53\_zone\_zone\_arn](#output\_route53\_zone\_zone\_arn) | Zone ARN of Route53 zone | | [route53\_zone\_zone\_id](#output\_route53\_zone\_zone\_id) | Zone ID of Route53 zone | - + diff --git a/examples/complete/main.tf b/examples/complete/main.tf index 4fbdf72..1bc093e 100644 --- a/examples/complete/main.tf +++ b/examples/complete/main.tf @@ -2,6 +2,11 @@ provider "aws" { region = "eu-west-1" } +provider "aws" { + region = "eu-west-1" + alias = "second_account" +} + locals { zone_name = sort(keys(module.zones.route53_zone_zone_id))[0] # zone_id = module.zones.route53_zone_zone_id["terraform-aws-modules-example.com"] @@ -10,6 +15,10 @@ locals { azs = slice(data.aws_availability_zones.available.names, 0, 3) } +data "aws_region" "second_account_current" { + provider = aws.second_account +} + module "zones" { source = "../../modules/zones" @@ -22,8 +31,8 @@ module "zones" { } "app.terraform-aws-modules-example.com" = { - comment = "app.terraform-aws-modules-example.com" - delegation_set_id = module.delegation_sets.route53_delegation_set_id.main + comment = "app.terraform-aws-modules-example.com" + # delegation_set_id = module.delegation_sets.route53_delegation_set_id.main tags = { Name = "app.terraform-aws-modules-example.com" } @@ -45,6 +54,21 @@ module "zones" { Name = "private-vpc.terraform-aws-modules-example.com" } } + + "private-vpc.terraform-aws-modules-example2.com" = { + # in case than private and public zones with the same domain name + domain_name = "terraform-aws-modules-example2.com" + comment = "private-vpc.terraform-aws-modules-example2.com" + vpc = [ + { + vpc_id = module.vpc1.vpc_id + }, + ] + tags = { + Name = "private-vpc.terraform-aws-modules-example2.com" + } + } + } tags = { @@ -60,9 +84,10 @@ module "records" { records = [ { - name = "" - type = "SOA" - ttl = 900 + name = "" + type = "SOA" + ttl = 900 + allow_overwrite = true # SOA record already exist in the zone records = [ "${module.zones.primary_name_server[local.zone_name]}. awsdns-hostmaster.amazon.com. 1 7200 900 1209600 60", ] @@ -273,9 +298,34 @@ module "delegation_sets" { delegation_sets = { main = {} + another = { + reference_name = "MySet" + } } } + +module "zone_cross_account_vpc_association" { + source = "../../modules/zone-cross-account-vpc-association" + providers = { + aws.r53_owner = aws + aws.vpc_owner = aws.second_account + } + + zone_vpc_associations = { + example = { + zone_id = module.zones.route53_zone_zone_id["private-vpc.terraform-aws-modules-example.com"] + vpc_id = module.vpc_otheraccount.vpc_id + }, + example2 = { + zone_id = module.zones.route53_zone_zone_id["private-vpc.terraform-aws-modules-example2.com"] + vpc_id = module.vpc_otheraccount.vpc_id + vpc_region = data.aws_region.second_account_current.name + }, + } +} + + module "resolver_rule_associations" { source = "../../modules/resolver-rule-associations" @@ -357,6 +407,17 @@ module "disabled_records" { create = false } +module "disabled_zone_cross_account_vpc_association" { + source = "../../modules/zone-cross-account-vpc-association" + + providers = { + aws.r53_owner = aws + aws.vpc_owner = aws.second_account + } + + create = false +} + ######### # Extras - should be created in advance ######### @@ -385,7 +446,7 @@ module "cloudfront" { source = "terraform-aws-modules/cloudfront/aws" version = "~> 3.0" - enabled = true + enabled = false wait_for_deployment = false origin = { @@ -425,6 +486,18 @@ module "vpc2" { cidr = "10.1.0.0/16" } +module "vpc_otheraccount" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 5.0" + + providers = { + aws = aws.second_account + } + + name = "my-second-account-vpc-for-private-route53-zone" + cidr = "172.16.0.0/16" +} + resource "aws_route53_resolver_rule" "sys" { domain_name = "sys-example.com" rule_type = "SYSTEM" diff --git a/modules/delegation-sets/README.md b/modules/delegation-sets/README.md index 8aeae3b..468381a 100644 --- a/modules/delegation-sets/README.md +++ b/modules/delegation-sets/README.md @@ -42,7 +42,7 @@ module "zones" { } ``` - + ## Requirements | Name | Version | @@ -80,4 +80,4 @@ No modules. | [route53\_delegation\_set\_id](#output\_route53\_delegation\_set\_id) | ID of Route53 delegation set | | [route53\_delegation\_set\_name\_servers](#output\_route53\_delegation\_set\_name\_servers) | Name servers in the Route53 delegation set | | [route53\_delegation\_set\_reference\_name](#output\_route53\_delegation\_set\_reference\_name) | Reference name used when the Route53 delegation set has been created | - + diff --git a/modules/delegation-sets/main.tf b/modules/delegation-sets/main.tf index 49762dc..d74f70b 100644 --- a/modules/delegation-sets/main.tf +++ b/modules/delegation-sets/main.tf @@ -1,5 +1,5 @@ resource "aws_route53_delegation_set" "this" { - for_each = var.create ? var.delegation_sets : tomap({}) + for_each = { for k, v in var.delegation_sets : k => v if var.create } reference_name = lookup(each.value, "reference_name", null) } diff --git a/modules/records/README.md b/modules/records/README.md index 2e1a6a2..ca2095b 100644 --- a/modules/records/README.md +++ b/modules/records/README.md @@ -26,7 +26,7 @@ records_jsonencoded = jsonencode([ ]) ``` - + ## Requirements | Name | Version | @@ -68,4 +68,4 @@ No modules. |------|-------------| | [route53\_record\_fqdn](#output\_route53\_record\_fqdn) | FQDN built using the zone domain and name | | [route53\_record\_name](#output\_route53\_record\_name) | The name of the record | - + diff --git a/modules/resolver-endpoints/README.md b/modules/resolver-endpoints/README.md index ece4f0e..cebac18 100644 --- a/modules/resolver-endpoints/README.md +++ b/modules/resolver-endpoints/README.md @@ -2,7 +2,7 @@ This module creates Route53 Resolver Endpoints. - + ## Requirements | Name | Version | @@ -58,4 +58,4 @@ No modules. | [route53\_resolver\_endpoint\_id](#output\_route53\_resolver\_endpoint\_id) | The ID of the Resolver Endpoint | | [route53\_resolver\_endpoint\_ip\_addresses](#output\_route53\_resolver\_endpoint\_ip\_addresses) | Resolver Endpoint IP Addresses | | [route53\_resolver\_endpoint\_security\_group\_ids](#output\_route53\_resolver\_endpoint\_security\_group\_ids) | Security Group IDs mapped to Resolver Endpoint | - + diff --git a/modules/resolver-endpoints/main.tf b/modules/resolver-endpoints/main.tf index f801aa5..e1c1258 100644 --- a/modules/resolver-endpoints/main.tf +++ b/modules/resolver-endpoints/main.tf @@ -1,6 +1,6 @@ locals { security_group_ids = var.create && var.create_security_group ? [aws_security_group.this[0].id] : var.security_group_ids - subnet_ids = var.create && length(var.subnet_ids) > 0 ? [for subnet in var.subnet_ids : { subnet_id = subnet }] : var.subnet_ids + subnet_ids = [for subnet in var.subnet_ids : { subnet_id = subnet } if var.create] } resource "aws_route53_resolver_endpoint" "this" { @@ -17,7 +17,7 @@ resource "aws_route53_resolver_endpoint" "this" { content { ip = lookup(ip_address.value, "ip", null) - subnet_id = each.value.subnet_id + subnet_id = ip_address.value.subnet_id } } diff --git a/modules/resolver-rule-associations/README.md b/modules/resolver-rule-associations/README.md index 0f0ff9b..53109e2 100644 --- a/modules/resolver-rule-associations/README.md +++ b/modules/resolver-rule-associations/README.md @@ -26,7 +26,7 @@ module "resolver_rule_associations" { } ``` - + ## Requirements | Name | Version | @@ -65,4 +65,4 @@ No modules. | [route53\_resolver\_rule\_association\_id](#output\_route53\_resolver\_rule\_association\_id) | ID of Route53 Resolver rule associations | | [route53\_resolver\_rule\_association\_name](#output\_route53\_resolver\_rule\_association\_name) | Name of Route53 Resolver rule associations | | [route53\_resolver\_rule\_association\_resolver\_rule\_id](#output\_route53\_resolver\_rule\_association\_resolver\_rule\_id) | ID of Route53 Resolver rule associations resolver rule | - + diff --git a/modules/zone-cross-account-vpc-association/README.md b/modules/zone-cross-account-vpc-association/README.md new file mode 100644 index 0000000..0efc18c --- /dev/null +++ b/modules/zone-cross-account-vpc-association/README.md @@ -0,0 +1,78 @@ +# Route53 Zone cross-account VPC association + +This module creates cross-account Route53 Zone associations. + +It does need two providers to be passed to handle both AWS accounts: +- `aws.r53_owner`: Account owning the Route53 zones to make the cross-account association authorization +- `aws.vpc_owner`: Account owning the VPCs to associate with the Route53 zones + +Many-to-many associations are possible, using the zone_vpc_associations input variable. + +## Usage + +### Create Route53 Zone cross-account VPC association + +```hcl +module "zone_cross_account_vpc_association" { + source = "terraform-aws-modules/route53/aws//modules/zone-cross-account-vpc-association" + + providers = { + aws.r53_owner = aws + aws.vpc_owner = aws.second_account + } + + zone_vpc_associations = { + example = { + zone_id = "Z111111QQQQQQQ" + vpc_id = "vpc-185a3e2f2d6d2c863" + }, + example2 = { + zone_id = "Z222222VVVVVVV" + vpc_id = "vpc-123456789abcd1234" + vpc_region = "us-east-2" + }, + } +} +``` + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.3.2 | +| [aws](#requirement\_aws) | >= 3.56 | + +## Providers + +| Name | Version | +|------|---------| +| [aws.r53\_owner](#provider\_aws.r53\_owner) | >= 3.56 | +| [aws.vpc\_owner](#provider\_aws.vpc\_owner) | >= 3.56 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_route53_vpc_association_authorization.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_vpc_association_authorization) | resource | +| [aws_route53_zone_association.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_zone_association) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [create](#input\_create) | Whether to create Route53 Zone associations | `bool` | `true` | no | +| [zone\_vpc\_associations](#input\_zone\_vpc\_associations) | Map of associations indicating zone\_id and vpc\_id to associate. |
map(object({
zone_id = string
vpc_id = string
vpc_region = optional(string)
}))
| `{}` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [aws\_route53\_vpc\_association\_authorization\_id](#output\_aws\_route53\_vpc\_association\_authorization\_id) | ID of Route53 VPC association authorizations | +| [aws\_route53\_zone\_association\_id](#output\_aws\_route53\_zone\_association\_id) | ID of Route53 VPC association | +| [aws\_route53\_zone\_association\_owning\_account](#output\_aws\_route53\_zone\_association\_owning\_account) | The account ID of the account that created the hosted zone. | + diff --git a/modules/zone-cross-account-vpc-association/main.tf b/modules/zone-cross-account-vpc-association/main.tf new file mode 100644 index 0000000..761b289 --- /dev/null +++ b/modules/zone-cross-account-vpc-association/main.tf @@ -0,0 +1,19 @@ +resource "aws_route53_vpc_association_authorization" "this" { + for_each = { for k, v in var.zone_vpc_associations : k => v if var.create } + + provider = aws.r53_owner + + zone_id = each.value.zone_id + vpc_id = each.value.vpc_id + vpc_region = try(each.value.vpc_region, null) +} + +resource "aws_route53_zone_association" "this" { + for_each = aws_route53_vpc_association_authorization.this + + provider = aws.vpc_owner + + vpc_id = each.value.vpc_id + zone_id = each.value.zone_id + vpc_region = try(each.value.vpc_region, null) +} diff --git a/modules/zone-cross-account-vpc-association/outputs.tf b/modules/zone-cross-account-vpc-association/outputs.tf new file mode 100644 index 0000000..1871993 --- /dev/null +++ b/modules/zone-cross-account-vpc-association/outputs.tf @@ -0,0 +1,14 @@ +output "aws_route53_vpc_association_authorization_id" { + description = "ID of Route53 VPC association authorizations" + value = { for k, v in aws_route53_vpc_association_authorization.this : k => v.id } +} + +output "aws_route53_zone_association_id" { + description = "ID of Route53 VPC association" + value = { for k, v in aws_route53_zone_association.this : k => v.id } +} + +output "aws_route53_zone_association_owning_account" { + description = "The account ID of the account that created the hosted zone." + value = { for k, v in aws_route53_zone_association.this : k => v.owning_account } +} diff --git a/modules/zone-cross-account-vpc-association/variables.tf b/modules/zone-cross-account-vpc-association/variables.tf new file mode 100644 index 0000000..21f8df6 --- /dev/null +++ b/modules/zone-cross-account-vpc-association/variables.tf @@ -0,0 +1,15 @@ +variable "create" { + description = "Whether to create Route53 Zone associations" + type = bool + default = true +} + +variable "zone_vpc_associations" { + description = "Map of associations indicating zone_id and vpc_id to associate." + type = map(object({ + zone_id = string + vpc_id = string + vpc_region = optional(string) + })) + default = {} +} diff --git a/modules/zone-cross-account-vpc-association/versions.tf b/modules/zone-cross-account-vpc-association/versions.tf new file mode 100644 index 0000000..f831127 --- /dev/null +++ b/modules/zone-cross-account-vpc-association/versions.tf @@ -0,0 +1,19 @@ +terraform { + required_version = ">= 1.3.2" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.56" + configuration_aliases = [aws.r53_owner, aws.vpc_owner] + } + } +} + +provider "aws" { + alias = "r53_owner" +} + +provider "aws" { + alias = "vpc_owner" +} diff --git a/modules/zones/README.md b/modules/zones/README.md index d975780..a9a57b6 100644 --- a/modules/zones/README.md +++ b/modules/zones/README.md @@ -2,7 +2,7 @@ This module creates Route53 zones. - + ## Requirements | Name | Version | @@ -44,4 +44,4 @@ No modules. | [route53\_zone\_name\_servers](#output\_route53\_zone\_name\_servers) | Name servers of Route53 zone | | [route53\_zone\_zone\_arn](#output\_route53\_zone\_zone\_arn) | Zone ARN of Route53 zone | | [route53\_zone\_zone\_id](#output\_route53\_zone\_zone\_id) | Zone ID of Route53 zone | - +