Skip to content

Commit d49054c

Browse files
authored
[feature] aws-acm-certificate module compatible with TF AWS Provider >3.0 (#321)
Users of TF AWS Provider <3.0 must continue using aws-acm-cert module, which is now officially deprecated; it should be deleted once all uses of it are done. Although cloudposse provides their own module https://github.com/cloudposse/terraform-aws-acm-request-certificate it does not have some features that we use, such as verifying subject alternative names on different Route 53 zones than the one used for the main domain, so we will for now continue maintaining our own. The following changes are made: * Removal of hacks cert_subject_alternative_names_count and cert_subject_alternative_names_order * Replacing individual project/env/service/owner inputs with a single tags input that takes those as a map * Using a for_each map instead of an indexed count for creating the validation Route 53 record (this is the change that makes it so we can't just do a drop in replacement) A migration of existing certificates to this module must either map the Route 53 records to the new Terraform state location (since it switches from a count to a for_each), or force recreation. In addition, the underlying provider changes the domain_validation_options of aws_acm_certificate from a list to a set, and does this in a backwards incompatible way such that naive applies (even if not creating Route 53 records) will break. See https://registry.terraform.io/providers/hashicorp/aws/latest/docs/guides/version-3-upgrade#resource-aws_acm_certificate for more details. Implementing the migration (i.e. identifying where the migration needs to be done, and scripting Terraform state moves and Terraform applies to the new state name, across existing resources even if the reference to aws-acm-cert is deeply nested inside modules) is left as a future exercise. This would either require a bunch of state migrations, or possibly deletion and recreation of the ACM certificate.
1 parent 781e2bc commit d49054c

File tree

7 files changed

+220
-0
lines changed

7 files changed

+220
-0
lines changed

aws-acm-cert/README.md

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# AWS ACM Cert
22

3+
**_DEPRECATED: Use aws-acm-certificate if using Terraform AWS Provider >3.0._**
4+
35
Will create and attempt to validate an certificate in the [AWS ACM service](https://aws.amazon.com/certificate-manager/). This module uses DNS verification so the principal running this needs to be able to write to the supplied Route53 zone.
46

57
NOTE: if you intend to use this certificate in a cloudfront distribution it must be created in `us-east-1` region.

aws-acm-certificate/README.md

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# AWS ACM Certificate
2+
3+
Will create and attempt to validate an certificate in the [AWS ACM service](https://aws.amazon.com/certificate-manager/). This module uses DNS verification so the principal running this needs to be able to write to the supplied Route53 zone.
4+
5+
NOTE: if you intend to use this certificate in a cloudfront distribution it must be created in `us-east-1` region.
6+
7+
## Example
8+
9+
```hcl
10+
module "cert" {
11+
source = "github.com/chanzuckerberg/cztack//aws-acm-certificate?ref=v0.40.0"
12+
13+
# the cert domain name
14+
cert_domain_name = "..."
15+
16+
# the route53 zone for validating the `cert_domain_name`
17+
aws_route53_zone_id = "..."
18+
19+
# an optional map of alternative : route53_zone_id
20+
cert_subject_alternative_names = {"foobar.com" = data.aws_route53_zone.foo.id}
21+
22+
# optional variable for tags
23+
tags = {
24+
project = "...",
25+
env = "...",
26+
service = "...",
27+
owner = "..."
28+
}
29+
}
30+
```
31+
32+
<!-- START -->
33+
## Requirements
34+
35+
| Name | Version |
36+
|------|---------|
37+
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 3.0.0 |
38+
39+
## Providers
40+
41+
| Name | Version |
42+
|------|---------|
43+
| <a name="provider_aws"></a> [aws](#provider\_aws) | >= 3.0.0 |
44+
45+
## Modules
46+
47+
No modules.
48+
49+
## Resources
50+
51+
| Name | Type |
52+
|------|------|
53+
| [aws_acm_certificate.cert](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/acm_certificate) | resource |
54+
| [aws_acm_certificate_validation.cert](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/acm_certificate_validation) | resource |
55+
| [aws_route53_record.cert_validation](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record) | resource |
56+
57+
## Inputs
58+
59+
| Name | Description | Type | Default | Required |
60+
|------|-------------|------|---------|:--------:|
61+
| <a name="input_aws_route53_zone_id"></a> [aws\_route53\_zone\_id](#input\_aws\_route53\_zone\_id) | n/a | `string` | n/a | yes |
62+
| <a name="input_cert_domain_name"></a> [cert\_domain\_name](#input\_cert\_domain\_name) | Like www.foo.bar.com or *.foo.bar.com | `string` | n/a | yes |
63+
| <a name="input_cert_subject_alternative_names"></a> [cert\_subject\_alternative\_names](#input\_cert\_subject\_alternative\_names) | A map of <alternative\_domain:route53\_zone\_id> | `map(string)` | `{}` | no |
64+
| <a name="input_tags"></a> [tags](#input\_tags) | Tags to apply to certificate | `map(string)` | `{}` | no |
65+
| <a name="input_validation_record_ttl"></a> [validation\_record\_ttl](#input\_validation\_record\_ttl) | n/a | `string` | `60` | no |
66+
67+
## Outputs
68+
69+
| Name | Description |
70+
|------|-------------|
71+
| <a name="output_arn"></a> [arn](#output\_arn) | n/a |
72+
| <a name="output_id"></a> [id](#output\_id) | n/a |
73+
<!-- END -->

aws-acm-certificate/main.tf

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
resource "aws_acm_certificate" "cert" {
2+
domain_name = var.cert_domain_name
3+
subject_alternative_names = keys(var.cert_subject_alternative_names)
4+
validation_method = "DNS"
5+
tags = var.tags
6+
7+
lifecycle {
8+
create_before_destroy = true
9+
}
10+
}
11+
12+
resource "aws_route53_record" "cert_validation" {
13+
for_each = {
14+
for dvo in aws_acm_certificate.cert.domain_validation_options : dvo.domain_name => {
15+
name = dvo.resource_record_name
16+
record = dvo.resource_record_value
17+
type = dvo.resource_record_type
18+
}
19+
}
20+
21+
name = each.value.name
22+
type = each.value.type
23+
zone_id = lookup(var.cert_subject_alternative_names, each.key, var.aws_route53_zone_id)
24+
records = [each.value.record]
25+
ttl = var.validation_record_ttl
26+
27+
allow_overwrite = true # Needed if making cert in multiple regions, and for AWS Provider 3.0 conversion
28+
}
29+
30+
resource "aws_acm_certificate_validation" "cert" {
31+
certificate_arn = aws_acm_certificate.cert.arn
32+
validation_record_fqdns = [for record in aws_route53_record.cert_validation : record.fqdn]
33+
}

aws-acm-certificate/module_test.go

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package test
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/chanzuckerberg/go-misc/tftest"
8+
"github.com/gruntwork-io/terratest/modules/terraform"
9+
)
10+
11+
func TestAWSACMCertificateInit(t *testing.T) {
12+
options := &terraform.Options{
13+
TerraformDir: ".",
14+
}
15+
terraform.Init(t, options)
16+
}
17+
18+
func TestAWSACMCertificateDefaults(t *testing.T) {
19+
t.Parallel()
20+
21+
test := tftest.Test{
22+
Setup: func(t *testing.T) *terraform.Options {
23+
certDomainName := fmt.Sprintf(
24+
"%s.%s",
25+
tftest.UniqueID(),
26+
tftest.EnvVar(tftest.EnvRoute53ZoneName))
27+
28+
alternativeDomainName := fmt.Sprintf(
29+
"%s.%s",
30+
tftest.UniqueID(),
31+
tftest.EnvVar(tftest.EnvRoute53ZoneName))
32+
33+
route53ZoneID := tftest.EnvVar(tftest.EnvRoute53ZoneID)
34+
35+
alternativeNames := map[string]string{
36+
alternativeDomainName: route53ZoneID,
37+
}
38+
39+
tags := map[string]string{
40+
"project": tftest.UniqueID(),
41+
"env": tftest.UniqueID(),
42+
"service": tftest.UniqueID(),
43+
"owner": tftest.UniqueID(),
44+
"managedBy": "terraform",
45+
}
46+
47+
vars := map[string]interface{}{
48+
"cert_domain_name": certDomainName,
49+
"aws_route53_zone_id": route53ZoneID,
50+
"validation_record_ttl": 5,
51+
"cert_subject_alternative_names": alternativeNames,
52+
"tags": tags,
53+
}
54+
55+
options := &terraform.Options{
56+
TerraformDir: ".",
57+
58+
EnvVars: map[string]string{
59+
"AWS_DEFAULT_REGION": tftest.DefaultRegion,
60+
},
61+
62+
Vars: vars,
63+
}
64+
65+
return options
66+
},
67+
Validate: func(t *testing.T, options *terraform.Options) {},
68+
}
69+
70+
test.Run(t)
71+
}

aws-acm-certificate/outputs.tf

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
output "arn" {
2+
value = aws_acm_certificate.cert.arn
3+
description = "AWS ARN of the certificate"
4+
}
5+
6+
output "id" {
7+
value = aws_acm_certificate.cert.id
8+
description = "ID of the certificate"
9+
}

aws-acm-certificate/terraform.tf

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
terraform {
2+
required_providers {
3+
# aws_acm_certificate changed API in 3.0.0
4+
aws = ">= 3.0.0"
5+
}
6+
}

aws-acm-certificate/variables.tf

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
variable "cert_domain_name" {
2+
type = string
3+
description = "Like www.foo.bar.com or *.foo.bar.com"
4+
}
5+
6+
variable "cert_subject_alternative_names" {
7+
type = map(string)
8+
description = "A map of <alternative_domain:route53_zone_id>"
9+
default = {}
10+
}
11+
12+
variable "aws_route53_zone_id" {
13+
type = string
14+
description = "Default Route 53 zone to create validation records in"
15+
}
16+
17+
variable "validation_record_ttl" {
18+
type = string
19+
default = 60
20+
description = "TTL value of DNS validation records"
21+
}
22+
23+
variable tags {
24+
type = object({ project : string, env : string, service : string, owner : string, managedBy : string })
25+
description = "Tags to apply to certificate"
26+
}

0 commit comments

Comments
 (0)