Fork me on GitHub

How to fully automate the provisioning of ACM certificates in CloudFormation

AWS Certificate Manager is a great service that eases the creation and renewal of certificates. After you request a certificate, it allows you two ways to prove that you own the domain. Either by clicking on an email sent to the administrative contact of the domain or by updating a record in DNS.

As we aim to automate the entire process, email validation method is not an option. It requires a human to click on a link. Validating through DNS is the way to go. With Route53 you can program the creation of the required DNS records.

But the CloudFormation AWS::CertificateManager::Certificate resource does not quite help. It does not reveal the DNS records that you need to create. It only writes them in the CloudFormation log. You still have to collect them and create the DNS records yourself.

With this custom provider you can automate the creation of certificates with CloudFormation!

How do I automate the provisioning of ACM certificates?

You can automate the provisioning of ACM certificates in 4 easy steps:

Step 1 - Request a certificate using a Custom::Certificate resource:

    Type: Custom::Certificate
      DomainName: !Ref DomainName
      ValidationMethod: DNS
      ServiceToken: !Sub 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:binxio-cfn-certificate-provider'

It takes the same arguments as the official AWS::CertificateManager::Certificate but does not wait for it to be issued.

Step 2 - Obtain the DNS record value using a Custom::CertificateDNSRecord resource:

    Type: Custom::CertificateDNSRecord
      CertificateArn: !Ref Certificate
      DomainName: !Ref DomainName
      ServiceToken: !Sub 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:binxio-cfn-certificate-provider'

This resource does not create anything, it just obtains the Name, Value, and Type of the DNS record for the domain as specified by the certificate.

Step 3 - Create the DNS record using a AWS::Route53::ResourceRecordSetGroup resource:

    Type: AWS::Route53::RecordSetGroup
      HostedZoneId: !Ref HostedZoneId
        - Name: !Sub '${CertificateDNSRecord.Name}'
          Type: !Sub '${CertificateDNSRecord.Type}'
          TTL: '60'
          Weight: 1
          SetIdentifier: !Ref 'DomainName'
            - !Sub '${CertificateDNSRecord.Value}'

Using the Name, Type and Value retrieved from the certificate. Note that we use a weighted recordset, as the same name may be returned for different domain names.

Step 4 - Wait for the certificate to be issued using a Custom::IssuedCertificate resource:

    Type: Custom::IssuedCertificate
      CertificateArn: !Ref Certificate
      ServiceToken: !Sub 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:binxio-cfn-certificate-provider'

As long as the certificate request is pending validation, it can not be used. This resource does not create anything. It just waits for the certificate to reach the state ISSUED. It will return the ARN to be used with an ELB or CloudFront resource.


Using these custom resources you can automate the provisioning of AWS certificates. For more information, check out the repository on GitHub.

Got to here? You probably also like deploying secrets and deploying private key pairs with CloudFormation.

Picture of Mark van Holsteijn
Mark van Holsteijn