diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a6cd369..e2260fa 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,8 @@ repos: - repo: https://github.com/antonbabenko/pre-commit-terraform - rev: v1.96.1 + rev: v1.100.0 hooks: + - id: terraform_wrapper_module_for_each - id: terraform_fmt - id: terraform_docs args: @@ -23,7 +24,7 @@ repos: - '--args=--only=terraform_workspace_remote' - id: terraform_validate - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 + rev: v6.0.0 hooks: - id: check-merge-conflict - id: end-of-file-fixer diff --git a/README.md b/README.md index 3eaabd4..a07d091 100644 --- a/README.md +++ b/README.md @@ -1,80 +1,283 @@ -# Route53 Terraform module +# AWS Route53 Terraform modules -Terraform module which creates Route53 resources. +Terraform modules which creates Route53 resources. [![SWUbanner](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner2-direct.svg)](https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md) -There are independent submodules: - -- [zones](https://github.com/terraform-aws-modules/terraform-aws-route53/tree/master/modules/zones) - to manage Route53 zones -- [records](https://github.com/terraform-aws-modules/terraform-aws-route53/tree/master/modules/records) - to manage Route53 records -- [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 -### Create Route53 zones and records +### Public Hosted Zone ```hcl -module "zones" { - source = "terraform-aws-modules/route53/aws//modules/zones" - version = "~> 3.0" - - zones = { - "terraform-aws-modules-example.com" = { - comment = "terraform-aws-modules-examples.com (production)" - tags = { - env = "production" +module "zone" { + source = "terraform-aws-modules/route53/aws" + + name = "terraform-aws-modules-example.com" + comment = "Public zone for terraform-aws-modules example" + + records = { + s3 = { + name = "s3-bucket-z1bkctxd74ezpe.terraform-aws-modules-example.com" + type = "A" + alias = { + name = "s3-website-eu-west-1.amazonaws.com" + zone_id = "Z1BKCTXD74EZPE" } } - - "myapp.com" = { - comment = "myapp.com" + mail = { + full_name = "terraform-aws-modules-example.com" + type = "MX" + ttl = 3600 + records = [ + "1 aspmx.l.google.com", + "5 alt1.aspmx.l.google.com", + "5 alt2.aspmx.l.google.com", + "10 alt3.aspmx.l.google.com", + "10 alt4.aspmx.l.google.com", + ] + } + geo = { + type = "CNAME" + ttl = 5 + records = ["europe.test.example.com."] + set_identifier = "europe" + geolocation_routing_policy = { + continent = "EU" + } + } + geoproximity-aws-region = { + type = "CNAME" + ttl = 5 + records = ["us-east-1.test.example.com."] + set_identifier = "us-east-1-region" + geoproximity_routing_policy = { + aws_region = "us-east-1" + bias = 0 + } + } + geoproximity-coordinates = { + type = "CNAME" + ttl = 5 + records = ["nyc.test.example.com."] + set_identifier = "nyc" + geoproximity_routing_policy = { + coordinates = [{ + latitude = "40.71" + longitude = "-74.01" + }] + } + } + cloudfront_ipv4 = { + name = "cloudfront" + type = "A" + alias = { + name = "d3778kt32cqdww.cloudfront.net" + zone_id = "EF3T6981F7M1" + } + } + cloudfront_ipv6 = { + name = "cloudfront" + type = "AAAA" + alias = { + name = "d3778kt32cqdww.cloudfront.net" + zone_id = "EF3T6981F7M1" + } + } + blue = { + name = "test" + type = "CNAME" + ttl = 5 + records = ["test.example.com."] + set_identifier = "test-primary" + weighted_routing_policy = { + weight = 90 + } + } + green = { + name = "test" + type = "CNAME" + ttl = 5 + records = ["test2.example.com."] + set_identifier = "test-secondary" + weighted_routing_policy = { + weight = 10 + } + } + failover-primary = { + type = "A" + set_identifier = "failover-primary" + health_check_id = "d641c34c-a992-4edd-8a63-c540a4b18d0a" + alias = { + name = "d3778kt32cqdww.cloudfront.net" + zone_id = "EF3T6981F7M1" + } + failover_routing_policy = { + type = "PRIMARY" + } + } + failover-secondary = { + type = "A" + set_identifier = "failover-secondary" + alias = { + name = "s3-website-eu-west-1.amazonaws.com" + zone_id = "Z1BKCTXD74EZPE" + } + failover_routing_policy = { + type = "SECONDARY" + } + } + latency-test = { + type = "A" + set_identifier = "latency-test" + alias = { + name = "d3778kt32cqdww.cloudfront.net" + zone_id = "EF3T6981F7M1" + evaluate_target_health = true + } + latency_routing_policy = { + region = "eu-west-1" + } } } tags = { - ManagedBy = "Terraform" + Environment = "example" + Project = "terraform-aws-route53" } } +``` -module "records" { - source = "terraform-aws-modules/route53/aws//modules/records" - version = "~> 3.0" +### Private Hosted Zone - zone_name = keys(module.zones.route53_zone_zone_id)[0] +```hcl +module "zone" { + source = "terraform-aws-modules/route53/aws" - records = [ - { - name = "apigateway1" + name = "terraform-aws-modules-example.com" + comment = "Private zone for terraform-aws-modules example" + + records = { + "apigateway1" = { type = "A" alias = { name = "d-10qxlbvagl.execute-api.eu-west-1.amazonaws.com" zone_id = "ZLY8HYME6SFAD" } - }, - { - name = "" + } + ip_alias = { + name = "terraform-aws-modules-example.com" type = "A" ttl = 3600 records = [ "10.10.10.10", ] - }, - ] + } + } + + vpc = { + one = { + vpc_id = "vpc-1234556abcdef" + vpc_region = "eu-west-1" + } + } - depends_on = [module.zones] + tags = { + Environment = "example" + Project = "terraform-aws-route53" + } } ``` +## Sub-Modules + +The following independent sub-modules are available: + +- [delegation-sets](https://github.com/terraform-aws-modules/terraform-aws-route53/tree/master/modules/delegation-sets) creates AWS Route53 Delegation Sets +- [resolver-endpoint](https://github.com/terraform-aws-modules/terraform-aws-route53/tree/master/modules/resolver-endpoint) creates an AWS Route53 Resolver Endpoint and associated resources +- [resolver-firewall-rule-group](https://github.com/terraform-aws-modules/terraform-aws-route53/tree/master/modules/resolver-firewall-rule-group) creates an AWS Route53 Resolver Firewall Rule Group and associated resources + +See the respective module directories for examples and documentation. + ## Examples -- [Complete Route53 zones and records example](https://github.com/terraform-aws-modules/terraform-aws-route53/tree/master/examples/complete) which shows how to create Route53 records of various types like S3 bucket and CloudFront distribution. +- [Complete](https://github.com/terraform-aws-modules/terraform-aws-route53/tree/master/examples/complete) + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [aws](#requirement\_aws) | >= 6.3 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 6.3 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [route53\_dnssec\_kms](#module\_route53\_dnssec\_kms) | terraform-aws-modules/kms/aws | 4.0.0 | + +## Resources + +| Name | Type | +|------|------| +| [aws_route53_hosted_zone_dnssec.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_hosted_zone_dnssec) | resource | +| [aws_route53_key_signing_key.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_key_signing_key) | resource | +| [aws_route53_record.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record) | resource | +| [aws_route53_vpc_association_authorization.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_vpc_association_authorization) | resource | +| [aws_route53_zone.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_zone) | resource | +| [aws_route53_zone.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/route53_zone) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [comment](#input\_comment) | A comment for the hosted zone. Defaults to `Managed by Terraform` | `string` | `null` | no | +| [create](#input\_create) | Whether to create Route53 zone | `bool` | `true` | no | +| [create\_dnssec\_kms\_key](#input\_create\_dnssec\_kms\_key) | Whether to create a KMS key for DNSSEC signing | `bool` | `true` | no | +| [create\_zone](#input\_create\_zone) | Determines whether to create the Route53 zone or lookup an existing zone | `bool` | `true` | no | +| [delegation\_set\_id](#input\_delegation\_set\_id) | The ID of the reusable delegation set whose NS records you want to assign to the hosted zone. Conflicts with vpc as delegation sets can only be used for public zones | `string` | `null` | no | +| [dnssec\_kms\_key\_aliases](#input\_dnssec\_kms\_key\_aliases) | A list of aliases to create. Note - due to the use of `toset()`, values must be static strings and not computed values | `list(string)` | `[]` | no | +| [dnssec\_kms\_key\_arn](#input\_dnssec\_kms\_key\_arn) | The ARN of the KMS key to use for DNSSEC signing. Required when `create_dnssec_kms_key` is `false` | `string` | `null` | no | +| [dnssec\_kms\_key\_description](#input\_dnssec\_kms\_key\_description) | The description of the key as viewed in AWS console | `string` | `"Route53 DNSSEC KMS Key"` | no | +| [dnssec\_kms\_key\_tags](#input\_dnssec\_kms\_key\_tags) | Additional tags to apply to the KMS key created for DNSSEC signing | `map(string)` | `{}` | no | +| [enable\_dnssec](#input\_enable\_dnssec) | Whether to enable DNSSEC for the Route53 zone | `bool` | `false` | no | +| [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 | +| [name](#input\_name) | This is the name of the hosted zone | `string` | `""` | no | +| [private\_zone](#input\_private\_zone) | Whether the hosted zone is private. Only applicable when `create_zone = false` | `bool` | `false` | no | +| [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 |
map(object({
alias = optional(object({
evaluate_target_health = optional(bool, false)
name = string
zone_id = string
}))
allow_overwrite = optional(bool)
cidr_routing_policy = optional(object({
collection_id = string
location_name = string
}))
failover_routing_policy = optional(object({
type = string
}))
geolocation_routing_policy = optional(object({
continent = optional(string)
country = optional(string)
subdivision = optional(string)
}))
geoproximity_routing_policy = optional(object({
aws_region = optional(string)
bias = optional(number)
coordinates = optional(list(object({
latitude = number
longitude = number
})))
local_zone_group = optional(string)
}))
health_check_id = optional(string)
latency_routing_policy = optional(object({
region = string
}))
multivalue_answer_routing_policy = optional(bool)
name = optional(string)
full_name = optional(string)
records = optional(list(string))
set_identifier = optional(string)
ttl = optional(number)
type = string
weighted_routing_policy = optional(object({
weight = number
}))
timeouts = optional(object({
create = optional(string)
update = optional(string)
delete = optional(string)
}))
}))
| `{}` | no | +| [tags](#input\_tags) | Tags added to all zones. Will take precedence over tags from the 'zones' variable | `map(string)` | `{}` | no | +| [timeouts](#input\_timeouts) | Timeouts for the Route53 zone operations |
object({
create = optional(string)
update = optional(string)
delete = optional(string)
})
| `null` | no | +| [vpc](#input\_vpc) | Configuration block(s) specifying VPC(s) to associate with a private hosted zone. Conflicts with the delegation\_set\_id argument in this resource and any aws\_route53\_zone\_association resource specifying the same zone ID |
map(object({
vpc_id = string
vpc_region = optional(string)
}))
| `null` | no | +| [vpc\_association\_authorizations](#input\_vpc\_association\_authorizations) | A map of VPC association authorizations to create for the Route53 zone |
map(object({
vpc_id = string
vpc_region = optional(string)
}))
| `null` | no | +| [vpc\_id](#input\_vpc\_id) | The ID of the VPC associated with the existing hosted zone. Only applicable when `create_zone = false` | `string` | `null` | no | + +## Outputs - - +| Name | Description | +|------|-------------| +| [arn](#output\_arn) | Zone ARN of Route53 zone | +| [dnssec\_kms\_key\_arn](#output\_dnssec\_kms\_key\_arn) | The Amazon Resource Name (ARN) of the key | +| [dnssec\_kms\_key\_id](#output\_dnssec\_kms\_key\_id) | The globally unique identifier for the key | +| [dnssec\_kms\_key\_policy](#output\_dnssec\_kms\_key\_policy) | The IAM resource policy set on the key | +| [dnssec\_kms\_key\_region](#output\_dnssec\_kms\_key\_region) | The region for the key | +| [dnssec\_signing\_key\_digest\_value](#output\_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 | +| [dnssec\_signing\_key\_dnskey\_record](#output\_dnssec\_signing\_key\_dnskey\_record) | A string that represents a DNSKEY record | +| [dnssec\_signing\_key\_ds\_record](#output\_dnssec\_signing\_key\_ds\_record) | A string that represents a delegation signer (DS) record | +| [dnssec\_signing\_key\_id](#output\_dnssec\_signing\_key\_id) | Route 53 Hosted Zone identifier and KMS Key identifier, separated by a comma (`,`) | +| [dnssec\_signing\_key\_public\_key](#output\_dnssec\_signing\_key\_public\_key) | The public key, represented as a Base64 encoding, as required by RFC-4034 Page 5 | +| [dnssec\_signing\_key\_tag](#output\_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 | +| [id](#output\_id) | Zone ID of Route53 zone | +| [name](#output\_name) | Name of Route53 zone | +| [name\_servers](#output\_name\_servers) | Name servers of Route53 zone | +| [primary\_name\_server](#output\_primary\_name\_server) | The Route 53 name server that created the SOA record. | +| [records](#output\_records) | Records created in the Route53 zone | + ## Authors diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..f417c0a --- /dev/null +++ b/examples/README.md @@ -0,0 +1,8 @@ +# Examples + +Please note - the examples provided serve two primary means: + +1. Show users working examples of the various ways in which the module can be configured and features supported +2. A means of testing/validating module changes + +Please do not mistake the examples provided as "best practices". It is up to users to consult the AWS service documentation for best practices, usage recommendations, etc. diff --git a/examples/complete/README.md b/examples/complete/README.md index 76f3e9e..e3e8816 100644 --- a/examples/complete/README.md +++ b/examples/complete/README.md @@ -1,8 +1,7 @@ -# Route53 zones and records example +# Complete -Configuration in this directory creates Route53 zones and records for various types of resources - S3 bucket, CloudFront distribution, static records. +Configuration in this directory creates: -Also, there is a solution for Terragrunt users. ## Usage @@ -14,54 +13,44 @@ $ terraform plan $ terraform apply ``` -Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. +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. ## Requirements | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 1.3.2 | -| [aws](#requirement\_aws) | >= 5.91 | +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [aws](#requirement\_aws) | >= 6.3 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 5.91 | -| [aws.second\_account](#provider\_aws.second\_account) | >= 5.91 | +| [aws](#provider\_aws) | >= 6.3 | ## Modules | Name | Source | Version | |------|--------|---------| -| [cloudfront](#module\_cloudfront) | terraform-aws-modules/cloudfront/aws | ~> 3.0 | -| [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 | -| [records\_with\_full\_names](#module\_records\_with\_full\_names) | ../../modules/records | n/a | -| [resolver\_rule\_associations](#module\_resolver\_rule\_associations) | ../../modules/resolver-rule-associations | n/a | -| [s3\_bucket](#module\_s3\_bucket) | terraform-aws-modules/s3-bucket/aws | n/a | -| [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 | +| [cloudfront](#module\_cloudfront) | terraform-aws-modules/cloudfront/aws | ~> 5.0 | +| [resolver\_endpoint\_disabled](#module\_resolver\_endpoint\_disabled) | ../../modules/resolver-endpoint | n/a | +| [resolver\_endpoint\_inbound](#module\_resolver\_endpoint\_inbound) | ../../modules/resolver-endpoint | n/a | +| [resolver\_endpoint\_outbound](#module\_resolver\_endpoint\_outbound) | ../../modules/resolver-endpoint | n/a | +| [resolver\_firewall\_rule\_group](#module\_resolver\_firewall\_rule\_group) | ../../modules/resolver-firewall-rule-group | n/a | +| [resolver\_firewall\_rule\_group\_disabled](#module\_resolver\_firewall\_rule\_group\_disabled) | ../../modules/resolver-firewall-rule-group | n/a | +| [s3\_bucket](#module\_s3\_bucket) | terraform-aws-modules/s3-bucket/aws | ~> 5.0 | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 6.0 | +| [zone\_disabled](#module\_zone\_disabled) | ../.. | n/a | +| [zone\_private](#module\_zone\_private) | ../.. | n/a | +| [zone\_public](#module\_zone\_public) | ../.. | n/a | ## Resources | Name | Type | |------|------| -| [aws_route53_cidr_collection.example](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_cidr_collection) | resource | | [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 @@ -71,16 +60,62 @@ No inputs. | Name | Description | |------|-------------| -| [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\_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 | -| [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 | -| [route53\_static\_zone\_name](#output\_route53\_static\_zone\_name) | Name of Route53 zone created statically to avoid invalid count argument error when creating records and zones simmultaneously | -| [route53\_zone\_name](#output\_route53\_zone\_name) | Name of Route53 zone | -| [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 | +| [inbound\_resolver\_endpoint\_arn](#output\_inbound\_resolver\_endpoint\_arn) | The ARN of the Resolver Endpoint | +| [inbound\_resolver\_endpoint\_host\_vpc\_id](#output\_inbound\_resolver\_endpoint\_host\_vpc\_id) | The VPC ID used by the Resolver Endpoint | +| [inbound\_resolver\_endpoint\_id](#output\_inbound\_resolver\_endpoint\_id) | The ID of the Resolver Endpoint | +| [inbound\_resolver\_endpoint\_ip\_addresses](#output\_inbound\_resolver\_endpoint\_ip\_addresses) | Resolver Endpoint IP Addresses | +| [inbound\_resolver\_endpoint\_rules](#output\_inbound\_resolver\_endpoint\_rules) | Resolver Endpoint Rules created | +| [inbound\_resolver\_endpoint\_security\_group\_arn](#output\_inbound\_resolver\_endpoint\_security\_group\_arn) | Amazon Resource Name (ARN) of the security group | +| [inbound\_resolver\_endpoint\_security\_group\_id](#output\_inbound\_resolver\_endpoint\_security\_group\_id) | ID of the security group | +| [inbound\_resolver\_endpoint\_security\_group\_ids](#output\_inbound\_resolver\_endpoint\_security\_group\_ids) | Security Group IDs mapped to Resolver Endpoint | +| [outbound\_resolver\_endpoint\_arn](#output\_outbound\_resolver\_endpoint\_arn) | The ARN of the Resolver Endpoint | +| [outbound\_resolver\_endpoint\_host\_vpc\_id](#output\_outbound\_resolver\_endpoint\_host\_vpc\_id) | The VPC ID used by the Resolver Endpoint | +| [outbound\_resolver\_endpoint\_id](#output\_outbound\_resolver\_endpoint\_id) | The ID of the Resolver Endpoint | +| [outbound\_resolver\_endpoint\_ip\_addresses](#output\_outbound\_resolver\_endpoint\_ip\_addresses) | Resolver Endpoint IP Addresses | +| [outbound\_resolver\_endpoint\_rules](#output\_outbound\_resolver\_endpoint\_rules) | Resolver Endpoint Rules created | +| [outbound\_resolver\_endpoint\_security\_group\_arn](#output\_outbound\_resolver\_endpoint\_security\_group\_arn) | Amazon Resource Name (ARN) of the security group | +| [outbound\_resolver\_endpoint\_security\_group\_id](#output\_outbound\_resolver\_endpoint\_security\_group\_id) | ID of the security group | +| [outbound\_resolver\_endpoint\_security\_group\_ids](#output\_outbound\_resolver\_endpoint\_security\_group\_ids) | Security Group IDs mapped to Resolver Endpoint | +| [private\_zone\_arn](#output\_private\_zone\_arn) | Zone ARN of Route53 zone | +| [private\_zone\_dnssec\_kms\_key\_arn](#output\_private\_zone\_dnssec\_kms\_key\_arn) | The Amazon Resource Name (ARN) of the key | +| [private\_zone\_dnssec\_kms\_key\_id](#output\_private\_zone\_dnssec\_kms\_key\_id) | The globally unique identifier for the key | +| [private\_zone\_dnssec\_kms\_key\_policy](#output\_private\_zone\_dnssec\_kms\_key\_policy) | The IAM resource policy set on the key | +| [private\_zone\_dnssec\_kms\_key\_region](#output\_private\_zone\_dnssec\_kms\_key\_region) | The region for the key | +| [private\_zone\_dnssec\_signing\_key\_digest\_value](#output\_private\_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 | +| [private\_zone\_dnssec\_signing\_key\_dnskey\_record](#output\_private\_zone\_dnssec\_signing\_key\_dnskey\_record) | A string that represents a DNSKEY record | +| [private\_zone\_dnssec\_signing\_key\_ds\_record](#output\_private\_zone\_dnssec\_signing\_key\_ds\_record) | A string that represents a delegation signer (DS) record | +| [private\_zone\_dnssec\_signing\_key\_id](#output\_private\_zone\_dnssec\_signing\_key\_id) | Route 53 Hosted Zone identifier and KMS Key identifier, separated by a comma (`,`) | +| [private\_zone\_dnssec\_signing\_key\_public\_key](#output\_private\_zone\_dnssec\_signing\_key\_public\_key) | The public key, represented as a Base64 encoding, as required by RFC-4034 Page 5 | +| [private\_zone\_dnssec\_signing\_key\_tag](#output\_private\_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 | +| [private\_zone\_id](#output\_private\_zone\_id) | Zone ID of Route53 zone | +| [private\_zone\_name](#output\_private\_zone\_name) | Name of Route53 zone | +| [private\_zone\_name\_servers](#output\_private\_zone\_name\_servers) | Name servers of Route53 zone | +| [private\_zone\_primary\_name\_server](#output\_private\_zone\_primary\_name\_server) | The Route 53 name server that created the SOA record. | +| [private\_zone\_records](#output\_private\_zone\_records) | Records created in the Route53 zone | +| [public\_zone\_arn](#output\_public\_zone\_arn) | Zone ARN of Route53 zone | +| [public\_zone\_dnssec\_kms\_key\_arn](#output\_public\_zone\_dnssec\_kms\_key\_arn) | The Amazon Resource Name (ARN) of the key | +| [public\_zone\_dnssec\_kms\_key\_id](#output\_public\_zone\_dnssec\_kms\_key\_id) | The globally unique identifier for the key | +| [public\_zone\_dnssec\_kms\_key\_policy](#output\_public\_zone\_dnssec\_kms\_key\_policy) | The IAM resource policy set on the key | +| [public\_zone\_dnssec\_kms\_key\_region](#output\_public\_zone\_dnssec\_kms\_key\_region) | The region for the key | +| [public\_zone\_dnssec\_signing\_key\_digest\_value](#output\_public\_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 | +| [public\_zone\_dnssec\_signing\_key\_dnskey\_record](#output\_public\_zone\_dnssec\_signing\_key\_dnskey\_record) | A string that represents a DNSKEY record | +| [public\_zone\_dnssec\_signing\_key\_ds\_record](#output\_public\_zone\_dnssec\_signing\_key\_ds\_record) | A string that represents a delegation signer (DS) record | +| [public\_zone\_dnssec\_signing\_key\_id](#output\_public\_zone\_dnssec\_signing\_key\_id) | Route 53 Hosted Zone identifier and KMS Key identifier, separated by a comma (`,`) | +| [public\_zone\_dnssec\_signing\_key\_public\_key](#output\_public\_zone\_dnssec\_signing\_key\_public\_key) | The public key, represented as a Base64 encoding, as required by RFC-4034 Page 5 | +| [public\_zone\_dnssec\_signing\_key\_tag](#output\_public\_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 | +| [public\_zone\_id](#output\_public\_zone\_id) | Zone ID of Route53 zone | +| [public\_zone\_name](#output\_public\_zone\_name) | Name of Route53 zone | +| [public\_zone\_name\_servers](#output\_public\_zone\_name\_servers) | Name servers of Route53 zone | +| [public\_zone\_primary\_name\_server](#output\_public\_zone\_primary\_name\_server) | The Route 53 name server that created the SOA record. | +| [public\_zone\_records](#output\_public\_zone\_records) | Records created in the Route53 zone | +| [resolver\_firewall\_rule\_group\_arn](#output\_resolver\_firewall\_rule\_group\_arn) | The ARN (Amazon Resource Name) of the rule group | +| [resolver\_firewall\_rule\_group\_domain\_lists](#output\_resolver\_firewall\_rule\_group\_domain\_lists) | Map of all domain lists created and their attributes | +| [resolver\_firewall\_rule\_group\_id](#output\_resolver\_firewall\_rule\_group\_id) | The ID of the rule group | +| [resolver\_firewall\_rule\_group\_rules](#output\_resolver\_firewall\_rule\_group\_rules) | Map of all rules created and their attributes | +| [resolver\_firewall\_rule\_group\_share\_status](#output\_resolver\_firewall\_rule\_group\_share\_status) | Whether the rule group is shared with other AWS accounts, or was shared with the current account by another AWS account. Sharing is configured through AWS Resource Access Manager (AWS RAM). Valid values: `NOT_SHARED`, `SHARED_BY_ME`, `SHARED_WITH_ME` | +| [s3](#output\_s3) | S3 bucket name used for logging | + +## License + +Apache-2.0 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-ecs/blob/master/LICENSE). diff --git a/examples/complete/main.tf b/examples/complete/main.tf index 5d7fe55..9a7070d 100644 --- a/examples/complete/main.tf +++ b/examples/complete/main.tf @@ -1,143 +1,59 @@ provider "aws" { - region = "eu-west-1" + region = local.region } -provider "aws" { - region = "eu-west-1" - alias = "second_account" -} +data "aws_availability_zones" "available" {} 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"] + region = "eu-west-1" + name = "ex-${basename(path.cwd)}" vpc_cidr = "10.0.0.0/16" 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" - - zones = { - "terraform-aws-modules-example.com" = { - comment = "terraform-aws-modules-example.com (production)" - tags = { - Name = "terraform-aws-modules-example.com" - } - timeouts = { - create = "2h" - update = "3h" - delete = "1h" - } - } - - "app.terraform-aws-modules-example.com" = { - 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" - } - } - - "private-vpc.terraform-aws-modules-example.com" = { - # in case than private and public zones with the same domain name - domain_name = "terraform-aws-modules-example.com" - comment = "private-vpc.terraform-aws-modules-example.com" - vpc = [ - { - vpc_id = module.vpc1.vpc_id - }, - { - vpc_id = module.vpc2.vpc_id - }, - ] - tags = { - 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 = { - ManagedBy = "Terraform" + Name = local.name + Example = local.name + Repository = "https://github.com/terraform-aws-modules/terraform-aws-route53" } } -module "records" { - source = "../../modules/records" +################################################################################ +# Zone +################################################################################ - zone_name = local.zone_name - # zone_id = local.zone_id +module "zone_public" { + source = "../.." - records = [ - { - 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", - ] - timeouts = { - create = "2h" - update = "2h" - delete = "1h" - } - }, - { - name = "" - type = "A" - ttl = 3600 - records = [ - "10.10.10.10", - ] - set_identifier = "dev" - cidr_routing_policy = { - collection_id = aws_route53_cidr_collection.example.id - location_name = "*" - } - }, - { - key = "s3-bucket" + name = "terraform-aws-modules-example.com" + comment = "Public zone for terraform-aws-modules example" + + # DNSSEC + enable_dnssec = true + + records = { + s3 = { + # Have to use `name` due to computed values (cannot use as key) name = "s3-bucket-${module.s3_bucket.s3_bucket_hosted_zone_id}" type = "A" alias = { name = module.s3_bucket.s3_bucket_website_domain zone_id = module.s3_bucket.s3_bucket_hosted_zone_id } - }, - { - name = "" - type = "MX" - ttl = 3600 + } + mail = { + full_name = "terraform-aws-modules-example.com" + type = "MX" + ttl = 3600 records = [ "1 aspmx.l.google.com", "5 alt1.aspmx.l.google.com", "5 alt2.aspmx.l.google.com", "10 alt3.aspmx.l.google.com", - "10 alt4.aspmx.l.google.com" + "10 alt4.aspmx.l.google.com", ] - }, - { - name = "geo" + } + geo = { type = "CNAME" ttl = 5 records = ["europe.test.example.com."] @@ -145,9 +61,8 @@ module "records" { geolocation_routing_policy = { continent = "EU" } - }, - { - name = "geoproximity-aws-region" + } + geoproximity-aws-region = { type = "CNAME" ttl = 5 records = ["us-east-1.test.example.com."] @@ -156,37 +71,35 @@ module "records" { aws_region = "us-east-1" bias = 0 } - }, - { - name = "geoproximity-coordinates" + } + geoproximity-coordinates = { type = "CNAME" ttl = 5 records = ["nyc.test.example.com."] set_identifier = "nyc" geoproximity_routing_policy = { - coordinates = { + coordinates = [{ latitude = "40.71" longitude = "-74.01" - } + }] } - }, - { - name = "cloudfront" + } + cloudfront = { type = "A" alias = { name = module.cloudfront.cloudfront_distribution_domain_name zone_id = module.cloudfront.cloudfront_distribution_hosted_zone_id } - }, - { + } + cloudfront_ipv6 = { name = "cloudfront" type = "AAAA" alias = { name = module.cloudfront.cloudfront_distribution_domain_name zone_id = module.cloudfront.cloudfront_distribution_hosted_zone_id } - }, - { + } + blue = { name = "test" type = "CNAME" ttl = 5 @@ -195,8 +108,8 @@ module "records" { weighted_routing_policy = { weight = 90 } - }, - { + } + green = { name = "test" type = "CNAME" ttl = 5 @@ -205,9 +118,8 @@ module "records" { weighted_routing_policy = { weight = 10 } - }, - { - name = "failover-primary" + } + failover-primary = { type = "A" set_identifier = "failover-primary" health_check_id = aws_route53_health_check.failover.id @@ -218,9 +130,8 @@ module "records" { failover_routing_policy = { type = "PRIMARY" } - }, - { - name = "failover-secondary" + } + failover-secondary = { type = "A" set_identifier = "failover-secondary" alias = { @@ -230,9 +141,8 @@ module "records" { failover_routing_policy = { type = "SECONDARY" } - }, - { - name = "latency-test" + } + latency-test = { type = "A" set_identifier = "latency-test" alias = { @@ -244,222 +154,212 @@ module "records" { region = "eu-west-1" } } - ] + } - depends_on = [module.zones] + tags = local.tags } -module "terragrunt" { - source = "../../modules/records" +module "zone_private" { + source = "../.." - zone_name = local.zone_name + name = "terraform-aws-modules-example.com" + comment = "Private zone for terraform-aws-modules example" - # Terragrunt has a bug (https://github.com/gruntwork-io/terragrunt/issues/1211) that requires `records` to be wrapped with `jsonencode()` - records_jsonencoded = jsonencode([ - { - key = "new A" - name = "new" - type = "A" - ttl = 3600 - records = [ - "10.10.10.10", - ] - }, - { - name = "new2" - type = "A" - ttl = 3600 - records = [ - "10.10.10.11", - "10.10.10.12", - ] - }, - { - name = "s3-bucket-terragrunt" - type = "A" - alias = { - # In Terragrunt code the values may depend on the outputs of modules: - # name = dependency.s3_bucket.outputs.s3_bucket_website_domain - # zone_id = dependency.s3_bucket.outputs.s3_bucket_hosted_zone_id - # Terragrunt passes known values to the module: - name = "s3-website-eu-west-1.amazonaws.com" - zone_id = "Z1BKCTXD74EZPE" - } + vpc = { + one = { + vpc_id = module.vpc.vpc_id + vpc_region = local.region } - ]) + } + + tags = local.tags +} + +module "zone_disabled" { + source = "../.." - depends_on = [module.zones] + create = false } -module "records_with_full_names" { - source = "../../modules/records" +################################################################################ +# Resolver Endpoint +################################################################################ - zone_name = local.zone_name +module "resolver_endpoint_inbound" { + source = "../../modules/resolver-endpoint" - records = [ + name = "${local.name}-inbound" + direction = "INBOUND" + type = "IPV4" + protocols = ["Do53", "DoH"] + + ip_address = [ { - name = "with-full-name-override.${local.zone_name}" - full_name_override = true - type = "A" - ttl = 3600 - records = [ - "10.10.10.10", - ] + subnet_id = element(module.vpc.private_subnets, 0) }, { - name = "web" - type = "A" - ttl = 3600 - records = [ - "10.10.10.11", - "10.10.10.12", - ] - }, + subnet_id = element(module.vpc.private_subnets, 1) + } ] - depends_on = [module.zones] -} - -module "delegation_sets" { - source = "../../modules/delegation-sets" - - delegation_sets = { - main = {} - another = { - reference_name = "MySet" + # Security group + vpc_id = module.vpc.vpc_id + security_group_ingress_rules = { + one = { + cidr_ipv4 = element(module.vpc.private_subnets_cidr_blocks, 0) + description = "Allow inbound DNS queries from first subnet" + } + two = { + cidr_ipv4 = element(module.vpc.private_subnets_cidr_blocks, 1) + description = "Allow inbound DNS queries from second subnet" } } -} - - -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 - }, + security_group_egress_rules = { + one = { + cidr_ipv4 = element(module.vpc.private_subnets_cidr_blocks, 0) + description = "Allow outbound DNS responses to first subnet" + } + two = { + cidr_ipv4 = element(module.vpc.private_subnets_cidr_blocks, 1) + description = "Allow outbound DNS responses to second subnet" + } } -} - -module "resolver_rule_associations" { - source = "../../modules/resolver-rule-associations" - - vpc_id = module.vpc1.vpc_id - - resolver_rule_associations = { - example = { - resolver_rule_id = aws_route53_resolver_rule.sys.id - }, - example2 = { - name = "example2" - resolver_rule_id = aws_route53_resolver_rule.sys.id - vpc_id = module.vpc2.vpc_id - }, - } + tags = local.tags } -module "inbound_resolver_endpoints" { - source = "../../modules/resolver-endpoints" +module "resolver_endpoint_outbound" { + source = "../../modules/resolver-endpoint" - name = "example1" - direction = "INBOUND" - protocols = ["Do53", "DoH"] - - subnet_ids = slice(module.vpc1.private_subnets, 0, 2) - - vpc_id = module.vpc1.vpc_id - security_group_name_prefix = "example1-sg-" - security_group_ingress_cidr_blocks = [ - module.vpc2.vpc_cidr_block - ] - security_group_egress_cidr_blocks = [ - module.vpc2.vpc_cidr_block - ] -} - -module "outbound_resolver_endpoints" { - source = "../../modules/resolver-endpoints" - - name = "example2" + name = "${local.name}-outbound" direction = "OUTBOUND" + type = "IPV4" protocols = ["Do53", "DoH"] # Using fixed IP addresses ip_address = [ { ip = "10.0.0.35" - subnet_id = module.vpc1.private_subnets[0] + subnet_id = element(module.vpc.private_subnets, 0) }, { - ip = "10.0.1.35" - subnet_id = module.vpc1.private_subnets[1] + ip = "10.0.16.35" + subnet_id = element(module.vpc.private_subnets, 1) } ] - vpc_id = module.vpc1.vpc_id - security_group_name_prefix = "example2-sg-" - security_group_ingress_cidr_blocks = [ - module.vpc1.vpc_cidr_block - ] - security_group_egress_cidr_blocks = [ - module.vpc2.vpc_cidr_block - ] -} - -################### -# Disabled modules -################### + # Security group + vpc_id = module.vpc.vpc_id + security_group_ingress_rules = { + one = { + cidr_ipv4 = element(module.vpc.private_subnets_cidr_blocks, 0) + description = "Allow inbound DNS queries from first subnet" + } + two = { + cidr_ipv4 = element(module.vpc.private_subnets_cidr_blocks, 1) + description = "Allow inbound DNS queries from second subnet" + } + } + security_group_egress_rules = { + one = { + cidr_ipv4 = element(module.vpc.private_subnets_cidr_blocks, 0) + description = "Allow outbound DNS responses to first subnet" + } + two = { + cidr_ipv4 = element(module.vpc.private_subnets_cidr_blocks, 1) + description = "Allow outbound DNS responses to second subnet" + } + } -module "disabled_resolver_endpoints" { - source = "../../modules/resolver-endpoints" + # Resolver Rule(s) & Association(s) + rules = { + ex-forward = { + domain_name = "example.com." + rule_type = "FORWARD" + target_ip = [{ + ip = "123.45.67.89" + }] + # Association + vpc_id = module.vpc.vpc_id + } + ex-system = { + domain_name = "system.com." + rule_type = "SYSTEM" + } + } - create = false + tags = local.tags } -module "disabled_records" { - source = "../../modules/records" +module "resolver_endpoint_disabled" { + source = "../../modules/resolver-endpoint" create = false } -module "disabled_zone_cross_account_vpc_association" { - source = "../../modules/zone-cross-account-vpc-association" +################################################################################ +# DNS Firewall Module +################################################################################ - providers = { - aws.r53_owner = aws - aws.vpc_owner = aws.second_account +module "resolver_firewall_rule_group" { + source = "../../modules/resolver-firewall-rule-group" + + name = local.name + + rules = { + block = { + name = "blockit" + priority = 110 + action = "BLOCK" + block_response = "NODATA" + domains = ["google.com."] + } + block_override = { + priority = 120 + action = "BLOCK" + block_response = "OVERRIDE" + block_override_dns_type = "CNAME" + block_override_domain = "example.com." + block_override_ttl = 1 + domains = ["microsoft.com."] + } + allow = { + priority = 130 + action = "ALLOW" + domains = ["amazon.com.", "amazonaws.com."] + } } + tags = local.tags +} + +module "resolver_firewall_rule_group_disabled" { + source = "../../modules/resolver-firewall-rule-group" + create = false } -######### -# Extras - should be created in advance -######### +################################################################################ +# Supporting Resources +################################################################################ -resource "aws_route53_health_check" "failover" { - fqdn = module.cloudfront.cloudfront_distribution_domain_name - port = 443 - type = "HTTPS" - resource_path = "/index.html" - failure_threshold = 3 - request_interval = 30 +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 6.0" + + name = local.name + cidr = local.vpc_cidr + + azs = local.azs + private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 4, k)] + public_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 48)] + + tags = local.tags } module "s3_bucket" { - source = "terraform-aws-modules/s3-bucket/aws" + source = "terraform-aws-modules/s3-bucket/aws" + version = "~> 5.0" bucket_prefix = "s3-bucket-" force_destroy = true @@ -467,11 +367,13 @@ module "s3_bucket" { website = { index_document = "index.html" } + + tags = local.tags } module "cloudfront" { source = "terraform-aws-modules/cloudfront/aws" - version = "~> 3.0" + version = "~> 5.0" enabled = false wait_for_deployment = false @@ -490,46 +392,17 @@ module "cloudfront" { viewer_certificate = { cloudfront_default_certificate = true } -} - -data "aws_availability_zones" "available" {} - -module "vpc1" { - source = "terraform-aws-modules/vpc/aws" - version = "~> 5.0" - - name = "my-vpc-for-private-route53-zone" - cidr = local.vpc_cidr - - azs = local.azs - private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k)] -} - -module "vpc2" { - source = "terraform-aws-modules/vpc/aws" - version = "~> 5.0" - - name = "my-second-vpc-for-private-route53-zone" - 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" + tags = local.tags } -resource "aws_route53_resolver_rule" "sys" { - domain_name = "sys-example.com" - rule_type = "SYSTEM" -} +resource "aws_route53_health_check" "failover" { + fqdn = module.cloudfront.cloudfront_distribution_domain_name + port = 443 + type = "HTTPS" + resource_path = "/index.html" + failure_threshold = 3 + request_interval = 30 -resource "aws_route53_cidr_collection" "example" { - name = "collection-1" + tags = local.tags } diff --git a/examples/complete/outputs.tf b/examples/complete/outputs.tf index d0d7103..f66c1cc 100644 --- a/examples/complete/outputs.tf +++ b/examples/complete/outputs.tf @@ -1,63 +1,291 @@ -# zones -output "route53_zone_zone_id" { +################################################################################ +# Public Zone +################################################################################ + +output "public_zone_id" { description = "Zone ID of Route53 zone" - value = module.zones.route53_zone_zone_id + value = module.zone_public.id } -output "route53_zone_zone_arn" { +output "public_zone_arn" { description = "Zone ARN of Route53 zone" - value = module.zones.route53_zone_zone_arn + value = module.zone_public.arn } -output "route53_zone_name_servers" { +output "public_zone_name_servers" { description = "Name servers of Route53 zone" - value = module.zones.route53_zone_name_servers + value = module.zone_public.name_servers +} + +output "public_zone_primary_name_server" { + description = "The Route 53 name server that created the SOA record." + value = module.zone_public.primary_name_server } -output "route53_zone_name" { +output "public_zone_name" { description = "Name of Route53 zone" - value = module.zones.route53_zone_name + value = module.zone_public.name +} + +output "public_zone_dnssec_signing_key_digest_value" { + description = "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" + value = module.zone_public.dnssec_signing_key_digest_value +} + +output "public_zone_dnssec_signing_key_dnskey_record" { + description = "A string that represents a DNSKEY record" + value = module.zone_public.dnssec_signing_key_dnskey_record +} + +output "public_zone_dnssec_signing_key_ds_record" { + description = "A string that represents a delegation signer (DS) record" + value = module.zone_public.dnssec_signing_key_ds_record +} + +output "public_zone_dnssec_signing_key_id" { + description = "Route 53 Hosted Zone identifier and KMS Key identifier, separated by a comma (`,`)" + value = module.zone_public.dnssec_signing_key_id +} + +output "public_zone_dnssec_signing_key_tag" { + description = "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" + value = module.zone_public.dnssec_signing_key_tag +} + +output "public_zone_dnssec_signing_key_public_key" { + description = "The public key, represented as a Base64 encoding, as required by RFC-4034 Page 5" + value = module.zone_public.dnssec_signing_key_public_key +} + +# KMS key +output "public_zone_dnssec_kms_key_arn" { + description = "The Amazon Resource Name (ARN) of the key" + value = module.zone_public.dnssec_kms_key_arn +} + +output "public_zone_dnssec_kms_key_id" { + description = "The globally unique identifier for the key" + value = module.zone_public.dnssec_kms_key_id +} + +output "public_zone_dnssec_kms_key_region" { + description = "The region for the key" + value = module.zone_public.dnssec_kms_key_region } -output "route53_static_zone_name" { - description = "Name of Route53 zone created statically to avoid invalid count argument error when creating records and zones simmultaneously" - value = module.zones.route53_static_zone_name +output "public_zone_dnssec_kms_key_policy" { + description = "The IAM resource policy set on the key" + value = module.zone_public.dnssec_kms_key_policy } -# records -output "route53_record_name" { - description = "The name of the record" - value = module.records.route53_record_name +output "public_zone_records" { + description = "Records created in the Route53 zone" + value = module.zone_public.records } -output "route53_record_fqdn" { - description = "FQDN built using the zone domain and name" - value = module.records.route53_record_fqdn +################################################################################ +# Private Zone +################################################################################ + +output "private_zone_id" { + description = "Zone ID of Route53 zone" + value = module.zone_private.id +} + +output "private_zone_arn" { + description = "Zone ARN of Route53 zone" + value = module.zone_private.arn +} + +output "private_zone_name_servers" { + description = "Name servers of Route53 zone" + value = module.zone_private.name_servers +} + +output "private_zone_primary_name_server" { + description = "The Route 53 name server that created the SOA record." + value = module.zone_private.primary_name_server +} + +output "private_zone_name" { + description = "Name of Route53 zone" + value = module.zone_private.name +} + +output "private_zone_dnssec_signing_key_digest_value" { + description = "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" + value = module.zone_private.dnssec_signing_key_digest_value +} + +output "private_zone_dnssec_signing_key_dnskey_record" { + description = "A string that represents a DNSKEY record" + value = module.zone_private.dnssec_signing_key_dnskey_record +} + +output "private_zone_dnssec_signing_key_ds_record" { + description = "A string that represents a delegation signer (DS) record" + value = module.zone_private.dnssec_signing_key_ds_record +} + +output "private_zone_dnssec_signing_key_id" { + description = "Route 53 Hosted Zone identifier and KMS Key identifier, separated by a comma (`,`)" + value = module.zone_private.dnssec_signing_key_id +} + +output "private_zone_dnssec_signing_key_tag" { + description = "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" + value = module.zone_private.dnssec_signing_key_tag +} + +output "private_zone_dnssec_signing_key_public_key" { + description = "The public key, represented as a Base64 encoding, as required by RFC-4034 Page 5" + value = module.zone_private.dnssec_signing_key_public_key +} + +# KMS key +output "private_zone_dnssec_kms_key_arn" { + description = "The Amazon Resource Name (ARN) of the key" + value = module.zone_private.dnssec_kms_key_arn +} + +output "private_zone_dnssec_kms_key_id" { + description = "The globally unique identifier for the key" + value = module.zone_private.dnssec_kms_key_id +} + +output "private_zone_dnssec_kms_key_region" { + description = "The region for the key" + value = module.zone_private.dnssec_kms_key_region +} + +output "private_zone_dnssec_kms_key_policy" { + description = "The IAM resource policy set on the key" + value = module.zone_private.dnssec_kms_key_policy +} + +output "private_zone_records" { + description = "Records created in the Route53 zone" + value = module.zone_private.records +} + +################################################################################ +# Inbound Resolver Endpoint +################################################################################ + +output "inbound_resolver_endpoint_id" { + description = "The ID of the Resolver Endpoint" + value = module.resolver_endpoint_inbound.id +} + +output "inbound_resolver_endpoint_arn" { + description = "The ARN of the Resolver Endpoint" + value = module.resolver_endpoint_inbound.arn +} + +output "inbound_resolver_endpoint_host_vpc_id" { + description = "The VPC ID used by the Resolver Endpoint" + value = module.resolver_endpoint_inbound.host_vpc_id +} + +output "inbound_resolver_endpoint_security_group_ids" { + description = "Security Group IDs mapped to Resolver Endpoint" + value = module.resolver_endpoint_inbound.security_group_ids +} + +output "inbound_resolver_endpoint_ip_addresses" { + description = "Resolver Endpoint IP Addresses" + value = module.resolver_endpoint_inbound.ip_addresses +} + +output "inbound_resolver_endpoint_security_group_arn" { + description = "Amazon Resource Name (ARN) of the security group" + value = module.resolver_endpoint_inbound.security_group_arn +} + +output "inbound_resolver_endpoint_security_group_id" { + description = "ID of the security group" + value = module.resolver_endpoint_inbound.security_group_id +} + +output "inbound_resolver_endpoint_rules" { + description = "Resolver Endpoint Rules created" + value = module.resolver_endpoint_inbound.rules +} + +################################################################################ +# Outbound Resolver Endpoint +################################################################################ + +output "outbound_resolver_endpoint_id" { + description = "The ID of the Resolver Endpoint" + value = module.resolver_endpoint_outbound.id +} + +output "outbound_resolver_endpoint_arn" { + description = "The ARN of the Resolver Endpoint" + value = module.resolver_endpoint_outbound.arn +} + +output "outbound_resolver_endpoint_host_vpc_id" { + description = "The VPC ID used by the Resolver Endpoint" + value = module.resolver_endpoint_outbound.host_vpc_id +} + +output "outbound_resolver_endpoint_security_group_ids" { + description = "Security Group IDs mapped to Resolver Endpoint" + value = module.resolver_endpoint_outbound.security_group_ids +} + +output "outbound_resolver_endpoint_ip_addresses" { + description = "Resolver Endpoint IP Addresses" + value = module.resolver_endpoint_outbound.ip_addresses +} + +output "outbound_resolver_endpoint_security_group_arn" { + description = "Amazon Resource Name (ARN) of the security group" + value = module.resolver_endpoint_outbound.security_group_arn +} + +output "outbound_resolver_endpoint_security_group_id" { + description = "ID of the security group" + value = module.resolver_endpoint_outbound.security_group_id +} + +output "outbound_resolver_endpoint_rules" { + description = "Resolver Endpoint Rules created" + value = module.resolver_endpoint_outbound.rules +} + +################################################################################ +# Resolver Firewall Rule Group +################################################################################ + +output "resolver_firewall_rule_group_arn" { + description = "The ARN (Amazon Resource Name) of the rule group" + value = module.resolver_firewall_rule_group.arn } -# delegation sets -output "route53_delegation_set_id" { - description = "ID of Route53 delegation set" - value = module.delegation_sets.route53_delegation_set_id +output "resolver_firewall_rule_group_id" { + description = "The ID of the rule group" + value = module.resolver_firewall_rule_group.id } -output "route53_delegation_set_name_servers" { - description = "Name servers in the Route53 delegation set" - value = module.delegation_sets.route53_delegation_set_name_servers +output "resolver_firewall_rule_group_share_status" { + description = "Whether the rule group is shared with other AWS accounts, or was shared with the current account by another AWS account. Sharing is configured through AWS Resource Access Manager (AWS RAM). Valid values: `NOT_SHARED`, `SHARED_BY_ME`, `SHARED_WITH_ME`" + value = module.resolver_firewall_rule_group.share_status } -# resolver rule associations -output "route53_resolver_rule_association_id" { - description = "ID of Route53 Resolver rule associations" - value = module.resolver_rule_associations.route53_resolver_rule_association_id +output "resolver_firewall_rule_group_domain_lists" { + description = "Map of all domain lists created and their attributes" + value = module.resolver_firewall_rule_group.domain_lists } -output "route53_resolver_rule_association_name" { - description = "Name of Route53 Resolver rule associations" - value = module.resolver_rule_associations.route53_resolver_rule_association_name +output "resolver_firewall_rule_group_rules" { + description = "Map of all rules created and their attributes" + value = module.resolver_firewall_rule_group.rules } -output "route53_resolver_rule_association_resolver_rule_id" { - description = "ID of Route53 Resolver rule associations resolver rule" - value = module.resolver_rule_associations.route53_resolver_rule_association_resolver_rule_id +output "s3" { + description = "S3 bucket name used for logging" + value = module.s3_bucket } diff --git a/examples/complete/versions.tf b/examples/complete/versions.tf index 409aebe..fd053a1 100644 --- a/examples/complete/versions.tf +++ b/examples/complete/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 1.3.2" + required_version = ">= 1.5.7" required_providers { aws = { source = "hashicorp/aws" - version = ">= 5.91" + version = ">= 6.3" } } } diff --git a/main.tf b/main.tf new file mode 100644 index 0000000..2873325 --- /dev/null +++ b/main.tf @@ -0,0 +1,204 @@ +locals { + zone_id = try(aws_route53_zone.this[0].id, data.aws_route53_zone.this[0].id, null) + zone_name = try(aws_route53_zone.this[0].name, data.aws_route53_zone.this[0].name, null) +} + +################################################################################ +# Zone +################################################################################ + +data "aws_route53_zone" "this" { + count = var.create && !var.create_zone ? 1 : 0 + + name = var.name + private_zone = var.private_zone + vpc_id = var.vpc_id +} + +resource "aws_route53_zone" "this" { + count = var.create && var.create_zone ? 1 : 0 + + comment = var.comment + delegation_set_id = var.vpc == null ? var.delegation_set_id : null + force_destroy = var.force_destroy + name = var.name + tags = var.tags + + dynamic "vpc" { + for_each = var.vpc != null ? var.vpc : {} + + content { + vpc_id = vpc.value.vpc_id + vpc_region = vpc.value.vpc_region + } + } + + dynamic "timeouts" { + for_each = var.timeouts != null ? [var.timeouts] : [] + + content { + create = timeouts.value.create + update = timeouts.value.update + delete = timeouts.value.delete + } + } +} + +################################################################################ +# VPC Association Authorization +################################################################################ + +resource "aws_route53_vpc_association_authorization" "this" { + for_each = var.create && var.vpc_association_authorizations != null ? var.vpc_association_authorizations : {} + + zone_id = local.zone_id + vpc_id = each.value.vpc_id + vpc_region = each.value.vpc_region +} + +################################################################################ +# DNSSEC +################################################################################ + +resource "aws_route53_key_signing_key" "this" { + count = var.create && var.enable_dnssec ? 1 : 0 + + name = local.zone_name + hosted_zone_id = local.zone_id + key_management_service_arn = var.create_dnssec_kms_key ? module.route53_dnssec_kms.key_arn : var.dnssec_kms_key_arn +} + +resource "aws_route53_hosted_zone_dnssec" "this" { + count = var.create && var.enable_dnssec ? 1 : 0 + + hosted_zone_id = aws_route53_key_signing_key.this[0].hosted_zone_id +} + +module "route53_dnssec_kms" { + source = "terraform-aws-modules/kms/aws" + version = "4.0.0" + + create = var.create && var.enable_dnssec && var.create_dnssec_kms_key + + # This key must be in the us-east-1 Region per AWS API + region = "us-east-1" + + description = var.dnssec_kms_key_description + + key_usage = "SIGN_VERIFY" + customer_master_key_spec = "ECC_NIST_P256" + enable_key_rotation = false + enable_route53_dnssec = true + + # Aliases + aliases = var.dnssec_kms_key_aliases + + tags = merge( + var.tags, + var.dnssec_kms_key_tags + ) +} + +################################################################################ +# Records +################################################################################ + +resource "aws_route53_record" "this" { + for_each = { for k, v in var.records : k => v if var.create } + + dynamic "alias" { + for_each = each.value.alias != null ? [each.value.alias] : [] + + content { + evaluate_target_health = each.value.alias.evaluate_target_health + name = each.value.alias.name + zone_id = each.value.alias.zone_id + } + } + + allow_overwrite = each.value.allow_overwrite + + dynamic "cidr_routing_policy" { + for_each = each.value.cidr_routing_policy != null ? [each.value.cidr_routing_policy] : [] + + content { + collection_id = cidr_routing_policy.value.collection_id + location_name = cidr_routing_policy.value.location_name + } + } + + dynamic "failover_routing_policy" { + for_each = each.value.failover_routing_policy != null ? [each.value.failover_routing_policy] : [] + + content { + type = each.value.failover_routing_policy.type + } + } + + dynamic "geolocation_routing_policy" { + for_each = each.value.geolocation_routing_policy != null ? [each.value.geolocation_routing_policy] : [] + + content { + continent = each.value.geolocation_routing_policy.continent + country = each.value.geolocation_routing_policy.country + subdivision = each.value.geolocation_routing_policy.subdivision + } + } + + dynamic "geoproximity_routing_policy" { + for_each = each.value.geoproximity_routing_policy != null ? [each.value.geoproximity_routing_policy] : [] + + content { + aws_region = each.value.geoproximity_routing_policy.aws_region + bias = each.value.geoproximity_routing_policy.bias + + dynamic "coordinates" { + for_each = each.value.geoproximity_routing_policy.coordinates != null ? each.value.geoproximity_routing_policy.coordinates : [] + + content { + latitude = coordinates.value.latitude + longitude = coordinates.value.longitude + } + } + + local_zone_group = each.value.geoproximity_routing_policy.local_zone_group + } + } + + health_check_id = each.value.health_check_id + + dynamic "latency_routing_policy" { + for_each = each.value.latency_routing_policy != null ? [each.value.latency_routing_policy] : [] + + content { + region = each.value.latency_routing_policy.region + } + } + + multivalue_answer_routing_policy = each.value.multivalue_answer_routing_policy + name = coalesce(each.value.full_name, "${each.value.name}.${local.zone_name}", "${each.key}.${local.zone_name}") + records = each.value.records + set_identifier = each.value.set_identifier + ttl = each.value.ttl + type = each.value.type + + dynamic "weighted_routing_policy" { + for_each = each.value.weighted_routing_policy != null ? [each.value.weighted_routing_policy] : [] + + content { + weight = each.value.weighted_routing_policy.weight + } + } + + zone_id = local.zone_id + + dynamic "timeouts" { + for_each = each.value.timeouts != null ? [each.value.timeouts] : [] + + content { + create = timeouts.value.create + update = timeouts.value.update + delete = timeouts.value.delete + } + } +} diff --git a/modules/delegation-sets/README.md b/modules/delegation-sets/README.md index 91af013..6261cb9 100644 --- a/modules/delegation-sets/README.md +++ b/modules/delegation-sets/README.md @@ -2,43 +2,37 @@ This module creates Route53 delegation sets. -## Usage +[![SWUbanner](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner2-direct.svg)](https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md) -### Create Route53 delegation sets and public zones using a delegation set +## Usage ```hcl module "delegation_sets" { source = "terraform-aws-modules/route53/aws//modules/delegation-sets" - version = "~> 2.0" delegation_sets = { - "myset" = { - reference_name = "myset" + "myapp1" = { + reference_name = "myapp1" } } + + tags = { + Environment = "example" + Project = "terraform-aws-route53" + } } module "zones" { - source = "terraform-aws-modules/route53/aws//modules/zones" - version = "~> 2.0" + source = "terraform-aws-modules/route53/aws" - zones = { - "myapp1.com" = { - comment = "myapp1.com" - delegation_set_id = module.delegation_sets.route53_delegation_set_id["myset"] - } + name = "myapp1.com" - "myapp2.com" = { - comment = "myapp2.com" - delegation_set_id = module.delegation_sets.route53_delegation_set_id["myset"] - } - } + delegation_set_id = module.delegation_sets.route53_delegation_set_id["myapp1"] tags = { - ManagedBy = "Terraform" + Environment = "example" + Project = "terraform-aws-route53" } - - depends_on = [module.delegation_sets] } ``` @@ -47,14 +41,14 @@ module "zones" { | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 1.3.2 | -| [aws](#requirement\_aws) | >= 5.91 | +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [aws](#requirement\_aws) | >= 6.3 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 5.91 | +| [aws](#provider\_aws) | >= 6.3 | ## Modules @@ -71,7 +65,7 @@ No modules. | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| | [create](#input\_create) | Whether to create Route53 delegation sets | `bool` | `true` | no | -| [delegation\_sets](#input\_delegation\_sets) | Map of Route53 delegation set parameters | `any` | `{}` | no | +| [delegation\_sets](#input\_delegation\_sets) | Map of Route53 delegation set parameters |
map(object({
reference_name = optional(string)
}))
| `{}` | no | ## Outputs @@ -81,3 +75,7 @@ No modules. | [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 | + +## License + +Apache 2 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-route53/tree/master/LICENSE) for full details. diff --git a/modules/delegation-sets/main.tf b/modules/delegation-sets/main.tf index d74f70b..7e6fbc8 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 = { for k, v in var.delegation_sets : k => v if var.create } - reference_name = lookup(each.value, "reference_name", null) + reference_name = each.value.reference_name } diff --git a/modules/delegation-sets/variables.tf b/modules/delegation-sets/variables.tf index 086e437..a373624 100644 --- a/modules/delegation-sets/variables.tf +++ b/modules/delegation-sets/variables.tf @@ -6,6 +6,8 @@ variable "create" { variable "delegation_sets" { description = "Map of Route53 delegation set parameters" - type = any - default = {} + type = map(object({ + reference_name = optional(string) + })) + default = {} } diff --git a/modules/delegation-sets/versions.tf b/modules/delegation-sets/versions.tf index 409aebe..fd053a1 100644 --- a/modules/delegation-sets/versions.tf +++ b/modules/delegation-sets/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 1.3.2" + required_version = ">= 1.5.7" required_providers { aws = { source = "hashicorp/aws" - version = ">= 5.91" + version = ">= 6.3" } } } diff --git a/modules/records/README.md b/modules/records/README.md deleted file mode 100644 index 47d3183..0000000 --- a/modules/records/README.md +++ /dev/null @@ -1,71 +0,0 @@ -# Route53 Records - -This module creates DNS records in Route53 zone. - -Due to a bug in Terragrunt (https://github.com/gruntwork-io/terragrunt/issues/1211), users should specify records using `records_jsonencoded` argument as jsonencode()'d string, like this: - -```hcl -records_jsonencoded = jsonencode([ - { - name = "tg-list1" - type = "A" - ttl = 3600 - records = [ - "10.10.10.10", - ] - }, - { - name = "tg-list2" - type = "A" - ttl = 3600 - records = [ - "20.10.10.10", - "30.10.10.10", - ] - } -]) -``` - - -## Requirements - -| Name | Version | -|------|---------| -| [terraform](#requirement\_terraform) | >= 1.3.2 | -| [aws](#requirement\_aws) | >= 5.91 | - -## Providers - -| Name | Version | -|------|---------| -| [aws](#provider\_aws) | >= 5.91 | - -## Modules - -No modules. - -## Resources - -| Name | Type | -|------|------| -| [aws_route53_record.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record) | resource | -| [aws_route53_zone.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/route53_zone) | data source | - -## Inputs - -| Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| -| [create](#input\_create) | Whether to create DNS records | `bool` | `true` | no | -| [private\_zone](#input\_private\_zone) | Whether Route53 zone is private or public | `bool` | `false` | no | -| [records](#input\_records) | List of objects of DNS records | `any` | `[]` | no | -| [records\_jsonencoded](#input\_records\_jsonencoded) | List of map of DNS records (stored as jsonencoded string, for terragrunt) | `string` | `null` | no | -| [zone\_id](#input\_zone\_id) | ID of DNS zone | `string` | `null` | no | -| [zone\_name](#input\_zone\_name) | Name of DNS zone | `string` | `null` | no | - -## Outputs - -| Name | Description | -|------|-------------| -| [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/records/main.tf b/modules/records/main.tf deleted file mode 100644 index 0de5992..0000000 --- a/modules/records/main.tf +++ /dev/null @@ -1,113 +0,0 @@ -locals { - # Terragrunt users have to provide `records_jsonencoded` as jsonencode()'d string. - # See details: https://github.com/gruntwork-io/terragrunt/issues/1211 - records = concat(var.records, try(jsondecode(var.records_jsonencoded), [])) - - # Convert `records` from list to map with unique keys - recordsets = { for rs in local.records : try(rs.key, join(" ", compact(["${rs.name} ${rs.type}", try(rs.set_identifier, "")]))) => rs } -} - -data "aws_route53_zone" "this" { - count = var.create && (var.zone_id != null || var.zone_name != null) ? 1 : 0 - - zone_id = var.zone_id - name = var.zone_name - private_zone = var.private_zone -} - -resource "aws_route53_record" "this" { - for_each = { for k, v in local.recordsets : k => v if var.create && (var.zone_id != null || var.zone_name != null) } - - zone_id = data.aws_route53_zone.this[0].zone_id - - name = each.value.name != "" ? (lookup(each.value, "full_name_override", false) ? each.value.name : "${each.value.name}.${data.aws_route53_zone.this[0].name}") : data.aws_route53_zone.this[0].name - type = each.value.type - ttl = lookup(each.value, "ttl", null) - records = try(each.value.records, null) - set_identifier = lookup(each.value, "set_identifier", null) - health_check_id = lookup(each.value, "health_check_id", null) - multivalue_answer_routing_policy = lookup(each.value, "multivalue_answer_routing_policy", null) - allow_overwrite = lookup(each.value, "allow_overwrite", false) - - dynamic "alias" { - for_each = length(keys(lookup(each.value, "alias", {}))) == 0 ? [] : [true] - - content { - name = each.value.alias.name - zone_id = try(each.value.alias.zone_id, data.aws_route53_zone.this[0].zone_id) - evaluate_target_health = lookup(each.value.alias, "evaluate_target_health", false) - } - } - - dynamic "failover_routing_policy" { - for_each = length(keys(lookup(each.value, "failover_routing_policy", {}))) == 0 ? [] : [true] - - content { - type = each.value.failover_routing_policy.type - } - } - - dynamic "latency_routing_policy" { - for_each = length(keys(lookup(each.value, "latency_routing_policy", {}))) == 0 ? [] : [true] - - content { - region = each.value.latency_routing_policy.region - } - } - - dynamic "weighted_routing_policy" { - for_each = length(keys(lookup(each.value, "weighted_routing_policy", {}))) == 0 ? [] : [true] - - content { - weight = each.value.weighted_routing_policy.weight - } - } - - dynamic "geolocation_routing_policy" { - for_each = length(keys(lookup(each.value, "geolocation_routing_policy", {}))) == 0 ? [] : [true] - - content { - continent = lookup(each.value.geolocation_routing_policy, "continent", null) - country = lookup(each.value.geolocation_routing_policy, "country", null) - subdivision = lookup(each.value.geolocation_routing_policy, "subdivision", null) - } - } - - dynamic "geoproximity_routing_policy" { - for_each = length(keys(lookup(each.value, "geoproximity_routing_policy", {}))) == 0 ? [] : [true] - - content { - aws_region = lookup(each.value.geoproximity_routing_policy, "aws_region", null) - bias = lookup(each.value.geoproximity_routing_policy, "bias", null) - local_zone_group = lookup(each.value.geoproximity_routing_policy, "local_zone_group", null) - - dynamic "coordinates" { - for_each = lookup(each.value.geoproximity_routing_policy, "coordinates", null) == null ? [] : [lookup(each.value.geoproximity_routing_policy, "coordinates", null)] - - content { - latitude = coordinates.value.latitude - longitude = coordinates.value.longitude - } - } - } - } - - dynamic "cidr_routing_policy" { - for_each = try([each.value.cidr_routing_policy], []) - - content { - collection_id = cidr_routing_policy.value.collection_id - location_name = cidr_routing_policy.value.location_name - } - } - - dynamic "timeouts" { - for_each = try([each.value.timeouts], []) - - content { - create = try(timeouts.value.create, null) - update = try(timeouts.value.update, null) - delete = try(timeouts.value.delete, null) - } - } -} diff --git a/modules/records/outputs.tf b/modules/records/outputs.tf deleted file mode 100644 index 6d3b7ff..0000000 --- a/modules/records/outputs.tf +++ /dev/null @@ -1,9 +0,0 @@ -output "route53_record_name" { - description = "The name of the record" - value = { for k, v in aws_route53_record.this : k => v.name } -} - -output "route53_record_fqdn" { - description = "FQDN built using the zone domain and name" - value = { for k, v in aws_route53_record.this : k => v.fqdn } -} diff --git a/modules/records/variables.tf b/modules/records/variables.tf deleted file mode 100644 index d3581be..0000000 --- a/modules/records/variables.tf +++ /dev/null @@ -1,35 +0,0 @@ -variable "create" { - description = "Whether to create DNS records" - type = bool - default = true -} - -variable "zone_id" { - description = "ID of DNS zone" - type = string - default = null -} - -variable "zone_name" { - description = "Name of DNS zone" - type = string - default = null -} - -variable "private_zone" { - description = "Whether Route53 zone is private or public" - type = bool - default = false -} - -variable "records" { - description = "List of objects of DNS records" - type = any - default = [] -} - -variable "records_jsonencoded" { - description = "List of map of DNS records (stored as jsonencoded string, for terragrunt)" - type = string - default = null -} diff --git a/modules/resolver-endpoint/README.md b/modules/resolver-endpoint/README.md new file mode 100644 index 0000000..1e8d4d5 --- /dev/null +++ b/modules/resolver-endpoint/README.md @@ -0,0 +1,198 @@ +# AWS Route53 Resolver Endpoint + +Terraform module that creates: +- AWS Route53 Resolver Endpoint +- Security group and rules for port 53 on TCP and UDP + +[![SWUbanner](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner2-direct.svg)](https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md) + +## Usage + +### Inbound Resolver Endpoint + +```hcl +module "resolver_endpoint" { + source = "terraform-aws-modules/route53/aws//modules/resolver-endpoint" + + name = "example-inbound" + direction = "INBOUND" + type = "IPV4" + protocols = ["Do53", "DoH"] + + ip_address = [ + { + subnet_id = "subnet-abcde012" + }, + { + subnet_id = "subnet-bcde012a" + } + ] + + # Security group + vpc_id = "vpc-1234556abcdef" + security_group_ingress_rules = { + one = { + cidr_ipv4 = "10.0.0.0/20" + description = "Allow inbound DNS queries from first subnet" + } + two = { + cidr_ipv4 = "10.0.16.0/20" + description = "Allow inbound DNS queries from second subnet" + } + } + security_group_egress_rules = { + one = { + cidr_ipv4 = "10.0.0.0/20" + description = "Allow outbound DNS responses to first subnet" + } + two = { + cidr_ipv4 = "10.0.16.0/20" + description = "Allow outbound DNS responses to second subnet" + } + } + + tags = { + Environment = "example" + Project = "terraform-aws-route53" + } +} +``` + +### Outbound Resolver Endpoint + +```hcl +module "resolver_endpoint" { + source = "terraform-aws-modules/route53/aws//modules/resolver-endpoint" + + name = "example-inbound" + direction = "OUTBOUND" + type = "IPV4" + protocols = ["Do53", "DoH"] + + ip_address = [ + { + subnet_id = "subnet-abcde012" + }, + { + subnet_id = "subnet-bcde012a" + } + ] + + # Security group + vpc_id = "vpc-1234556abcdef" + security_group_ingress_rules = { + one = { + cidr_ipv4 = "10.0.0.0/20" + description = "Allow inbound DNS queries from first subnet" + } + two = { + cidr_ipv4 = "10.0.16.0/20" + description = "Allow inbound DNS queries from second subnet" + } + } + security_group_egress_rules = { + one = { + cidr_ipv4 = "10.0.0.0/20" + description = "Allow outbound DNS responses to first subnet" + } + two = { + cidr_ipv4 = "10.0.16.0/20" + description = "Allow outbound DNS responses to second subnet" + } + } + + # Resolver Rule(s) & Association(s) + rules = { + ex-forward = { + domain_name = "example.com." + rule_type = "FORWARD" + target_ip = [{ + ip = "123.45.67.89" + }] + # Association + vpc_id = "vpc-1234556abcdef" + } + ex-system = { + domain_name = "system.com." + rule_type = "SYSTEM" + } + } + + tags = { + Environment = "example" + Project = "terraform-aws-route53" + } +} +``` + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [aws](#requirement\_aws) | >= 6.3 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 6.3 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_route53_resolver_endpoint.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_resolver_endpoint) | resource | +| [aws_route53_resolver_rule.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_resolver_rule) | resource | +| [aws_route53_resolver_rule_association.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_resolver_rule_association) | resource | +| [aws_security_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | +| [aws_vpc_security_group_egress_rule.tcp](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_egress_rule) | resource | +| [aws_vpc_security_group_egress_rule.udp](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_egress_rule) | resource | +| [aws_vpc_security_group_ingress_rule.tcp](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_ingress_rule) | resource | +| [aws_vpc_security_group_ingress_rule.udp](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_ingress_rule) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [create](#input\_create) | Determines whether resources will be created (affects all resources) | `bool` | `true` | no | +| [create\_security\_group](#input\_create\_security\_group) | Determines if a security group is created | `bool` | `true` | no | +| [direction](#input\_direction) | Direction of DNS queries to or from the Route 53 Resolver endpoint. Valid values are `INBOUND` (resolver forwards DNS queries to the DNS service for a VPC from your network or another VPC) or `OUTBOUND` (resolver forwards DNS queries from the DNS service for a VPC to your network or another VPC) | `string` | `"INBOUND"` | no | +| [ip\_address](#input\_ip\_address) | Subnets and IP addresses in your VPC that you want DNS queries to pass through on the way from your VPCs to your network (for outbound endpoints) or on the way from your network to your VPCs (for inbound endpoints) |
list(object({
ip = optional(string)
ipv6 = optional(string)
subnet_id = string
}))
| `[]` | no | +| [name](#input\_name) | Friendly name of the Route 53 Resolver endpoint | `string` | `null` | no | +| [protocols](#input\_protocols) | Protocols you want to use for the Route 53 Resolver endpoint. Valid values are `DoH`, `Do53`, or `DoH-FIPS` | `list(string)` | `[]` | no | +| [region](#input\_region) | Region where the resource(s) will be managed. Defaults to the Region set in the provider configuration | `string` | `null` | no | +| [rules](#input\_rules) | A map of Route 53 Resolver rules & associations to create |
map(object({
# Rule
domain_name = string
name = optional(string)
rule_type = string
tags = optional(map(string), {})
target_ip = optional(list(object({
ip = string
ipv6 = optional(string)
port = optional(number)
protocol = optional(string)
})))

# Association
vpc_id = optional(string)
}))
| `{}` | no | +| [security\_group\_description](#input\_security\_group\_description) | Description of the security group created | `string` | `null` | no | +| [security\_group\_egress\_rules](#input\_security\_group\_egress\_rules) | Security group tcp/udp on port 53 egress rules to add to the security group created |
map(object({
name = optional(string)

cidr_ipv4 = optional(string)
cidr_ipv6 = optional(string)
description = optional(string)
prefix_list_id = optional(string)
referenced_security_group_id = optional(string)
tags = optional(map(string), {})
}))
| `{}` | no | +| [security\_group\_ids](#input\_security\_group\_ids) | ID of one or more security groups that you want to use to control access to this VPC | `list(string)` | `[]` | no | +| [security\_group\_ingress\_rules](#input\_security\_group\_ingress\_rules) | Security group tcp/udp on port 53 ingress rules to add to the security group created |
map(object({
name = optional(string)

cidr_ipv4 = optional(string)
cidr_ipv6 = optional(string)
description = optional(string)
prefix_list_id = optional(string)
referenced_security_group_id = optional(string)
tags = optional(map(string), {})
}))
| `{}` | no | +| [security\_group\_name](#input\_security\_group\_name) | Name to use on security group created | `string` | `null` | no | +| [security\_group\_tags](#input\_security\_group\_tags) | A map of additional tags to add to the security group created | `map(string)` | `{}` | no | +| [security\_group\_use\_name\_prefix](#input\_security\_group\_use\_name\_prefix) | Determines whether the security group name (`security_group_name`) is used as a prefix | `bool` | `true` | no | +| [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no | +| [type](#input\_type) | Endpoint IP type. This endpoint type is applied to all IP addresses. Valid values are `IPV6`, `IPV4` or `DUALSTACK` (both IPv4 and IPv6) | `string` | `null` | no | +| [vpc\_id](#input\_vpc\_id) | The VPC ID where the security group will be created | `string` | `null` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [arn](#output\_arn) | The ARN of the Resolver Endpoint | +| [host\_vpc\_id](#output\_host\_vpc\_id) | The VPC ID used by the Resolver Endpoint | +| [id](#output\_id) | The ID of the Resolver Endpoint | +| [ip\_addresses](#output\_ip\_addresses) | Resolver Endpoint IP Addresses | +| [rules](#output\_rules) | Resolver Endpoint Rules created | +| [security\_group\_arn](#output\_security\_group\_arn) | Amazon Resource Name (ARN) of the security group | +| [security\_group\_id](#output\_security\_group\_id) | ID of the security group | +| [security\_group\_ids](#output\_security\_group\_ids) | Security Group IDs mapped to Resolver Endpoint | + + +## License + +Apache 2 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-route53/tree/master/LICENSE) for full details. diff --git a/modules/resolver-endpoint/main.tf b/modules/resolver-endpoint/main.tf new file mode 100644 index 0000000..17d3eef --- /dev/null +++ b/modules/resolver-endpoint/main.tf @@ -0,0 +1,190 @@ +################################################################################ +# Resolver Endpoint +################################################################################ + +resource "aws_route53_resolver_endpoint" "this" { + count = var.create ? 1 : 0 + + region = var.region + + direction = var.direction + + dynamic "ip_address" { + for_each = var.ip_address + + content { + ip = ip_address.value.ip + ipv6 = ip_address.value.ipv6 + subnet_id = ip_address.value.subnet_id + } + } + + name = var.name + resolver_endpoint_type = var.type + security_group_ids = concat(aws_security_group.this[*].id, var.security_group_ids) + protocols = var.protocols + tags = var.tags +} + +################################################################################ +# Security Group +################################################################################ + +locals { + create_security_group = var.create && var.create_security_group + security_group_name = try(coalesce(var.security_group_name, var.name), "") +} + +resource "aws_security_group" "this" { + count = local.create_security_group ? 1 : 0 + + region = var.region + + name = var.security_group_use_name_prefix ? null : local.security_group_name + name_prefix = var.security_group_use_name_prefix ? "${local.security_group_name}-" : null + description = var.security_group_description + vpc_id = var.vpc_id + + tags = merge( + var.tags, + { "Name" = local.security_group_name }, + var.security_group_tags + ) + + lifecycle { + create_before_destroy = true + } +} + +resource "aws_vpc_security_group_ingress_rule" "tcp" { + for_each = { for k, v in var.security_group_ingress_rules : k => v if var.security_group_ingress_rules != null && local.create_security_group } + + region = var.region + + cidr_ipv4 = each.value.cidr_ipv4 + cidr_ipv6 = each.value.cidr_ipv6 + description = each.value.description + from_port = 53 + ip_protocol = "tcp" + prefix_list_id = each.value.prefix_list_id + referenced_security_group_id = each.value.referenced_security_group_id + security_group_id = aws_security_group.this[0].id + tags = merge( + var.tags, + var.security_group_tags, + { "Name" = coalesce(each.value.name, "${local.security_group_name}-${each.key}-tcp") }, + each.value.tags + ) + to_port = 53 +} + +resource "aws_vpc_security_group_ingress_rule" "udp" { + for_each = { for k, v in var.security_group_ingress_rules : k => v if var.security_group_ingress_rules != null && local.create_security_group } + + region = var.region + + cidr_ipv4 = each.value.cidr_ipv4 + cidr_ipv6 = each.value.cidr_ipv6 + description = each.value.description + from_port = 53 + ip_protocol = "udp" + prefix_list_id = each.value.prefix_list_id + referenced_security_group_id = each.value.referenced_security_group_id + security_group_id = aws_security_group.this[0].id + tags = merge( + var.tags, + var.security_group_tags, + { "Name" = coalesce(each.value.name, "${local.security_group_name}-${each.key}-udp") }, + each.value.tags + ) + to_port = 53 +} + +resource "aws_vpc_security_group_egress_rule" "tcp" { + for_each = { for k, v in var.security_group_egress_rules : k => v if var.security_group_egress_rules != null && local.create_security_group } + + region = var.region + + cidr_ipv4 = each.value.cidr_ipv4 + cidr_ipv6 = each.value.cidr_ipv6 + description = each.value.description + from_port = 53 + ip_protocol = "tcp" + prefix_list_id = each.value.prefix_list_id + referenced_security_group_id = each.value.referenced_security_group_id + security_group_id = aws_security_group.this[0].id + tags = merge( + var.tags, + var.security_group_tags, + { "Name" = coalesce(each.value.name, "${local.security_group_name}-${each.key}-tcp") }, + each.value.tags + ) + to_port = 53 +} + +resource "aws_vpc_security_group_egress_rule" "udp" { + for_each = { for k, v in var.security_group_egress_rules : k => v if var.security_group_egress_rules != null && local.create_security_group } + + region = var.region + + cidr_ipv4 = each.value.cidr_ipv4 + cidr_ipv6 = each.value.cidr_ipv6 + description = each.value.description + from_port = 53 + ip_protocol = "udp" + prefix_list_id = each.value.prefix_list_id + referenced_security_group_id = each.value.referenced_security_group_id + security_group_id = aws_security_group.this[0].id + tags = merge( + var.tags, + var.security_group_tags, + { "Name" = coalesce(each.value.name, "${local.security_group_name}-${each.key}-udp") }, + each.value.tags + ) + to_port = 53 +} + +################################################################################ +# Resolver Endpoint Rule(s) +################################################################################ + +resource "aws_route53_resolver_rule" "this" { + for_each = { for k, v in var.rules : k => v if var.create && v.vpc_id != null } + + region = var.region + + domain_name = each.value.domain_name + name = coalesce(each.value.name, each.key) + resolver_endpoint_id = each.value.rule_type == "FORWARD" ? aws_route53_resolver_endpoint.this[0].id : null + rule_type = each.value.rule_type + + dynamic "target_ip" { + for_each = each.value.target_ip != null ? each.value.target_ip : [] + + content { + ip = target_ip.value.ip + ipv6 = target_ip.value.ipv6 + port = target_ip.value.port + protocol = target_ip.value.protocol + } + } + + tags = merge( + var.tags, + each.value.tags + ) +} + +################################################################################ +# Resolver Endpoint Rule Association(s) +################################################################################ + +resource "aws_route53_resolver_rule_association" "this" { + for_each = { for k, v in var.rules : k => v if var.create && v.vpc_id != null } + + region = var.region + + name = coalesce(each.value.name, each.key) + resolver_rule_id = aws_route53_resolver_rule.this[each.key].id + vpc_id = each.value.vpc_id +} diff --git a/modules/resolver-endpoint/outputs.tf b/modules/resolver-endpoint/outputs.tf new file mode 100644 index 0000000..54e54f8 --- /dev/null +++ b/modules/resolver-endpoint/outputs.tf @@ -0,0 +1,51 @@ +################################################################################ +# Resolver Endpoint +################################################################################ + +output "id" { + description = "The ID of the Resolver Endpoint" + value = try(aws_route53_resolver_endpoint.this[0].id, null) +} + +output "arn" { + description = "The ARN of the Resolver Endpoint" + value = try(aws_route53_resolver_endpoint.this[0].arn, null) +} + +output "host_vpc_id" { + description = "The VPC ID used by the Resolver Endpoint" + value = try(aws_route53_resolver_endpoint.this[0].host_vpc_id, null) +} + +output "security_group_ids" { + description = "Security Group IDs mapped to Resolver Endpoint" + value = try(aws_route53_resolver_endpoint.this[0].security_group_ids, null) +} + +output "ip_addresses" { + description = "Resolver Endpoint IP Addresses" + value = try(aws_route53_resolver_endpoint.this[0].ip_address, null) +} + +################################################################################ +# Security Group +################################################################################ + +output "security_group_arn" { + description = "Amazon Resource Name (ARN) of the security group" + value = try(aws_security_group.this[0].arn, null) +} + +output "security_group_id" { + description = "ID of the security group" + value = try(aws_security_group.this[0].id, null) +} + +################################################################################ +# Resolver Endpoint Rule(s) +################################################################################ + +output "rules" { + description = "Resolver Endpoint Rules created" + value = aws_route53_resolver_rule.this +} diff --git a/modules/resolver-endpoint/variables.tf b/modules/resolver-endpoint/variables.tf new file mode 100644 index 0000000..7d84d91 --- /dev/null +++ b/modules/resolver-endpoint/variables.tf @@ -0,0 +1,156 @@ +variable "create" { + description = "Determines whether resources will be created (affects all resources)" + type = bool + default = true +} + +variable "region" { + description = "Region where the resource(s) will be managed. Defaults to the Region set in the provider configuration" + type = string + default = null +} + +variable "tags" { + description = "A map of tags to add to all resources" + type = map(string) + default = {} +} + +################################################################################ +# Resolver Endpoint +################################################################################ + +variable "direction" { + description = " Direction of DNS queries to or from the Route 53 Resolver endpoint. Valid values are `INBOUND` (resolver forwards DNS queries to the DNS service for a VPC from your network or another VPC) or `OUTBOUND` (resolver forwards DNS queries from the DNS service for a VPC to your network or another VPC)" + type = string + default = "INBOUND" +} + +variable "ip_address" { + description = "Subnets and IP addresses in your VPC that you want DNS queries to pass through on the way from your VPCs to your network (for outbound endpoints) or on the way from your network to your VPCs (for inbound endpoints)" + type = list(object({ + ip = optional(string) + ipv6 = optional(string) + subnet_id = string + })) + default = [] +} + +variable "name" { + description = "Friendly name of the Route 53 Resolver endpoint" + type = string + default = null +} + +variable "type" { + description = "Endpoint IP type. This endpoint type is applied to all IP addresses. Valid values are `IPV6`, `IPV4` or `DUALSTACK` (both IPv4 and IPv6)" + type = string + default = null +} + +variable "protocols" { + description = "Protocols you want to use for the Route 53 Resolver endpoint. Valid values are `DoH`, `Do53`, or `DoH-FIPS`" + type = list(string) + default = [] +} + +variable "security_group_ids" { + description = "ID of one or more security groups that you want to use to control access to this VPC" + type = list(string) + default = [] +} + +################################################################################ +# Security Group +################################################################################ + +variable "create_security_group" { + description = "Determines if a security group is created" + type = bool + default = true +} + +variable "security_group_name" { + description = "Name to use on security group created" + type = string + default = null +} + +variable "security_group_use_name_prefix" { + description = "Determines whether the security group name (`security_group_name`) is used as a prefix" + type = bool + default = true +} + +variable "security_group_description" { + description = "Description of the security group created" + type = string + default = null +} + +variable "vpc_id" { + description = "The VPC ID where the security group will be created" + type = string + default = null +} + +variable "security_group_ingress_rules" { + description = "Security group tcp/udp on port 53 ingress rules to add to the security group created" + type = map(object({ + name = optional(string) + + cidr_ipv4 = optional(string) + cidr_ipv6 = optional(string) + description = optional(string) + prefix_list_id = optional(string) + referenced_security_group_id = optional(string) + tags = optional(map(string), {}) + })) + default = {} +} + +variable "security_group_egress_rules" { + description = "Security group tcp/udp on port 53 egress rules to add to the security group created" + type = map(object({ + name = optional(string) + + cidr_ipv4 = optional(string) + cidr_ipv6 = optional(string) + description = optional(string) + prefix_list_id = optional(string) + referenced_security_group_id = optional(string) + tags = optional(map(string), {}) + })) + default = {} +} + +variable "security_group_tags" { + description = "A map of additional tags to add to the security group created" + type = map(string) + default = {} +} + +################################################################################ +# Resolver Endpoint Rule(s) +################################################################################ + +variable "rules" { + description = "A map of Route 53 Resolver rules & associations to create" + type = map(object({ + # Rule + domain_name = string + name = optional(string) + rule_type = string + tags = optional(map(string), {}) + target_ip = optional(list(object({ + ip = string + ipv6 = optional(string) + port = optional(number) + protocol = optional(string) + }))) + + # Association + vpc_id = optional(string) + })) + default = {} +} diff --git a/modules/records/versions.tf b/modules/resolver-endpoint/versions.tf similarity index 61% rename from modules/records/versions.tf rename to modules/resolver-endpoint/versions.tf index 409aebe..fd053a1 100644 --- a/modules/records/versions.tf +++ b/modules/resolver-endpoint/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 1.3.2" + required_version = ">= 1.5.7" required_providers { aws = { source = "hashicorp/aws" - version = ">= 5.91" + version = ">= 6.3" } } } diff --git a/modules/resolver-endpoints/README.md b/modules/resolver-endpoints/README.md deleted file mode 100644 index 90a5658..0000000 --- a/modules/resolver-endpoints/README.md +++ /dev/null @@ -1,61 +0,0 @@ -# Route53 Resolver Endpoints - -This module creates Route53 Resolver Endpoints. - - -## Requirements - -| Name | Version | -|------|---------| -| [terraform](#requirement\_terraform) | >= 1.3.2 | -| [aws](#requirement\_aws) | >= 5.91 | - -## Providers - -| Name | Version | -|------|---------| -| [aws](#provider\_aws) | >= 5.91 | - -## Modules - -No modules. - -## Resources - -| Name | Type | -|------|------| -| [aws_route53_resolver_endpoint.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_resolver_endpoint) | resource | -| [aws_security_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | - -## Inputs - -| Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| -| [create](#input\_create) | Whether to create Route53 resolver endpoints | `bool` | `true` | no | -| [create\_security\_group](#input\_create\_security\_group) | Whether to create Security Groups for Route53 Resolver Endpoints | `bool` | `true` | no | -| [direction](#input\_direction) | The resolver endpoint flow direction | `string` | `"INBOUND"` | no | -| [ip\_address](#input\_ip\_address) | A list of IP addresses and subnets where Route53 resolver endpoints will be deployed | `list(any)` | `[]` | no | -| [name](#input\_name) | The resolver endpoint name | `string` | `null` | no | -| [protocols](#input\_protocols) | The resolver endpoint protocols | `list(string)` | `[]` | no | -| [security\_group\_description](#input\_security\_group\_description) | The security group description | `string` | `null` | no | -| [security\_group\_egress\_cidr\_blocks](#input\_security\_group\_egress\_cidr\_blocks) | A list of CIDR blocks to allow on security group egress rules | `list(string)` | `[]` | no | -| [security\_group\_ids](#input\_security\_group\_ids) | A list of security group IDs | `list(string)` | `[]` | no | -| [security\_group\_ingress\_cidr\_blocks](#input\_security\_group\_ingress\_cidr\_blocks) | A list of CIDR blocks to allow on security group ingress rules | `list(string)` | `[]` | no | -| [security\_group\_name](#input\_security\_group\_name) | The name of the security group | `string` | `null` | no | -| [security\_group\_name\_prefix](#input\_security\_group\_name\_prefix) | The prefix of the security group | `string` | `null` | no | -| [security\_group\_tags](#input\_security\_group\_tags) | A map of tags for the security group | `map(string)` | `{}` | no | -| [subnet\_ids](#input\_subnet\_ids) | A list of subnets where Route53 resolver endpoints will be deployed | `list(any)` | `[]` | no | -| [tags](#input\_tags) | A map of tags for the Route53 resolver endpoint | `map(string)` | `{}` | no | -| [type](#input\_type) | The resolver endpoint IP type | `string` | `"IPV4"` | no | -| [vpc\_id](#input\_vpc\_id) | The VPC ID for all the Route53 Resolver Endpoints | `string` | `""` | no | - -## Outputs - -| Name | Description | -|------|-------------| -| [route53\_resolver\_endpoint\_arn](#output\_route53\_resolver\_endpoint\_arn) | The ARN of the Resolver Endpoint | -| [route53\_resolver\_endpoint\_host\_vpc\_id](#output\_route53\_resolver\_endpoint\_host\_vpc\_id) | The VPC ID used by the Resolver Endpoint | -| [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 deleted file mode 100644 index e1c1258..0000000 --- a/modules/resolver-endpoints/main.tf +++ /dev/null @@ -1,62 +0,0 @@ -locals { - security_group_ids = var.create && var.create_security_group ? [aws_security_group.this[0].id] : var.security_group_ids - subnet_ids = [for subnet in var.subnet_ids : { subnet_id = subnet } if var.create] -} - -resource "aws_route53_resolver_endpoint" "this" { - count = var.create ? 1 : 0 - - name = var.name - direction = var.direction - - resolver_endpoint_type = var.type - security_group_ids = local.security_group_ids - - dynamic "ip_address" { - for_each = length(var.ip_address) == 0 ? local.subnet_ids : var.ip_address - - content { - ip = lookup(ip_address.value, "ip", null) - subnet_id = ip_address.value.subnet_id - } - } - - protocols = var.protocols - - tags = var.tags -} - -resource "aws_security_group" "this" { - count = var.create && var.create_security_group ? 1 : 0 - - name = var.security_group_name_prefix == null ? coalesce(var.security_group_name, var.name) : null - name_prefix = var.security_group_name_prefix - description = var.security_group_description - vpc_id = var.vpc_id - - dynamic "ingress" { - for_each = toset(["tcp", "udp"]) - - content { - description = "Allow DNS" - protocol = ingress.value - from_port = 53 - to_port = 53 - cidr_blocks = var.security_group_ingress_cidr_blocks - } - } - - dynamic "egress" { - for_each = toset(["tcp", "udp"]) - - content { - description = "Allow DNS" - protocol = egress.value - from_port = 53 - to_port = 53 - cidr_blocks = try(var.security_group_egress_cidr_blocks, ["0.0.0.0"]) - } - } - - tags = var.security_group_tags -} diff --git a/modules/resolver-endpoints/outputs.tf b/modules/resolver-endpoints/outputs.tf deleted file mode 100644 index e41d815..0000000 --- a/modules/resolver-endpoints/outputs.tf +++ /dev/null @@ -1,25 +0,0 @@ - -output "route53_resolver_endpoint_id" { - description = "The ID of the Resolver Endpoint" - value = try(aws_route53_resolver_endpoint.this[0].id, null) -} - -output "route53_resolver_endpoint_arn" { - description = "The ARN of the Resolver Endpoint" - value = try(aws_route53_resolver_endpoint.this[0].arn, null) -} - -output "route53_resolver_endpoint_host_vpc_id" { - description = "The VPC ID used by the Resolver Endpoint" - value = try(aws_route53_resolver_endpoint.this[0].host_vpc_id, null) -} - -output "route53_resolver_endpoint_security_group_ids" { - description = "Security Group IDs mapped to Resolver Endpoint" - value = try(aws_route53_resolver_endpoint.this[0].security_group_ids, null) -} - -output "route53_resolver_endpoint_ip_addresses" { - description = "Resolver Endpoint IP Addresses" - value = try(aws_route53_resolver_endpoint.this[0].ip_address, null) -} diff --git a/modules/resolver-endpoints/variables.tf b/modules/resolver-endpoints/variables.tf deleted file mode 100644 index c8cc7be..0000000 --- a/modules/resolver-endpoints/variables.tf +++ /dev/null @@ -1,103 +0,0 @@ -variable "create" { - description = "Whether to create Route53 resolver endpoints" - type = bool - default = true -} - -variable "name" { - description = "The resolver endpoint name" - type = string - default = null -} - -variable "protocols" { - description = "The resolver endpoint protocols" - type = list(string) - default = [] -} - -variable "direction" { - description = "The resolver endpoint flow direction" - type = string - default = "INBOUND" -} - -variable "type" { - description = "The resolver endpoint IP type" - type = string - default = "IPV4" -} - -variable "subnet_ids" { - description = "A list of subnets where Route53 resolver endpoints will be deployed" - type = list(any) - default = [] -} - -variable "ip_address" { - description = "A list of IP addresses and subnets where Route53 resolver endpoints will be deployed" - type = list(any) - default = [] -} - -variable "security_group_ids" { - description = "A list of security group IDs" - type = list(string) - default = [] -} - -variable "tags" { - description = "A map of tags for the Route53 resolver endpoint" - type = map(string) - default = {} -} - -# Security Group - -variable "create_security_group" { - description = "Whether to create Security Groups for Route53 Resolver Endpoints" - type = bool - default = true -} - -variable "vpc_id" { - description = "The VPC ID for all the Route53 Resolver Endpoints" - type = string - default = "" -} - -variable "security_group_name" { - description = "The name of the security group" - type = string - default = null -} - -variable "security_group_name_prefix" { - description = "The prefix of the security group" - type = string - default = null -} - -variable "security_group_description" { - description = "The security group description" - type = string - default = null -} - -variable "security_group_ingress_cidr_blocks" { - description = "A list of CIDR blocks to allow on security group ingress rules" - type = list(string) - default = [] -} - -variable "security_group_egress_cidr_blocks" { - description = "A list of CIDR blocks to allow on security group egress rules" - type = list(string) - default = [] -} - -variable "security_group_tags" { - description = "A map of tags for the security group" - type = map(string) - default = {} -} diff --git a/modules/resolver-firewall-rule-group/README.md b/modules/resolver-firewall-rule-group/README.md new file mode 100644 index 0000000..f839cf8 --- /dev/null +++ b/modules/resolver-firewall-rule-group/README.md @@ -0,0 +1,112 @@ +# AWS Route53 Resolver Firewall Rule Group Terraform Module + +Terraform module that creates: +- AWS Route53 Resolver Firewall Rule Group +- RAM resource association for sharing the rule group with other accounts +- Resolver firewall domain list(s) +- Resolver firewall rule(s) + +[![SWUbanner](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner2-direct.svg)](https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md) + +## Usage + +```hcl +module "resolver_firewall_rule_group" { + source = "terraform-aws-modules/route53/aws//modules/resolver-firewall-rule-group" + + name = "example" + + rules = { + block = { + name = "blockit" + priority = 110 + action = "BLOCK" + block_response = "NODATA" + domains = ["google.com."] + + tags = { rule = true } + } + block_override = { + priority = 120 + action = "BLOCK" + block_response = "OVERRIDE" + block_override_dns_type = "CNAME" + block_override_domain = "example.com" + block_override_ttl = 1 + domains = ["microsoft.com."] + } + # Unfortunately, a data source does not exist yet to pull managed domain lists + # but users can stil copy the managed domain list ID and paste it into the config + # block_managed_domain_list = { + # priority = 135 + # action = "BLOCK" + # block_response = "NODATA" + # domain_list_id = data.aws_route53_resolver_firewall_domain_list.this.id # => "rslvr-fdl-fad18de921a64bfa" + # } + allow = { + priority = 130 + action = "ALLOW" + domains = ["amazon.com.", "amazonaws.com."] + } + } + + tags = { + Environment = "example" + Project = "terraform-aws-route53" + } +} +``` + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [aws](#requirement\_aws) | >= 6.3 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 6.3 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_ram_resource_association.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ram_resource_association) | resource | +| [aws_route53_resolver_firewall_domain_list.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_resolver_firewall_domain_list) | resource | +| [aws_route53_resolver_firewall_rule.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_resolver_firewall_rule) | resource | +| [aws_route53_resolver_firewall_rule_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_resolver_firewall_rule_group) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [create](#input\_create) | Determines whether resources will be created (affects all resources) | `bool` | `true` | no | +| [name](#input\_name) | A name that lets you identify the rule group, to manage and use it | `string` | `""` | no | +| [ram\_resource\_associations](#input\_ram\_resource\_associations) | A map of RAM resource associations for the created rule group |
map(object({
resource_share_arn = string
}))
| `{}` | no | +| [region](#input\_region) | Region where the resource(s) will be managed. Defaults to the Region set in the provider configuration | `string` | `null` | no | +| [rules](#input\_rules) | A map of rule definitions to create |
map(object({
name = optional(string)

# Domain list
domains = optional(list(string))

# Rule
action = string
block_override_dns_type = optional(string)
block_override_domain = optional(string)
block_override_ttl = optional(number)
block_response = optional(string)
firewall_domain_list_id = optional(string)
firewall_domain_redirection_action = optional(string)
priority = number
q_type = optional(string)

tags = optional(map(string), {})
}))
| `{}` | no | +| [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [arn](#output\_arn) | The ARN (Amazon Resource Name) of the rule group | +| [domain\_lists](#output\_domain\_lists) | Map of all domain lists created and their attributes | +| [id](#output\_id) | The ID of the rule group | +| [ram\_resource\_associations](#output\_ram\_resource\_associations) | Map of RAM resource associations created and their attributes | +| [rules](#output\_rules) | Map of all rules created and their attributes | +| [share\_status](#output\_share\_status) | Whether the rule group is shared with other AWS accounts, or was shared with the current account by another AWS account. Sharing is configured through AWS Resource Access Manager (AWS RAM). Valid values: `NOT_SHARED`, `SHARED_BY_ME`, `SHARED_WITH_ME` | + + +## License + +Apache 2 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-route53/tree/master/LICENSE) for full details. diff --git a/modules/resolver-firewall-rule-group/main.tf b/modules/resolver-firewall-rule-group/main.tf new file mode 100644 index 0000000..1f357c4 --- /dev/null +++ b/modules/resolver-firewall-rule-group/main.tf @@ -0,0 +1,66 @@ +################################################################################ +# Firewall Rule Group +################################################################################ + +resource "aws_route53_resolver_firewall_rule_group" "this" { + count = var.create ? 1 : 0 + + region = var.region + + name = var.name + tags = var.tags +} + +################################################################################ +# RAM Resource Association +################################################################################ + +resource "aws_ram_resource_association" "this" { + for_each = { for k, v in var.ram_resource_associations : k => v if var.create } + + region = var.region + + resource_arn = aws_route53_resolver_firewall_rule_group.this[0].arn + resource_share_arn = each.value.resource_share_arn +} + +################################################################################ +# Firewall Domain List +################################################################################ + +resource "aws_route53_resolver_firewall_domain_list" "this" { + # Because there is a 1:1 relationship between domain list and rule group + # association, we have defined them under one map definition + for_each = { for k, v in var.rules : k => v if var.create } + + region = var.region + + domains = each.value.domains + name = coalesce(each.value.name, each.key) + + tags = merge( + var.tags, + each.value.tags, + ) +} + +################################################################################ +# Firewall Rule +################################################################################ + +resource "aws_route53_resolver_firewall_rule" "this" { + for_each = { for k, v in var.rules : k => v if var.create } + + region = var.region + + action = each.value.action + block_override_dns_type = each.value.block_override_dns_type + block_override_domain = each.value.block_override_domain + block_override_ttl = each.value.block_override_ttl + block_response = each.value.block_response + firewall_domain_list_id = try(coalesce(each.value.firewall_domain_list_id, aws_route53_resolver_firewall_domain_list.this[each.key].id), null) + firewall_rule_group_id = aws_route53_resolver_firewall_rule_group.this[0].id + name = coalesce(each.value.name, each.key) + priority = each.value.priority + q_type = each.value.q_type +} diff --git a/modules/resolver-firewall-rule-group/outputs.tf b/modules/resolver-firewall-rule-group/outputs.tf new file mode 100644 index 0000000..eeb4d4f --- /dev/null +++ b/modules/resolver-firewall-rule-group/outputs.tf @@ -0,0 +1,45 @@ +################################################################################ +# Firewall Rule Group +################################################################################ + +output "arn" { + description = "The ARN (Amazon Resource Name) of the rule group" + value = try(aws_route53_resolver_firewall_rule_group.this[0].arn, null) +} + +output "id" { + description = "The ID of the rule group" + value = try(aws_route53_resolver_firewall_rule_group.this[0].id, null) +} + +output "share_status" { + description = "Whether the rule group is shared with other AWS accounts, or was shared with the current account by another AWS account. Sharing is configured through AWS Resource Access Manager (AWS RAM). Valid values: `NOT_SHARED`, `SHARED_BY_ME`, `SHARED_WITH_ME`" + value = try(aws_route53_resolver_firewall_rule_group.this[0].share_status, null) +} + +################################################################################ +# RAM Resource Association +################################################################################ + +output "ram_resource_associations" { + description = "Map of RAM resource associations created and their attributes" + value = aws_ram_resource_association.this +} + +################################################################################ +# Firewall Domain List +################################################################################ + +output "domain_lists" { + description = "Map of all domain lists created and their attributes" + value = aws_route53_resolver_firewall_domain_list.this +} + +################################################################################ +# Firewall Rule +################################################################################ + +output "rules" { + description = "Map of all rules created and their attributes" + value = aws_route53_resolver_firewall_rule.this +} diff --git a/modules/resolver-firewall-rule-group/variables.tf b/modules/resolver-firewall-rule-group/variables.tf new file mode 100644 index 0000000..f81e48b --- /dev/null +++ b/modules/resolver-firewall-rule-group/variables.tf @@ -0,0 +1,67 @@ +variable "create" { + description = "Determines whether resources will be created (affects all resources)" + type = bool + default = true +} + +variable "region" { + description = "Region where the resource(s) will be managed. Defaults to the Region set in the provider configuration" + type = string + default = null +} + +variable "tags" { + description = "A map of tags to add to all resources" + type = map(string) + default = {} +} + +################################################################################ +# Firewall Rule Group +################################################################################ + +variable "name" { + description = "A name that lets you identify the rule group, to manage and use it" + type = string + default = "" +} + +################################################################################ +# RAM Resource Association +################################################################################ + +variable "ram_resource_associations" { + description = "A map of RAM resource associations for the created rule group" + type = map(object({ + resource_share_arn = string + })) + default = {} +} + +################################################################################ +# Firewall Rules +################################################################################ + +variable "rules" { + description = "A map of rule definitions to create" + type = map(object({ + name = optional(string) + + # Domain list + domains = optional(list(string)) + + # Rule + action = string + block_override_dns_type = optional(string) + block_override_domain = optional(string) + block_override_ttl = optional(number) + block_response = optional(string) + firewall_domain_list_id = optional(string) + firewall_domain_redirection_action = optional(string) + priority = number + q_type = optional(string) + + tags = optional(map(string), {}) + })) + default = {} +} diff --git a/modules/resolver-endpoints/versions.tf b/modules/resolver-firewall-rule-group/versions.tf similarity index 61% rename from modules/resolver-endpoints/versions.tf rename to modules/resolver-firewall-rule-group/versions.tf index 409aebe..fd053a1 100644 --- a/modules/resolver-endpoints/versions.tf +++ b/modules/resolver-firewall-rule-group/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 1.3.2" + required_version = ">= 1.5.7" required_providers { aws = { source = "hashicorp/aws" - version = ">= 5.91" + version = ">= 6.3" } } } diff --git a/modules/resolver-rule-associations/README.md b/modules/resolver-rule-associations/README.md deleted file mode 100644 index 7a08d93..0000000 --- a/modules/resolver-rule-associations/README.md +++ /dev/null @@ -1,68 +0,0 @@ -# Route53 Resolver Rule Associations - -This module creates Route53 resolver rule associations. - -## Usage - -### Create Route53 Resolver rule associations - -```hcl -module "resolver_rule_associations" { - source = "terraform-aws-modules/route53/aws//modules/resolver-rule-associations" - version = "~> 2.0" - - vpc_id = "vpc-185a3e2f2d6d2c863" - - resolver_rule_associations = { - foo = { - resolver_rule_id = "rslvr-rr-2d3e8e42eea14f20a" - }, - bar = { - name = "bar" - resolver_rule_id = "rslvr-rr-2d3e8e42eea14f20a" - vpc_id = "vpc-285a3e2f2d6d2c863" - }, - } -} -``` - - -## Requirements - -| Name | Version | -|------|---------| -| [terraform](#requirement\_terraform) | >= 1.3.2 | -| [aws](#requirement\_aws) | >= 5.91 | - -## Providers - -| Name | Version | -|------|---------| -| [aws](#provider\_aws) | >= 5.91 | - -## Modules - -No modules. - -## Resources - -| Name | Type | -|------|------| -| [aws_route53_resolver_rule_association.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_resolver_rule_association) | resource | - -## Inputs - -| Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| -| [create](#input\_create) | Whether to create Route53 Resolver rule associations | `bool` | `true` | no | -| [resolver\_rule\_associations](#input\_resolver\_rule\_associations) | Map of Route53 Resolver rule associations parameters | `any` | `{}` | no | -| [vpc\_id](#input\_vpc\_id) | Default VPC ID for all the Route53 Resolver rule associations | `string` | `null` | no | - -## Outputs - -| Name | Description | -|------|-------------| -| [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/resolver-rule-associations/main.tf b/modules/resolver-rule-associations/main.tf deleted file mode 100644 index ad81109..0000000 --- a/modules/resolver-rule-associations/main.tf +++ /dev/null @@ -1,7 +0,0 @@ -resource "aws_route53_resolver_rule_association" "this" { - for_each = { for k, v in var.resolver_rule_associations : k => v if var.create } - - name = try(each.value.name, null) - vpc_id = try(each.value.vpc_id, var.vpc_id) - resolver_rule_id = each.value.resolver_rule_id -} diff --git a/modules/resolver-rule-associations/outputs.tf b/modules/resolver-rule-associations/outputs.tf deleted file mode 100644 index 02521b3..0000000 --- a/modules/resolver-rule-associations/outputs.tf +++ /dev/null @@ -1,14 +0,0 @@ -output "route53_resolver_rule_association_id" { - description = "ID of Route53 Resolver rule associations" - value = { for k, v in aws_route53_resolver_rule_association.this : k => v.id } -} - -output "route53_resolver_rule_association_name" { - description = "Name of Route53 Resolver rule associations" - value = { for k, v in aws_route53_resolver_rule_association.this : k => v.name } -} - -output "route53_resolver_rule_association_resolver_rule_id" { - description = "ID of Route53 Resolver rule associations resolver rule" - value = { for k, v in aws_route53_resolver_rule_association.this : k => v.resolver_rule_id } -} diff --git a/modules/resolver-rule-associations/variables.tf b/modules/resolver-rule-associations/variables.tf deleted file mode 100644 index 47a674a..0000000 --- a/modules/resolver-rule-associations/variables.tf +++ /dev/null @@ -1,17 +0,0 @@ -variable "create" { - description = "Whether to create Route53 Resolver rule associations" - type = bool - default = true -} - -variable "vpc_id" { - description = "Default VPC ID for all the Route53 Resolver rule associations" - type = string - default = null -} - -variable "resolver_rule_associations" { - description = "Map of Route53 Resolver rule associations parameters" - type = any - default = {} -} diff --git a/modules/zone-cross-account-vpc-association/README.md b/modules/zone-cross-account-vpc-association/README.md deleted file mode 100644 index 5139511..0000000 --- a/modules/zone-cross-account-vpc-association/README.md +++ /dev/null @@ -1,78 +0,0 @@ -# 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) | >= 5.91 | - -## Providers - -| Name | Version | -|------|---------| -| [aws.r53\_owner](#provider\_aws.r53\_owner) | >= 5.91 | -| [aws.vpc\_owner](#provider\_aws.vpc\_owner) | >= 5.91 | - -## 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 deleted file mode 100644 index 761b289..0000000 --- a/modules/zone-cross-account-vpc-association/main.tf +++ /dev/null @@ -1,19 +0,0 @@ -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 deleted file mode 100644 index 1871993..0000000 --- a/modules/zone-cross-account-vpc-association/outputs.tf +++ /dev/null @@ -1,14 +0,0 @@ -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 deleted file mode 100644 index 21f8df6..0000000 --- a/modules/zone-cross-account-vpc-association/variables.tf +++ /dev/null @@ -1,15 +0,0 @@ -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 deleted file mode 100644 index 502a53f..0000000 --- a/modules/zone-cross-account-vpc-association/versions.tf +++ /dev/null @@ -1,19 +0,0 @@ -terraform { - required_version = ">= 1.3.2" - - required_providers { - aws = { - source = "hashicorp/aws" - version = ">= 5.91" - 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 deleted file mode 100644 index 64aacbf..0000000 --- a/modules/zones/README.md +++ /dev/null @@ -1,47 +0,0 @@ -# Route53 Zones - -This module creates Route53 zones. - - -## Requirements - -| Name | Version | -|------|---------| -| [terraform](#requirement\_terraform) | >= 1.3.2 | -| [aws](#requirement\_aws) | >= 5.91 | - -## Providers - -| Name | Version | -|------|---------| -| [aws](#provider\_aws) | >= 5.91 | - -## Modules - -No modules. - -## Resources - -| Name | Type | -|------|------| -| [aws_route53_zone.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_zone) | resource | - -## Inputs - -| Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| -| [create](#input\_create) | Whether to create Route53 zone | `bool` | `true` | no | -| [tags](#input\_tags) | Tags added to all zones. Will take precedence over tags from the 'zones' variable | `map(any)` | `{}` | no | -| [zones](#input\_zones) | Map of Route53 zone parameters | `any` | `{}` | no | - -## Outputs - -| Name | Description | -|------|-------------| -| [primary\_name\_server](#output\_primary\_name\_server) | The Route 53 name server that created the SOA record. | -| [route53\_static\_zone\_name](#output\_route53\_static\_zone\_name) | Name of Route53 zone created statically to avoid invalid count argument error when creating records and zones simmultaneously | -| [route53\_zone\_name](#output\_route53\_zone\_name) | Name of Route53 zone | -| [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/modules/zones/main.tf b/modules/zones/main.tf deleted file mode 100644 index c87a405..0000000 --- a/modules/zones/main.tf +++ /dev/null @@ -1,33 +0,0 @@ -resource "aws_route53_zone" "this" { - for_each = { for k, v in var.zones : k => v if var.create } - - name = lookup(each.value, "domain_name", each.key) - comment = lookup(each.value, "comment", null) - force_destroy = lookup(each.value, "force_destroy", false) - - delegation_set_id = lookup(each.value, "delegation_set_id", null) - - dynamic "vpc" { - for_each = try(tolist(lookup(each.value, "vpc", [])), [lookup(each.value, "vpc", {})]) - - content { - vpc_id = vpc.value.vpc_id - vpc_region = lookup(vpc.value, "vpc_region", null) - } - } - - tags = merge( - lookup(each.value, "tags", {}), - var.tags - ) - - dynamic "timeouts" { - for_each = try([each.value.timeouts], []) - - content { - create = try(timeouts.value.create, null) - update = try(timeouts.value.update, null) - delete = try(timeouts.value.delete, null) - } - } -} diff --git a/modules/zones/outputs.tf b/modules/zones/outputs.tf deleted file mode 100644 index 5df8ec5..0000000 --- a/modules/zones/outputs.tf +++ /dev/null @@ -1,29 +0,0 @@ -output "route53_zone_zone_id" { - description = "Zone ID of Route53 zone" - value = { for k, v in aws_route53_zone.this : k => v.zone_id } -} - -output "route53_zone_zone_arn" { - description = "Zone ARN of Route53 zone" - value = { for k, v in aws_route53_zone.this : k => v.arn } -} - -output "route53_zone_name_servers" { - description = "Name servers of Route53 zone" - value = { for k, v in aws_route53_zone.this : k => v.name_servers } -} - -output "primary_name_server" { - description = "The Route 53 name server that created the SOA record." - value = { for k, v in aws_route53_zone.this : k => v.primary_name_server } -} - -output "route53_zone_name" { - description = "Name of Route53 zone" - value = { for k, v in aws_route53_zone.this : k => v.name } -} - -output "route53_static_zone_name" { - description = "Name of Route53 zone created statically to avoid invalid count argument error when creating records and zones simmultaneously" - value = { for k, v in var.zones : k => lookup(v, "domain_name", k) if var.create } -} diff --git a/modules/zones/variables.tf b/modules/zones/variables.tf deleted file mode 100644 index f2e0eff..0000000 --- a/modules/zones/variables.tf +++ /dev/null @@ -1,17 +0,0 @@ -variable "create" { - description = "Whether to create Route53 zone" - type = bool - default = true -} - -variable "zones" { - description = "Map of Route53 zone parameters" - type = any - default = {} -} - -variable "tags" { - description = "Tags added to all zones. Will take precedence over tags from the 'zones' variable" - type = map(any) - default = {} -} diff --git a/outputs.tf b/outputs.tf new file mode 100644 index 0000000..fde010e --- /dev/null +++ b/outputs.tf @@ -0,0 +1,92 @@ +################################################################################ +# Zone +################################################################################ + +output "id" { + description = "Zone ID of Route53 zone" + value = local.zone_id +} + +output "arn" { + description = "Zone ARN of Route53 zone" + value = try(aws_route53_zone.this[0].arn, data.aws_route53_zone.this[0].arn, null) +} + +output "name_servers" { + description = "Name servers of Route53 zone" + value = try(aws_route53_zone.this[0].name_servers, data.aws_route53_zone.this[0].name_servers, null) +} + +output "primary_name_server" { + description = "The Route 53 name server that created the SOA record." + value = try(aws_route53_zone.this[0].primary_name_server, data.aws_route53_zone.this[0].primary_name_server, null) +} + +output "name" { + description = "Name of Route53 zone" + value = local.zone_name +} + +################################################################################ +# DNSSEC +################################################################################ + +output "dnssec_signing_key_digest_value" { + description = "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" + value = try(aws_route53_key_signing_key.this[0].digest_value, null) +} + +output "dnssec_signing_key_dnskey_record" { + description = "A string that represents a DNSKEY record" + value = try(aws_route53_key_signing_key.this[0].dnskey_record, null) +} + +output "dnssec_signing_key_ds_record" { + description = "A string that represents a delegation signer (DS) record" + value = try(aws_route53_key_signing_key.this[0].ds_record, null) +} + +output "dnssec_signing_key_id" { + description = "Route 53 Hosted Zone identifier and KMS Key identifier, separated by a comma (`,`)" + value = try(aws_route53_key_signing_key.this[0].id, null) +} + +output "dnssec_signing_key_tag" { + description = "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" + value = try(aws_route53_key_signing_key.this[0].key_tag, null) +} + +output "dnssec_signing_key_public_key" { + description = "The public key, represented as a Base64 encoding, as required by RFC-4034 Page 5" + value = try(aws_route53_key_signing_key.this[0].public_key, null) +} + +# KMS key +output "dnssec_kms_key_arn" { + description = "The Amazon Resource Name (ARN) of the key" + value = module.route53_dnssec_kms.key_arn +} + +output "dnssec_kms_key_id" { + description = "The globally unique identifier for the key" + value = module.route53_dnssec_kms.key_id +} + +output "dnssec_kms_key_region" { + description = "The region for the key" + value = module.route53_dnssec_kms.key_region +} + +output "dnssec_kms_key_policy" { + description = "The IAM resource policy set on the key" + value = module.route53_dnssec_kms.key_policy +} + +################################################################################ +# Records +################################################################################ + +output "records" { + description = "Records created in the Route53 zone" + value = aws_route53_zone.this +} diff --git a/terragrunt_59.md b/terragrunt_59.md deleted file mode 100644 index 629ff3b..0000000 --- a/terragrunt_59.md +++ /dev/null @@ -1,32 +0,0 @@ -# Notes for issue 59 and PR 72 - -https://github.com/terraform-aws-modules/terraform-aws-route53/pull/72 -https://github.com/terraform-aws-modules/terraform-aws-route53/pull/64 - -``` -######################################################################################## -# IF YOU ARE READING THIS AND WANT TO TRY FIXING IT, PLEASE INCREASE THE COUNTER BELOW! -######################################################################################## -# number_of_hours_spent_on_issue_59 = 22 -######################################################################################## -``` - -## Here is the short explanation of the problem. -This module can be called by TF and TG, so it has to support these 3 cases: -1. Terraform with "known values" (static values) -2. Terraform with "(known after apply)" (module.s3_bucket, module.cloudfront, etc) -3. Terragrunt with "known values" - (TG always resolves values before calling Terraform). TG wraps values with `jsonencode()`. -4. Terragrunt with "unknown values"/"(known after apply)" - this is not possible with TG. - -## Considerations: - -1. Try not to change variable types very much. `type = any` is prefered. -2. Keep in mind different behaviour when `records` has different length, `records` or/and `alias` is specified. -3. The same name of resources (aws_route53_record.this) should be used for both TF and TG. -4. If necessary, use Terraform 1.0 as a minimum version (not newer). Terraform 0.13 can be a history, if necessary for this fix to work. - -## Success criteria: - -1. `terraform apply` in `examples/complete` should work without `-target` -2. `records` should include records, alias, references to `module.s3_bucket` in `name`. -3. At least, modules `zones`, `records`, and `terragrunt` should be enabled (not commented) diff --git a/variables.tf b/variables.tf new file mode 100644 index 0000000..be770a7 --- /dev/null +++ b/variables.tf @@ -0,0 +1,186 @@ +variable "create" { + description = "Whether to create Route53 zone" + type = bool + default = true +} + +variable "tags" { + description = "Tags added to all zones. Will take precedence over tags from the 'zones' variable" + type = map(string) + default = {} +} + +################################################################################ +# Zone +################################################################################ + +variable "create_zone" { + description = "Determines whether to create the Route53 zone or lookup an existing zone" + type = bool + default = true +} + +variable "private_zone" { + description = "Whether the hosted zone is private. Only applicable when `create_zone = false`" + type = bool + default = false +} + +variable "vpc_id" { + description = "The ID of the VPC associated with the existing hosted zone. Only applicable when `create_zone = false`" + type = string + default = null +} + +variable "comment" { + description = "A comment for the hosted zone. Defaults to `Managed by Terraform`" + type = string + default = null +} + +variable "delegation_set_id" { + description = "The ID of the reusable delegation set whose NS records you want to assign to the hosted zone. Conflicts with vpc as delegation sets can only be used for public zones" + type = string + default = null +} + +variable "force_destroy" { + description = "Whether to destroy all records (possibly managed outside of Terraform) in the zone when destroying the zone" + type = bool + default = null +} + +variable "name" { + description = "This is the name of the hosted zone" + type = string + default = "" +} + +variable "vpc" { + description = " Configuration block(s) specifying VPC(s) to associate with a private hosted zone. Conflicts with the delegation_set_id argument in this resource and any aws_route53_zone_association resource specifying the same zone ID" + type = map(object({ + vpc_id = string + vpc_region = optional(string) + })) + default = null +} + +variable "timeouts" { + description = "Timeouts for the Route53 zone operations" + type = object({ + create = optional(string) + update = optional(string) + delete = optional(string) + }) + default = null +} + +################################################################################ +# VPC Association Authorization +################################################################################ + +variable "vpc_association_authorizations" { + description = "A map of VPC association authorizations to create for the Route53 zone" + type = map(object({ + vpc_id = string + vpc_region = optional(string) + })) + default = null +} + +################################################################################ +# DNSSEC +################################################################################ + +variable "enable_dnssec" { + description = "Whether to enable DNSSEC for the Route53 zone" + type = bool + default = false +} + +variable "create_dnssec_kms_key" { + description = "Whether to create a KMS key for DNSSEC signing" + type = bool + default = true +} + +variable "dnssec_kms_key_arn" { + description = "The ARN of the KMS key to use for DNSSEC signing. Required when `create_dnssec_kms_key` is `false`" + type = string + default = null +} + +variable "dnssec_kms_key_description" { + description = "The description of the key as viewed in AWS console" + type = string + default = "Route53 DNSSEC KMS Key" +} + +variable "dnssec_kms_key_aliases" { + description = "A list of aliases to create. Note - due to the use of `toset()`, values must be static strings and not computed values" + type = list(string) + default = [] +} + +variable "dnssec_kms_key_tags" { + description = "Additional tags to apply to the KMS key created for DNSSEC signing" + type = map(string) + default = {} +} + +################################################################################ +# Records +################################################################################ + +variable "records" { + description = "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" + type = map(object({ + alias = optional(object({ + evaluate_target_health = optional(bool, false) + name = string + zone_id = string + })) + allow_overwrite = optional(bool) + cidr_routing_policy = optional(object({ + collection_id = string + location_name = string + })) + failover_routing_policy = optional(object({ + type = string + })) + geolocation_routing_policy = optional(object({ + continent = optional(string) + country = optional(string) + subdivision = optional(string) + })) + geoproximity_routing_policy = optional(object({ + aws_region = optional(string) + bias = optional(number) + coordinates = optional(list(object({ + latitude = number + longitude = number + }))) + local_zone_group = optional(string) + })) + health_check_id = optional(string) + latency_routing_policy = optional(object({ + region = string + })) + multivalue_answer_routing_policy = optional(bool) + name = optional(string) + full_name = optional(string) + records = optional(list(string)) + set_identifier = optional(string) + ttl = optional(number) + type = string + weighted_routing_policy = optional(object({ + weight = number + })) + timeouts = optional(object({ + create = optional(string) + update = optional(string) + delete = optional(string) + })) + })) + default = {} +} diff --git a/modules/zones/versions.tf b/versions.tf similarity index 61% rename from modules/zones/versions.tf rename to versions.tf index 409aebe..fd053a1 100644 --- a/modules/zones/versions.tf +++ b/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 1.3.2" + required_version = ">= 1.5.7" required_providers { aws = { source = "hashicorp/aws" - version = ">= 5.91" + version = ">= 6.3" } } } diff --git a/wrappers/README.md b/wrappers/README.md new file mode 100644 index 0000000..37be4d1 --- /dev/null +++ b/wrappers/README.md @@ -0,0 +1,100 @@ +# Wrapper for the root module + +The configuration in this directory contains an implementation of a single module wrapper pattern, which allows managing several copies of a module in places where using the native Terraform 0.13+ `for_each` feature is not feasible (e.g., with Terragrunt). + +You may want to use a single Terragrunt configuration file to manage multiple resources without duplicating `terragrunt.hcl` files for each copy of the same module. + +This wrapper does not implement any extra functionality. + +## Usage with Terragrunt + +`terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/route53/aws//wrappers" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-route53.git//wrappers?ref=master" +} + +inputs = { + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Usage with Terraform + +```hcl +module "wrapper" { + source = "terraform-aws-modules/route53/aws//wrappers" + + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Example: Manage multiple S3 buckets in one Terragrunt layer + +`eu-west-1/s3-buckets/terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/s3-bucket/aws//wrappers" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git//wrappers?ref=master" +} + +inputs = { + defaults = { + force_destroy = true + + attach_elb_log_delivery_policy = true + attach_lb_log_delivery_policy = true + attach_deny_insecure_transport_policy = true + attach_require_latest_tls_policy = true + } + + items = { + bucket1 = { + bucket = "my-random-bucket-1" + } + bucket2 = { + bucket = "my-random-bucket-2" + tags = { + Secure = "probably" + } + } + } +} +``` diff --git a/wrappers/delegation-sets/README.md b/wrappers/delegation-sets/README.md new file mode 100644 index 0000000..69213bc --- /dev/null +++ b/wrappers/delegation-sets/README.md @@ -0,0 +1,100 @@ +# Wrapper for module: `modules/delegation-sets` + +The configuration in this directory contains an implementation of a single module wrapper pattern, which allows managing several copies of a module in places where using the native Terraform 0.13+ `for_each` feature is not feasible (e.g., with Terragrunt). + +You may want to use a single Terragrunt configuration file to manage multiple resources without duplicating `terragrunt.hcl` files for each copy of the same module. + +This wrapper does not implement any extra functionality. + +## Usage with Terragrunt + +`terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/route53/aws//wrappers/delegation-sets" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-route53.git//wrappers/delegation-sets?ref=master" +} + +inputs = { + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Usage with Terraform + +```hcl +module "wrapper" { + source = "terraform-aws-modules/route53/aws//wrappers/delegation-sets" + + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Example: Manage multiple S3 buckets in one Terragrunt layer + +`eu-west-1/s3-buckets/terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/s3-bucket/aws//wrappers" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git//wrappers?ref=master" +} + +inputs = { + defaults = { + force_destroy = true + + attach_elb_log_delivery_policy = true + attach_lb_log_delivery_policy = true + attach_deny_insecure_transport_policy = true + attach_require_latest_tls_policy = true + } + + items = { + bucket1 = { + bucket = "my-random-bucket-1" + } + bucket2 = { + bucket = "my-random-bucket-2" + tags = { + Secure = "probably" + } + } + } +} +``` diff --git a/wrappers/delegation-sets/main.tf b/wrappers/delegation-sets/main.tf new file mode 100644 index 0000000..49a0246 --- /dev/null +++ b/wrappers/delegation-sets/main.tf @@ -0,0 +1,8 @@ +module "wrapper" { + source = "../../modules/delegation-sets" + + for_each = var.items + + create = try(each.value.create, var.defaults.create, true) + delegation_sets = try(each.value.delegation_sets, var.defaults.delegation_sets, {}) +} diff --git a/wrappers/delegation-sets/outputs.tf b/wrappers/delegation-sets/outputs.tf new file mode 100644 index 0000000..ec6da5f --- /dev/null +++ b/wrappers/delegation-sets/outputs.tf @@ -0,0 +1,5 @@ +output "wrapper" { + description = "Map of outputs of a wrapper." + value = module.wrapper + # sensitive = false # No sensitive module output found +} diff --git a/wrappers/delegation-sets/variables.tf b/wrappers/delegation-sets/variables.tf new file mode 100644 index 0000000..a6ea096 --- /dev/null +++ b/wrappers/delegation-sets/variables.tf @@ -0,0 +1,11 @@ +variable "defaults" { + description = "Map of default values which will be used for each item." + type = any + default = {} +} + +variable "items" { + description = "Maps of items to create a wrapper from. Values are passed through to the module." + type = any + default = {} +} diff --git a/modules/resolver-rule-associations/versions.tf b/wrappers/delegation-sets/versions.tf similarity index 61% rename from modules/resolver-rule-associations/versions.tf rename to wrappers/delegation-sets/versions.tf index 409aebe..fd053a1 100644 --- a/modules/resolver-rule-associations/versions.tf +++ b/wrappers/delegation-sets/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 1.3.2" + required_version = ">= 1.5.7" required_providers { aws = { source = "hashicorp/aws" - version = ">= 5.91" + version = ">= 6.3" } } } diff --git a/wrappers/main.tf b/wrappers/main.tf new file mode 100644 index 0000000..b62ffa1 --- /dev/null +++ b/wrappers/main.tf @@ -0,0 +1,25 @@ +module "wrapper" { + source = "../" + + for_each = var.items + + comment = try(each.value.comment, var.defaults.comment, null) + create = try(each.value.create, var.defaults.create, true) + create_dnssec_kms_key = try(each.value.create_dnssec_kms_key, var.defaults.create_dnssec_kms_key, true) + create_zone = try(each.value.create_zone, var.defaults.create_zone, true) + delegation_set_id = try(each.value.delegation_set_id, var.defaults.delegation_set_id, null) + dnssec_kms_key_aliases = try(each.value.dnssec_kms_key_aliases, var.defaults.dnssec_kms_key_aliases, []) + dnssec_kms_key_arn = try(each.value.dnssec_kms_key_arn, var.defaults.dnssec_kms_key_arn, null) + dnssec_kms_key_description = try(each.value.dnssec_kms_key_description, var.defaults.dnssec_kms_key_description, "Route53 DNSSEC KMS Key") + dnssec_kms_key_tags = try(each.value.dnssec_kms_key_tags, var.defaults.dnssec_kms_key_tags, {}) + enable_dnssec = try(each.value.enable_dnssec, var.defaults.enable_dnssec, false) + force_destroy = try(each.value.force_destroy, var.defaults.force_destroy, null) + name = try(each.value.name, var.defaults.name, "") + private_zone = try(each.value.private_zone, var.defaults.private_zone, false) + records = try(each.value.records, var.defaults.records, {}) + tags = try(each.value.tags, var.defaults.tags, {}) + timeouts = try(each.value.timeouts, var.defaults.timeouts, null) + vpc = try(each.value.vpc, var.defaults.vpc, null) + vpc_association_authorizations = try(each.value.vpc_association_authorizations, var.defaults.vpc_association_authorizations, null) + vpc_id = try(each.value.vpc_id, var.defaults.vpc_id, null) +} diff --git a/wrappers/outputs.tf b/wrappers/outputs.tf new file mode 100644 index 0000000..ec6da5f --- /dev/null +++ b/wrappers/outputs.tf @@ -0,0 +1,5 @@ +output "wrapper" { + description = "Map of outputs of a wrapper." + value = module.wrapper + # sensitive = false # No sensitive module output found +} diff --git a/wrappers/resolver-endpoint/README.md b/wrappers/resolver-endpoint/README.md new file mode 100644 index 0000000..bf58aed --- /dev/null +++ b/wrappers/resolver-endpoint/README.md @@ -0,0 +1,100 @@ +# Wrapper for module: `modules/resolver-endpoint` + +The configuration in this directory contains an implementation of a single module wrapper pattern, which allows managing several copies of a module in places where using the native Terraform 0.13+ `for_each` feature is not feasible (e.g., with Terragrunt). + +You may want to use a single Terragrunt configuration file to manage multiple resources without duplicating `terragrunt.hcl` files for each copy of the same module. + +This wrapper does not implement any extra functionality. + +## Usage with Terragrunt + +`terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/route53/aws//wrappers/resolver-endpoint" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-route53.git//wrappers/resolver-endpoint?ref=master" +} + +inputs = { + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Usage with Terraform + +```hcl +module "wrapper" { + source = "terraform-aws-modules/route53/aws//wrappers/resolver-endpoint" + + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Example: Manage multiple S3 buckets in one Terragrunt layer + +`eu-west-1/s3-buckets/terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/s3-bucket/aws//wrappers" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git//wrappers?ref=master" +} + +inputs = { + defaults = { + force_destroy = true + + attach_elb_log_delivery_policy = true + attach_lb_log_delivery_policy = true + attach_deny_insecure_transport_policy = true + attach_require_latest_tls_policy = true + } + + items = { + bucket1 = { + bucket = "my-random-bucket-1" + } + bucket2 = { + bucket = "my-random-bucket-2" + tags = { + Secure = "probably" + } + } + } +} +``` diff --git a/wrappers/resolver-endpoint/main.tf b/wrappers/resolver-endpoint/main.tf new file mode 100644 index 0000000..9387beb --- /dev/null +++ b/wrappers/resolver-endpoint/main.tf @@ -0,0 +1,24 @@ +module "wrapper" { + source = "../../modules/resolver-endpoint" + + for_each = var.items + + create = try(each.value.create, var.defaults.create, true) + create_security_group = try(each.value.create_security_group, var.defaults.create_security_group, true) + direction = try(each.value.direction, var.defaults.direction, "INBOUND") + ip_address = try(each.value.ip_address, var.defaults.ip_address, []) + name = try(each.value.name, var.defaults.name, null) + protocols = try(each.value.protocols, var.defaults.protocols, []) + region = try(each.value.region, var.defaults.region, null) + rules = try(each.value.rules, var.defaults.rules, {}) + security_group_description = try(each.value.security_group_description, var.defaults.security_group_description, null) + security_group_egress_rules = try(each.value.security_group_egress_rules, var.defaults.security_group_egress_rules, {}) + security_group_ids = try(each.value.security_group_ids, var.defaults.security_group_ids, []) + security_group_ingress_rules = try(each.value.security_group_ingress_rules, var.defaults.security_group_ingress_rules, {}) + security_group_name = try(each.value.security_group_name, var.defaults.security_group_name, null) + security_group_tags = try(each.value.security_group_tags, var.defaults.security_group_tags, {}) + security_group_use_name_prefix = try(each.value.security_group_use_name_prefix, var.defaults.security_group_use_name_prefix, true) + tags = try(each.value.tags, var.defaults.tags, {}) + type = try(each.value.type, var.defaults.type, null) + vpc_id = try(each.value.vpc_id, var.defaults.vpc_id, null) +} diff --git a/wrappers/resolver-endpoint/outputs.tf b/wrappers/resolver-endpoint/outputs.tf new file mode 100644 index 0000000..ec6da5f --- /dev/null +++ b/wrappers/resolver-endpoint/outputs.tf @@ -0,0 +1,5 @@ +output "wrapper" { + description = "Map of outputs of a wrapper." + value = module.wrapper + # sensitive = false # No sensitive module output found +} diff --git a/wrappers/resolver-endpoint/variables.tf b/wrappers/resolver-endpoint/variables.tf new file mode 100644 index 0000000..a6ea096 --- /dev/null +++ b/wrappers/resolver-endpoint/variables.tf @@ -0,0 +1,11 @@ +variable "defaults" { + description = "Map of default values which will be used for each item." + type = any + default = {} +} + +variable "items" { + description = "Maps of items to create a wrapper from. Values are passed through to the module." + type = any + default = {} +} diff --git a/wrappers/resolver-endpoint/versions.tf b/wrappers/resolver-endpoint/versions.tf new file mode 100644 index 0000000..fd053a1 --- /dev/null +++ b/wrappers/resolver-endpoint/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.5.7" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 6.3" + } + } +} diff --git a/wrappers/resolver-firewall-rule-group/README.md b/wrappers/resolver-firewall-rule-group/README.md new file mode 100644 index 0000000..83656c0 --- /dev/null +++ b/wrappers/resolver-firewall-rule-group/README.md @@ -0,0 +1,100 @@ +# Wrapper for module: `modules/resolver-firewall-rule-group` + +The configuration in this directory contains an implementation of a single module wrapper pattern, which allows managing several copies of a module in places where using the native Terraform 0.13+ `for_each` feature is not feasible (e.g., with Terragrunt). + +You may want to use a single Terragrunt configuration file to manage multiple resources without duplicating `terragrunt.hcl` files for each copy of the same module. + +This wrapper does not implement any extra functionality. + +## Usage with Terragrunt + +`terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/route53/aws//wrappers/resolver-firewall-rule-group" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-route53.git//wrappers/resolver-firewall-rule-group?ref=master" +} + +inputs = { + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Usage with Terraform + +```hcl +module "wrapper" { + source = "terraform-aws-modules/route53/aws//wrappers/resolver-firewall-rule-group" + + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Example: Manage multiple S3 buckets in one Terragrunt layer + +`eu-west-1/s3-buckets/terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/s3-bucket/aws//wrappers" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git//wrappers?ref=master" +} + +inputs = { + defaults = { + force_destroy = true + + attach_elb_log_delivery_policy = true + attach_lb_log_delivery_policy = true + attach_deny_insecure_transport_policy = true + attach_require_latest_tls_policy = true + } + + items = { + bucket1 = { + bucket = "my-random-bucket-1" + } + bucket2 = { + bucket = "my-random-bucket-2" + tags = { + Secure = "probably" + } + } + } +} +``` diff --git a/wrappers/resolver-firewall-rule-group/main.tf b/wrappers/resolver-firewall-rule-group/main.tf new file mode 100644 index 0000000..c9d853a --- /dev/null +++ b/wrappers/resolver-firewall-rule-group/main.tf @@ -0,0 +1,12 @@ +module "wrapper" { + source = "../../modules/resolver-firewall-rule-group" + + for_each = var.items + + create = try(each.value.create, var.defaults.create, true) + name = try(each.value.name, var.defaults.name, "") + ram_resource_associations = try(each.value.ram_resource_associations, var.defaults.ram_resource_associations, {}) + region = try(each.value.region, var.defaults.region, null) + rules = try(each.value.rules, var.defaults.rules, {}) + tags = try(each.value.tags, var.defaults.tags, {}) +} diff --git a/wrappers/resolver-firewall-rule-group/outputs.tf b/wrappers/resolver-firewall-rule-group/outputs.tf new file mode 100644 index 0000000..ec6da5f --- /dev/null +++ b/wrappers/resolver-firewall-rule-group/outputs.tf @@ -0,0 +1,5 @@ +output "wrapper" { + description = "Map of outputs of a wrapper." + value = module.wrapper + # sensitive = false # No sensitive module output found +} diff --git a/wrappers/resolver-firewall-rule-group/variables.tf b/wrappers/resolver-firewall-rule-group/variables.tf new file mode 100644 index 0000000..a6ea096 --- /dev/null +++ b/wrappers/resolver-firewall-rule-group/variables.tf @@ -0,0 +1,11 @@ +variable "defaults" { + description = "Map of default values which will be used for each item." + type = any + default = {} +} + +variable "items" { + description = "Maps of items to create a wrapper from. Values are passed through to the module." + type = any + default = {} +} diff --git a/wrappers/resolver-firewall-rule-group/versions.tf b/wrappers/resolver-firewall-rule-group/versions.tf new file mode 100644 index 0000000..fd053a1 --- /dev/null +++ b/wrappers/resolver-firewall-rule-group/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.5.7" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 6.3" + } + } +} diff --git a/wrappers/variables.tf b/wrappers/variables.tf new file mode 100644 index 0000000..a6ea096 --- /dev/null +++ b/wrappers/variables.tf @@ -0,0 +1,11 @@ +variable "defaults" { + description = "Map of default values which will be used for each item." + type = any + default = {} +} + +variable "items" { + description = "Maps of items to create a wrapper from. Values are passed through to the module." + type = any + default = {} +} diff --git a/wrappers/versions.tf b/wrappers/versions.tf new file mode 100644 index 0000000..fd053a1 --- /dev/null +++ b/wrappers/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.5.7" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 6.3" + } + } +}