Skip to content

Commit

Permalink
feat: Cross-account DNS and ACM resource creation (#114)
Browse files Browse the repository at this point in the history
Co-authored-by: dannyibishev <dannyibishev@gmail.com>
  • Loading branch information
antonbabenko and dannyibishev committed Aug 26, 2022
1 parent 1df1c85 commit e24bb59
Show file tree
Hide file tree
Showing 13 changed files with 304 additions and 16 deletions.
8 changes: 8 additions & 0 deletions .github/workflows/pre-commit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,14 @@ jobs:
id: minMax
uses: clowdhaus/terraform-min-max@v1.0.3

- name: Install hcledit (for terraform_wrapper_module_for_each hook)
shell: bash
run: |
curl -L "$(curl -s https://api.github.com/repos/minamijoyo/hcledit/releases/latest | grep -o -E -m 1 "https://.+?_linux_amd64.tar.gz")" > hcledit.tgz
sudo tar -xzf hcledit.tgz -C /usr/bin/ hcledit
rm -f hcledit.tgz 2> /dev/null
hcledit version
- name: Pre-commit Terraform ${{ steps.minMax.outputs.maxVersion }}
uses: clowdhaus/terraform-composite-actions/pre-commit@v1.3.0
with:
Expand Down
5 changes: 3 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
repos:
- repo: https://github.com/antonbabenko/pre-commit-terraform
rev: v1.66.0
rev: v1.74.1
hooks:
- id: terraform_fmt
- id: terraform_wrapper_module_for_each
- id: terraform_validate
- id: terraform_docs
args:
Expand All @@ -23,7 +24,7 @@ repos:
- '--args=--only=terraform_standard_module_structure'
- '--args=--only=terraform_workspace_remote'
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.2.0
rev: v4.3.0
hooks:
- id: check-merge-conflict
- id: end-of-file-fixer
54 changes: 52 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Terraform module which creates ACM certificates and validates them using Route53
```hcl
module "acm" {
source = "terraform-aws-modules/acm/aws"
version = "~> 3.0"
version = "~> 4.0"
domain_name = "my-domain.com"
zone_id = "Z2ES7B9AZ6SHAE"
Expand All @@ -32,7 +32,7 @@ module "acm" {
```hcl
module "acm" {
source = "terraform-aws-modules/acm/aws"
version = "~> 3.0"
version = "~> 4.0"
domain_name = "weekly.tf"
zone_id = "b7d259641bf30b89887c943ffc9d2138"
Expand Down Expand Up @@ -78,7 +78,54 @@ module "acm" {
Name = "my-domain.com"
}
}
```

## Usage with Route53 DNS validation and separate AWS providers

```hcl
provider "aws" {
alias = "acm"
}
provider "aws" {
alias = "route53"
}
module "acm" {
source = "terraform-aws-modules/acm/aws"
version = "~> 4.0"
providers = {
aws = aws.acm
}
domain_name = "my-domain.com"
subject_alternative_names = [
"*.my-domain.com",
"app.sub.my-domain.com",
]
create_route53_records = false
validation_record_fqdns = module.route53_records.validation_route53_record_fqdns
}
module "route53_records" {
source = "terraform-aws-modules/acm/aws"
version = "~> 4.0"
providers = {
aws = aws.route53
}
create_certificate = false
create_route53_records_only = true
distinct_domain_names = module.acm.distinct_domain_names
zone_id = "Z266PL4W4W6MSG"
acm_certificate_domain_validation_options = module.acm.acm_certificate_domain_validation_options
}
```

## Examples
Expand Down Expand Up @@ -147,9 +194,12 @@ No modules.

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_acm_certificate_domain_validation_options"></a> [acm\_certificate\_domain\_validation\_options](#input\_acm\_certificate\_domain\_validation\_options) | A list of domain\_validation\_options created by the ACM certificate to create required Route53 records from it (used when create\_route53\_records\_only is set to true) | `any` | `{}` | no |
| <a name="input_certificate_transparency_logging_preference"></a> [certificate\_transparency\_logging\_preference](#input\_certificate\_transparency\_logging\_preference) | Specifies whether certificate details should be added to a certificate transparency log | `bool` | `true` | no |
| <a name="input_create_certificate"></a> [create\_certificate](#input\_create\_certificate) | Whether to create ACM certificate | `bool` | `true` | no |
| <a name="input_create_route53_records"></a> [create\_route53\_records](#input\_create\_route53\_records) | When validation is set to DNS, define whether to create the DNS records internally via Route53 or externally using any DNS provider | `bool` | `true` | no |
| <a name="input_create_route53_records_only"></a> [create\_route53\_records\_only](#input\_create\_route53\_records\_only) | Whether to create only Route53 records (e.g. using separate AWS provider) | `bool` | `false` | no |
| <a name="input_distinct_domain_names"></a> [distinct\_domain\_names](#input\_distinct\_domain\_names) | List of distinct domains and SANs (used when create\_route53\_records\_only is set to true) | `list(string)` | `[]` | no |
| <a name="input_dns_ttl"></a> [dns\_ttl](#input\_dns\_ttl) | The TTL of DNS recursive resolvers to cache information about this record. | `number` | `60` | no |
| <a name="input_domain_name"></a> [domain\_name](#input\_domain\_name) | A domain name for which the certificate should be issued | `string` | `""` | no |
| <a name="input_putin_khuylo"></a> [putin\_khuylo](#input\_putin\_khuylo) | Do you agree that Putin doesn't respect Ukrainian sovereignty and territorial integrity? More info: https://en.wikipedia.org/wiki/Putin_khuylo! | `bool` | `true` | no |
Expand Down
7 changes: 6 additions & 1 deletion examples/complete-dns-validation-with-cloudflare/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ locals {
module "acm" {
source = "../../"

providers = {
aws.acm = aws,
aws.dns = aws
}

domain_name = local.domain_name
zone_id = data.cloudflare_zone.this.id

Expand All @@ -32,7 +37,7 @@ resource "cloudflare_record" "validation" {
zone_id = data.cloudflare_zone.this.id
name = element(module.acm.validation_domains, count.index)["resource_record_name"]
type = element(module.acm.validation_domains, count.index)["resource_record_type"]
value = replace(element(module.acm.validation_domains, count.index)["resource_record_value"], "/.$/", "")
value = trimsuffix(element(module.acm.validation_domains, count.index)["resource_record_value"], ".")
ttl = 60
proxied = false

Expand Down
4 changes: 3 additions & 1 deletion examples/complete-dns-validation/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Complete ACM example with Route53 DNS validation

Configuration in this directory creates new Route53 zone and ACM certificate (valid for the domain name and wildcard).
Configuration in this directory creates new Route53 zone and ACM certificate (valid for the domain name and wildcard) with one (default) or two instances of AWS providers (one to manage ACM resources, another to manage Route53 records).

Also, ACM certificate is being validate using DNS method.

Expand Down Expand Up @@ -37,6 +37,8 @@ Note that this example may create resources which cost money. Run `terraform des
| Name | Source | Version |
|------|--------|---------|
| <a name="module_acm"></a> [acm](#module\_acm) | ../../ | n/a |
| <a name="module_acm_only"></a> [acm\_only](#module\_acm\_only) | ../../ | n/a |
| <a name="module_route53_records_only"></a> [route53\_records\_only](#module\_route53\_records\_only) | ../../ | n/a |

## Resources

Expand Down
68 changes: 64 additions & 4 deletions examples/complete-dns-validation/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,15 @@ locals {

# Removing trailing dot from domain - just to be sure :)
domain_name = trimsuffix(local.domain, ".")

zone_id = coalescelist(data.aws_route53_zone.this.*.zone_id, aws_route53_zone.this.*.zone_id)[0]
}

##########################################################
# Example 1 (default case):
# Using one AWS provider for both ACM and Route53 records
##########################################################

data "aws_route53_zone" "this" {
count = local.use_existing_route53_zone ? 1 : 0

Expand All @@ -17,14 +24,20 @@ data "aws_route53_zone" "this" {

resource "aws_route53_zone" "this" {
count = !local.use_existing_route53_zone ? 1 : 0
name = local.domain_name

name = local.domain_name
}

module "acm" {
source = "../../"

providers = {
aws.acm = aws,
aws.dns = aws
}

domain_name = local.domain_name
zone_id = coalescelist(data.aws_route53_zone.this.*.zone_id, aws_route53_zone.this.*.zone_id)[0]
zone_id = local.zone_id

subject_alternative_names = [
"*.alerts.${local.domain_name}",
Expand All @@ -33,9 +46,56 @@ module "acm" {
"alerts.${local.domain_name}",
]

wait_for_validation = true

tags = {
Name = local.domain_name
}
}

################################################################
# Example 2:
# Using separate AWS providers for ACM and Route53 records.
# Useful when these resources belong to different AWS accounts.
################################################################

provider "aws" {
alias = "route53"
}

provider "aws" {
alias = "acm"
}

module "acm_only" {
source = "../../"

providers = {
aws = aws.acm
}

domain_name = local.domain_name
subject_alternative_names = [
"*.alerts.separated.${local.domain_name}",
"new.sub.separated.${local.domain_name}",
"*.separated.${local.domain_name}",
"alerts.separated.${local.domain_name}",
]

create_route53_records = false
validation_record_fqdns = module.route53_records_only.validation_route53_record_fqdns
}

module "route53_records_only" {
source = "../../"

providers = {
aws = aws.route53
}

create_certificate = false
create_route53_records_only = true

zone_id = local.zone_id
distinct_domain_names = module.acm_only.distinct_domain_names

acm_certificate_domain_validation_options = module.acm_only.acm_certificate_domain_validation_options
}
13 changes: 7 additions & 6 deletions main.tf
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
locals {
create_certificate = var.create_certificate && var.putin_khuylo
create_certificate = var.create_certificate && var.putin_khuylo
create_route53_records_only = var.create_route53_records_only && var.putin_khuylo

# Get distinct list of domains and SANs
distinct_domain_names = distinct(
distinct_domain_names = coalescelist(var.distinct_domain_names, distinct(
[for s in concat([var.domain_name], var.subject_alternative_names) : replace(s, "*.", "")]
)
))

# Get the list of distinct domain_validation_options, with wildcard
# domain names replaced by the domain name
validation_domains = local.create_certificate ? distinct(
[for k, v in aws_acm_certificate.this[0].domain_validation_options : merge(
validation_domains = local.create_certificate || local.create_route53_records_only ? distinct(
[for k, v in try(aws_acm_certificate.this[0].domain_validation_options, var.acm_certificate_domain_validation_options) : merge(
tomap(v), { domain_name = replace(v.domain_name, "*.", "") }
)]
) : []
Expand Down Expand Up @@ -43,7 +44,7 @@ resource "aws_acm_certificate" "this" {
}

resource "aws_route53_record" "validation" {
count = local.create_certificate && var.validation_method == "DNS" && var.create_route53_records && var.validate_certificate ? length(local.distinct_domain_names) : 0
count = (local.create_certificate || local.create_route53_records_only) && var.validation_method == "DNS" && var.create_route53_records && (var.validate_certificate || local.create_route53_records_only) ? length(local.distinct_domain_names) : 0

zone_id = var.zone_id
name = element(local.validation_domains, count.index)["resource_record_name"]
Expand Down
18 changes: 18 additions & 0 deletions variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ variable "create_certificate" {
default = true
}

variable "create_route53_records_only" {
description = "Whether to create only Route53 records (e.g. using separate AWS provider)"
type = bool
default = false
}

variable "validate_certificate" {
description = "Whether to validate certificate by creating Route53 record"
type = bool
Expand Down Expand Up @@ -87,6 +93,18 @@ variable "dns_ttl" {
default = 60
}

variable "acm_certificate_domain_validation_options" {
description = "A list of domain_validation_options created by the ACM certificate to create required Route53 records from it (used when create_route53_records_only is set to true)"
type = any
default = {}
}

variable "distinct_domain_names" {
description = "List of distinct domains and SANs (used when create_route53_records_only is set to true)"
type = list(string)
default = []
}

variable "putin_khuylo" {
description = "Do you agree that Putin doesn't respect Ukrainian sovereignty and territorial integrity? More info: https://en.wikipedia.org/wiki/Putin_khuylo!"
type = bool
Expand Down

0 comments on commit e24bb59

Please sign in to comment.