The devil is in the details is an idiom that refers to a catch or mysterious element hidden in the details, meaning that something might seem simple at a first look but will take more time and effort to complete than expected. I think this describes pretty well the announcement of ACM Cloudformation extension.

Promise of being able to provision SSL certificates from Cloudformation without any manual validation steps sounds absolutely great. Until now you either had to reference existing certificates with ARNs or do some hacky tricks to validate certificate request with Lambda-backed custom resource. While the promise is true, there are some details that make it less than perfect for typical use-cases :-(

HostedZoneName vs. HostedZoneId

When using AWS::Route53::RecordSet to create DNS entries, you can define the hosted zone by it’s id or name. Name is the preference for (most) humans as it is easy to remember and less error prone than random id. There is limitation that you must use id if you have more than one zone with the same name, but for most of the time name works fine.

To enable automatic certificate validation you must use DNS validation, but that is not enough. Before Cloudformation can add DNS records for certificate validation, you must also set HostedZoneId in DomainValidationOption to define which hosted zone records should be created to. Unfortunately it is not possible to use HostedZoneName here the same way it works with Route53 records. So in many cases you must provide a matching pair of hosted zone name and id for your template to setup both DNS record and SSL certificate.

Cross-parameter validation

When there are non-trivial parameter combinations in the template, it is good if you can catch errors before deploying resources. Cloudformation Rules can do some impressive pre-deployment validations like checking that given subnets are within given VPC.

Rules:

  SubnetsInVPC:
    Assertions:
    - Assert:
        Fn::EachMemberIn:
        - Fn::ValueOf:
          - Subnets
          - VpcId
        - 
          - !Ref VPCID
      AssertDescription: All subnets must be within the given VPC

Unfortunately above to work requires use of AWS-specific parameter type AND type to support useful attributes. Above example of matching subnets to VPC is about the only use-case that satisfies both requirements at the moment. But just using AWS::Route53::HostedZone::Id instead of generic string will help to catch errors, especially when deploying from console.

Macros and custom resources

There is no problem that couldn’t fixed with a lambda function. Cloudformation Macros could be used to find HostedZoneId for given HostedZoneName and implement the similar functionality as there is by default for Route53 RecordSets. You would have to create and register the macro to resolve name to id before deploying the stack using it. This can be seen as both advantage if you are building a library of support functions to extend cloudformation or disadvantage if you would like to keep everything within single template and make sure your solution can be deployed everywhere.

Other option is to create a custom resource where name2id mapping lambda function can be included in the same SAM template with rest of your stack. While this does sound like a tempting challenge I will leave to you to solve and will not provide code in this blog post :-)

Have patience

Provisioning ACM certificate is pretty fast, but validation can take time as DNS changes do need some time to propagete. In my very limited testing it took 8min 35s to create a validated certificate. Cloudformation will not consider certificate ready before it has been validated when ValidationOptions is defined. If you have many resources pending on certificate it would be a good idea to keep an eye on stack timeout value and increase it as needed.

Sample template

As I believe trying things out is really the way to find out all these little details, so here is a minimal template for you to build an ALB with HTTPS listener on port 443 with fixed response, i.e. no backends. To deploy this you will only need a Route53 hosted zone and a VPC with minimum of 2 subnets. If everything works as planned, you should have a valid SSL certificate and hello-world response at URL found in stack output.