Worry-free AWS ACM certificate DNS validation with Pulumi
Quite often I need to create AWS ACM certificate and do DNS validation using Pulumi.
Pulumi aws.acm.CertificateValidation DNS example only show basic and naive implementation:
- hardcoded single domain validation ¯_(ツ)_/¯
- will fail if you try to validated wildcard and apex domain simultaneously
* creating Route 53 Record: InvalidChangeBatch: [Tried to create resource record set [name='_f22110437fea5500aa0f8bf286aed7c7.howto.ahanoff.dev.', type='CNAME'] but it already exists]
Today I'll show how to create validation that works for any domain
- I will use
howto.ahanoff.dev
domain as example - I will use Pulumi with Typescript
I will create AWS ACM certificate with the following conditions:
- multi domain certificate
- validates wildcard domain:
*.howto.ahanoff.dev
- validates apex domain:
howto.ahanoff.dev
Prerequisites
- Pulumi CLI
- AWS programmatic credentials
- domain name
- domain is managed in Route53
It's not mandatory to use Route53 for domain DNS management and validation. This example uses Route53 just for simplicity
IaC
As of now ACM is not supported by @pulumi/aws-native. Code example will contains classic @pulumi/aws version
Hosted zone
First of all I create hosted zone to delegate domain management to AWS Route53
import * as aws from "@pulumi/aws";
const zone = new aws.route53.Zone('howto.ahanoff.dev', {
name: 'howto.ahanoff.dev'
})
Values from zone.nameServers
need to be created in your domain registrar to complete delegation.
Certificate
Also note that *.example.com protects only the subdomains of example.com, it does not protect the bare or apex domain (example.com). However, you can request a certificate that protects a bare or apex domain and its subdomains by specifying multiple domain names in your request. For example, you can request a certificate that protects example.com and *.example.com.
— https://docs.aws.amazon.com/acm/latest/userguide/acm-certificate.html
Let's folow official docs and create such certificate:
/**
* AWS ACM wildcard certificate that also protects apex domain
*
*/
const cert = new aws.acm.Certificate('howto.ahanoff.dev-cert', {
domainName: '*.howto.ahanoff.dev',
subjectAlternativeNames: [
'howto.ahanoff.dev'
],
validationMethod: 'DNS'
})
Worry-free validation
So why did we get that error in the first place?
* creating Route 53 Record: InvalidChangeBatch: [Tried to create resource record set [name='_f22110437fea5500aa0f8bf286aed7c7.howto.ahanoff.dev.', type='CNAME'] but it already exists]
If you look to cert.domainValidationOptions
, you would see that records for wildcard domain and apex domain are actually equal.
And Route53 prohibits to create duplicate records.
Let's fix that!
Prevously I would use lodash, but now it can be done with built-in NodeJS functions.
What we need is uniqWith lodash equivalient:
/**
* https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore#_uniqWith
* @param arr sequence of elements that are not unique
* @param fn comparator
* @returns
*/
const uniqWith = (arr: any[], fn: (arg0: any, arg1: any) => any) => arr.filter((element, index) => arr.findIndex((step) => fn(element, step)) === index);
Then final implementation with uniqWith
cert.domainValidationOptions.apply(validationOptions => {
// filter out duplicate validation options based on record type, name and value
uniqWith(validationOptions, (x: aws.types.output.acm.CertificateDomainValidationOption, y: aws.types.output.acm.CertificateDomainValidationOption) => {
return x.resourceRecordType === y.resourceRecordType && x.resourceRecordValue === y.resourceRecordValue && x.resourceRecordName === y.resourceRecordName
})
// map validation options to Route53 record
.map((validationOption, index) => {
return new aws.route53.Record(`howto.ahanoff.dev-cert-validation-record-${index}`, {
type: validationOption.resourceRecordType,
ttl: 60,
zoneId: zone.zoneId,
name: validationOption.resourceRecordName,
records: [
validationOption.resourceRecordValue
]
})
})
// for each record request DSN validation
.forEach((certValidationRecord, index) => {
new aws.acm.CertificateValidation(`howto.ahanoff.dev-cert-dns-validation-${index}`, {
certificateArn: cert.arn,
validationRecordFqdns: [ certValidationRecord.fqdn ]
})
})
})
Summary
After running pulumi up
you should have your ACM certificate issued and ready to use.
This validation will work for any ACM certificate.
Full code example can be found at https://github.com/ahanoff/how-to/blob/main/aws-acm-dns-validation/index.ts