Skip to content

Commit 4ccd567

Browse files
committed
feat: Add support for ignoring vpc changes to support cross account
zone associations
1 parent 429d96d commit 4ccd567

File tree

11 files changed

+453
-7
lines changed

11 files changed

+453
-7
lines changed

README.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,49 @@ module "zone" {
188188
}
189189
```
190190

191+
### Cross Account Zone Association
192+
193+
> [!NOTE]
194+
> Association (`aws_route53_zone_association`) must be performed by the account that owns the VPC.
195+
>
196+
> Association authorization (`aws_route53_vpc_association_authorization`) must be performed by the account that owns the zone.
197+
>
198+
> Hence why the `aws_route53_zone_association` resource is *outside* the scope of the module, but the authorization (`aws_route53_vpc_association_authorization`) is *inside* the scope of the module.
199+
200+
```hcl
201+
module "zone" {
202+
source = "terraform-aws-modules/route53/aws"
203+
204+
name = "terraform-aws-modules-example.com"
205+
comment = "Private zone for terraform-aws-modules example"
206+
207+
# Ignore VPC after creation to avoid disruptive diff with associations
208+
ignore_vpc = true
209+
vpc = {
210+
default = {
211+
vpc_id = "vpc-1234556abcdef"
212+
vpc_region = "eu-west-1"
213+
}
214+
}
215+
216+
vpc_association_authorizations = {
217+
external = {
218+
vpc_id = "vpc-987564fedcba"
219+
vpc_region = "eu-west-1"
220+
}
221+
external_region = {
222+
vpc_id = "vpc-1a2b3c4d56e7f8"
223+
vpc_region = "us-east-1"
224+
}
225+
}
226+
227+
tags = {
228+
Environment = "example"
229+
Project = "terraform-aws-route53"
230+
}
231+
}
232+
```
233+
191234
## Sub-Modules
192235

193236
The following independent sub-modules are available:
@@ -201,6 +244,7 @@ See the respective module directories for examples and documentation.
201244
## Examples
202245

203246
- [Complete](https://github.com/terraform-aws-modules/terraform-aws-route53/tree/master/examples/complete)
247+
- [Cross Account](https://github.com/terraform-aws-modules/terraform-aws-route53/tree/master/examples/cross-account)
204248

205249
<!-- BEGIN_TF_DOCS -->
206250
## Requirements
@@ -230,6 +274,7 @@ See the respective module directories for examples and documentation.
230274
| [aws_route53_key_signing_key.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_key_signing_key) | resource |
231275
| [aws_route53_record.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record) | resource |
232276
| [aws_route53_vpc_association_authorization.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_vpc_association_authorization) | resource |
277+
| [aws_route53_zone.ignore_vpc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_zone) | resource |
233278
| [aws_route53_zone.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_zone) | resource |
234279
| [aws_route53_zone.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/route53_zone) | data source |
235280

@@ -248,6 +293,7 @@ See the respective module directories for examples and documentation.
248293
| <a name="input_dnssec_kms_key_tags"></a> [dnssec\_kms\_key\_tags](#input\_dnssec\_kms\_key\_tags) | Additional tags to apply to the KMS key created for DNSSEC signing | `map(string)` | `{}` | no |
249294
| <a name="input_enable_dnssec"></a> [enable\_dnssec](#input\_enable\_dnssec) | Whether to enable DNSSEC for the Route53 zone | `bool` | `false` | no |
250295
| <a name="input_force_destroy"></a> [force\_destroy](#input\_force\_destroy) | Whether to destroy all records (possibly managed outside of Terraform) in the zone when destroying the zone | `bool` | `null` | no |
296+
| <a name="input_ignore_vpc"></a> [ignore\_vpc](#input\_ignore\_vpc) | Determines whether to ignore VPC association changes after creation to avoid disruptive diffs when using `aws_route53_zone_association` resource(s). Changing is a destructive action; users should be prepared to use Terraform state move commands/blocks when changing this value | `bool` | `false` | no |
251297
| <a name="input_name"></a> [name](#input\_name) | This is the name of the hosted zone | `string` | `""` | no |
252298
| <a name="input_private_zone"></a> [private\_zone](#input\_private\_zone) | Whether the hosted zone is private. Only applicable when `create_zone = false` | `bool` | `false` | no |
253299
| <a name="input_records"></a> [records](#input\_records) | A map of Route53 records to create in the zone. The key can be used as the subdomain name, or `name` can be used to specify the full name | <pre>map(object({<br/> alias = optional(object({<br/> evaluate_target_health = optional(bool, false)<br/> name = string<br/> zone_id = string<br/> }))<br/> allow_overwrite = optional(bool)<br/> cidr_routing_policy = optional(object({<br/> collection_id = string<br/> location_name = string<br/> }))<br/> failover_routing_policy = optional(object({<br/> type = string<br/> }))<br/> geolocation_routing_policy = optional(object({<br/> continent = optional(string)<br/> country = optional(string)<br/> subdivision = optional(string)<br/> }))<br/> geoproximity_routing_policy = optional(object({<br/> aws_region = optional(string)<br/> bias = optional(number)<br/> coordinates = optional(list(object({<br/> latitude = number<br/> longitude = number<br/> })))<br/> local_zone_group = optional(string)<br/> }))<br/> health_check_id = optional(string)<br/> latency_routing_policy = optional(object({<br/> region = string<br/> }))<br/> multivalue_answer_routing_policy = optional(bool)<br/> name = optional(string)<br/> full_name = optional(string)<br/> records = optional(list(string))<br/> set_identifier = optional(string)<br/> ttl = optional(number)<br/> type = string<br/> weighted_routing_policy = optional(object({<br/> weight = number<br/> }))<br/> timeouts = optional(object({<br/> create = optional(string)<br/> update = optional(string)<br/> delete = optional(string)<br/> }))<br/> }))</pre> | `{}` | no |

examples/complete/README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
Configuration in this directory creates:
44

5-
65
## Usage
76

87
To run this example you need to execute:

examples/cross-account/README.md

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Cross Account Route53 Zone Association
2+
3+
Configuration in this directory creates:
4+
5+
- A Route53 Private Hosted Zone w/ associated VPC in one AWS account
6+
- A VPC in another AWS account, associated to the Private Hosted Zone in the first account
7+
- A VPC in another AWS account and region, associated to the Private Hosted Zone in the first account
8+
9+
> [!NOTE]
10+
> Association (`aws_route53_zone_association`) must be performed by the account that owns the VPC.
11+
>
12+
> Association authorization (`aws_route53_vpc_association_authorization`) must be performed by the account that owns the zone.
13+
>
14+
> Hence why the `aws_route53_zone_association` resource is *outside* the scope of the module, but the authorization (`aws_route53_vpc_association_authorization`) is *inside* the scope of the module.
15+
16+
## Usage
17+
18+
To run this example you need to execute:
19+
20+
```bash
21+
$ terraform init
22+
$ terraform plan
23+
$ terraform apply
24+
```
25+
26+
Note that this example may create resources which will incur monetary charges on your AWS bill. Run `terraform destroy` when you no longer need these resources.
27+
28+
<!-- BEGIN_TF_DOCS -->
29+
## Requirements
30+
31+
| Name | Version |
32+
|------|---------|
33+
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.5.7 |
34+
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 6.3 |
35+
36+
## Providers
37+
38+
| Name | Version |
39+
|------|---------|
40+
| <a name="provider_aws"></a> [aws](#provider\_aws) | >= 6.3 |
41+
| <a name="provider_aws.external"></a> [aws.external](#provider\_aws.external) | >= 6.3 |
42+
| <a name="provider_aws.external_region"></a> [aws.external\_region](#provider\_aws.external\_region) | >= 6.3 |
43+
44+
## Modules
45+
46+
| Name | Source | Version |
47+
|------|--------|---------|
48+
| <a name="module_vpc"></a> [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 6.0 |
49+
| <a name="module_vpc_external"></a> [vpc\_external](#module\_vpc\_external) | terraform-aws-modules/vpc/aws | ~> 6.0 |
50+
| <a name="module_vpc_external_region"></a> [vpc\_external\_region](#module\_vpc\_external\_region) | terraform-aws-modules/vpc/aws | ~> 6.0 |
51+
| <a name="module_zone"></a> [zone](#module\_zone) | ../.. | n/a |
52+
53+
## Resources
54+
55+
| Name | Type |
56+
|------|------|
57+
| [aws_route53_zone_association.external](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_zone_association) | resource |
58+
| [aws_route53_zone_association.external_region](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_zone_association) | resource |
59+
| [aws_availability_zones.available](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source |
60+
| [aws_availability_zones.external_region](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source |
61+
62+
## Inputs
63+
64+
| Name | Description | Type | Default | Required |
65+
|------|-------------|------|---------|:--------:|
66+
| <a name="input_external_account_role_arn"></a> [external\_account\_role\_arn](#input\_external\_account\_role\_arn) | The ARN of the role to assume in the external account containing the VPC to be associated to the Route53 private zone | `string` | `"YOU MUST PROVIDE THIS VALUE"` | no |
67+
68+
## Outputs
69+
70+
| Name | Description |
71+
|------|-------------|
72+
| <a name="output_zone_arn"></a> [zone\_arn](#output\_zone\_arn) | Zone ARN of Route53 zone |
73+
| <a name="output_zone_dnssec_kms_key_arn"></a> [zone\_dnssec\_kms\_key\_arn](#output\_zone\_dnssec\_kms\_key\_arn) | The Amazon Resource Name (ARN) of the key |
74+
| <a name="output_zone_dnssec_kms_key_id"></a> [zone\_dnssec\_kms\_key\_id](#output\_zone\_dnssec\_kms\_key\_id) | The globally unique identifier for the key |
75+
| <a name="output_zone_dnssec_kms_key_policy"></a> [zone\_dnssec\_kms\_key\_policy](#output\_zone\_dnssec\_kms\_key\_policy) | The IAM resource policy set on the key |
76+
| <a name="output_zone_dnssec_kms_key_region"></a> [zone\_dnssec\_kms\_key\_region](#output\_zone\_dnssec\_kms\_key\_region) | The region for the key |
77+
| <a name="output_zone_dnssec_signing_key_digest_value"></a> [zone\_dnssec\_signing\_key\_digest\_value](#output\_zone\_dnssec\_signing\_key\_digest\_value) | A cryptographic digest of a DNSKEY resource record (RR). DNSKEY records are used to publish the public key that resolvers can use to verify DNSSEC signatures that are used to secure certain kinds of information provided by the DNS system |
78+
| <a name="output_zone_dnssec_signing_key_dnskey_record"></a> [zone\_dnssec\_signing\_key\_dnskey\_record](#output\_zone\_dnssec\_signing\_key\_dnskey\_record) | A string that represents a DNSKEY record |
79+
| <a name="output_zone_dnssec_signing_key_ds_record"></a> [zone\_dnssec\_signing\_key\_ds\_record](#output\_zone\_dnssec\_signing\_key\_ds\_record) | A string that represents a delegation signer (DS) record |
80+
| <a name="output_zone_dnssec_signing_key_id"></a> [zone\_dnssec\_signing\_key\_id](#output\_zone\_dnssec\_signing\_key\_id) | Route 53 Hosted Zone identifier and KMS Key identifier, separated by a comma (`,`) |
81+
| <a name="output_zone_dnssec_signing_key_public_key"></a> [zone\_dnssec\_signing\_key\_public\_key](#output\_zone\_dnssec\_signing\_key\_public\_key) | The public key, represented as a Base64 encoding, as required by RFC-4034 Page 5 |
82+
| <a name="output_zone_dnssec_signing_key_tag"></a> [zone\_dnssec\_signing\_key\_tag](#output\_zone\_dnssec\_signing\_key\_tag) | An integer used to identify the DNSSEC record for the domain name. The process used to calculate the value is described in RFC-4034 Appendix B |
83+
| <a name="output_zone_id"></a> [zone\_id](#output\_zone\_id) | Zone ID of Route53 zone |
84+
| <a name="output_zone_name"></a> [zone\_name](#output\_zone\_name) | Name of Route53 zone |
85+
| <a name="output_zone_name_servers"></a> [zone\_name\_servers](#output\_zone\_name\_servers) | Name servers of Route53 zone |
86+
| <a name="output_zone_primary_name_server"></a> [zone\_primary\_name\_server](#output\_zone\_primary\_name\_server) | The Route 53 name server that created the SOA record. |
87+
| <a name="output_zone_records"></a> [zone\_records](#output\_zone\_records) | Records created in the Route53 zone |
88+
<!-- END_TF_DOCS -->
89+
90+
## License
91+
92+
Apache-2.0 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-ecs/blob/master/LICENSE).

examples/cross-account/main.tf

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
provider "aws" {
2+
region = local.region
3+
}
4+
5+
provider "aws" {
6+
alias = "external"
7+
region = local.region
8+
9+
assume_role {
10+
role_arn = var.external_account_role_arn
11+
session_name = "terraform-aws-route53-example"
12+
}
13+
}
14+
15+
provider "aws" {
16+
alias = "external_region"
17+
region = local.external_region
18+
19+
assume_role {
20+
role_arn = var.external_account_role_arn
21+
session_name = "terraform-aws-route53-example"
22+
}
23+
}
24+
25+
data "aws_availability_zones" "available" {}
26+
27+
locals {
28+
region = "eu-west-1"
29+
external_region = "us-east-1"
30+
31+
name = "ex-${basename(path.cwd)}"
32+
33+
vpc_cidr = "10.0.0.0/16"
34+
azs = slice(data.aws_availability_zones.available.names, 0, 3)
35+
36+
tags = {
37+
Name = local.name
38+
Example = local.name
39+
Repository = "https://github.com/terraform-aws-modules/terraform-aws-route53"
40+
}
41+
}
42+
43+
################################################################################
44+
# Zone Account
45+
################################################################################
46+
47+
module "zone" {
48+
source = "../.."
49+
50+
name = "terraform-aws-modules-example.com"
51+
comment = "Private zone for terraform-aws-modules example"
52+
53+
# Ignore VPC after creation to avoid disruptive diff with associations
54+
ignore_vpc = true
55+
vpc = {
56+
default = {
57+
vpc_id = module.vpc.vpc_id
58+
vpc_region = local.region
59+
}
60+
}
61+
62+
vpc_association_authorizations = {
63+
external = {
64+
vpc_id = module.vpc_external.vpc_id
65+
vpc_region = local.region
66+
}
67+
external_region = {
68+
vpc_id = module.vpc_external_region.vpc_id
69+
vpc_region = local.external_region
70+
}
71+
}
72+
73+
tags = local.tags
74+
}
75+
76+
module "vpc" {
77+
source = "terraform-aws-modules/vpc/aws"
78+
version = "~> 6.0"
79+
80+
name = local.name
81+
cidr = local.vpc_cidr
82+
83+
azs = local.azs
84+
private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 4, k)]
85+
86+
enable_nat_gateway = false
87+
88+
tags = local.tags
89+
}
90+
91+
################################################################################
92+
# External Account
93+
################################################################################
94+
95+
module "vpc_external" {
96+
source = "terraform-aws-modules/vpc/aws"
97+
version = "~> 6.0"
98+
99+
providers = {
100+
aws = aws.external
101+
}
102+
103+
name = local.name
104+
cidr = local.vpc_cidr
105+
106+
azs = local.azs
107+
private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 4, k)]
108+
109+
enable_nat_gateway = false
110+
111+
tags = local.tags
112+
}
113+
114+
# Association (`aws_route53_zone_association`) must be performed by the account that owns the VPC.
115+
# Association authorization (`aws_route53_vpc_association_authorization`) must be performed by the account that owns the zone.
116+
# Hence why the `aws_route53_zone_association` resource is *outside* the scope of the module, but the authorization
117+
# (`aws_route53_vpc_association_authorization`) is *inside* the scope of the module.
118+
resource "aws_route53_zone_association" "external" {
119+
provider = aws.external
120+
121+
zone_id = module.zone.id
122+
vpc_id = module.vpc_external.vpc_id
123+
vpc_region = local.region
124+
}
125+
126+
################################################################################
127+
# External Account - different region
128+
################################################################################
129+
130+
data "aws_availability_zones" "external_region" {
131+
provider = aws.external_region
132+
}
133+
134+
locals {
135+
ext_reg_azs = slice(data.aws_availability_zones.external_region.names, 0, 3)
136+
}
137+
138+
module "vpc_external_region" {
139+
source = "terraform-aws-modules/vpc/aws"
140+
version = "~> 6.0"
141+
142+
providers = {
143+
aws = aws.external_region
144+
}
145+
146+
name = local.name
147+
cidr = local.vpc_cidr
148+
149+
azs = local.ext_reg_azs
150+
private_subnets = [for k, v in local.ext_reg_azs : cidrsubnet(local.vpc_cidr, 4, k)]
151+
152+
enable_nat_gateway = false
153+
154+
tags = local.tags
155+
}
156+
157+
# Association (`aws_route53_zone_association`) must be performed by the account that owns the VPC.
158+
# Association authorization (`aws_route53_vpc_association_authorization`) must be performed by the account that owns the zone.
159+
# Hence why the `aws_route53_zone_association` resource is *outside* the scope of the module, but the authorization
160+
# (`aws_route53_vpc_association_authorization`) is *inside* the scope of the module.
161+
resource "aws_route53_zone_association" "external_region" {
162+
provider = aws.external_region
163+
164+
zone_id = module.zone.id
165+
vpc_id = module.vpc_external_region.vpc_id
166+
vpc_region = local.external_region
167+
}

0 commit comments

Comments
 (0)