Skip to main content

Worry-free AWS ACM certificate DNS validation with Pulumi

· 4 min read
Akhan Zhakiyanov
Lead engineer

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

note
  • 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
tip

It's not mandatory to use Route53 for domain DNS management and validation. This example uses Route53 just for simplicity

IaC

warning

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]

AWS ACM wildcard and apex cert validation error

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.

AWS ACM duplicate validation 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.

AWS ACM issued cert

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