Cloudformation Trickery

Posted by Henry Cook on Sun 14 August 2016

As soon as Amazon announced AWS Certificate Manager (ACM) we started planning on how we were going to move all of services over to using it. The fact that you don't have to pay for them and it auto renews is just amazing.

To automate this as much as possible we wanted to shove it all into a CloudFormation (CF) template along with our Elastic Load Balancer (ELB) configuration. The only part we were not able to automate is the creation of the certicates, however once created you can use the certificate's Amazon Resource Name (ARN) in the CF template.

One thing to note is that if you create a cert in us-west-2 you aren't able to use it in another region. For example a wildcard cert e.g. *.havingatinker.uk in us-west-2, would not be useable in eu-west-1. This meant for us that we needed to create the same certificate in all the regions we needed it in, and then reference them somehow in the CF template.

Cloudformation Example

By using Mappings and Conditionals within the cloudformation template we could then utilise any existing certs if specified or use the new ACM certificates, see below for an example CF template:

{

    "Parameters" : {

      "SSLCertificateId" : {
        "Type" : "String",
        "Description" : "SSL certificate ID"
        ""
      }

    },

  "Mappings" : {
    "RegionMap" : {
        "us-east-1" : { 
        "SSLCertificateId" : "arn:aws:acm:us-east-1:0123456789:certificate/1111111-2222-3333-4444444444444444"
        },
        "eu-west-1" : {
          "SSLCertificateId" : "arn:aws:acm:eu-west-1:0123456789:certificate/1111111-2222-3333-4444444444444444"
        },
        "ap-northeast-1" : {
        "SSLCertificateId" : "arn:aws:acm:ap-northeast-1:0123456789:certificate/1111111-2222-3333-4444444444444444"
        }
    }
    },

    "Conditions" : {
      "SpecifiedSSLCert" : {
        "Fn::Equals" : [
          {"Ref" : "SSLCertificateId"}, 
          ""
        ]
      }
    }, 

    "Resources" : {

        "ELB" : {
          "Type" : "AWS::ElasticLoadBalancing::LoadBalancer",
          "Properties" : {
              {
                "SSLCertificateId" : {
                  "Fn::If" : [
                    "SpecifiedSSLCert",
                    { "Fn::FindInMap" : [ "RegionMap", { "Ref" : "AWS::Region" }, "SSLCertificateId"] },
                    { "Ref" : "SSLCertificateId" }
                  ]
                }
              }
            ],
          }
        },
  }

}

Mappings

Via the use of Mappings I can specify which ACM certs to use by its ARN depending on the region, and then reference it in the ELB resource.

"Mappings" : {
    "RegionMap" : {
    "us-east-1" : { 
        "SSLCertificateId" : "arn:aws:acm:us-east-1:0123456789:certificate/1111111-2222-3333-4444444444444444"
    },
    "eu-west-1" : {
      "SSLCertificateId" : "arn:aws:acm:eu-west-1:0123456789:certificate/1111111-2222-3333-4444444444444444"
    },
    "ap-northeast-1" : {
        "SSLCertificateId" : "arn:aws:acm:ap-northeast-1:0123456789:certificate/1111111-2222-3333-4444444444444444"
    }
    }
}

Conditionals

By using Cloudformation Conditionals we were then able to use the ACM certs in the mappings however use a pre-existing certificate if needed. We wanted to make sure nothing would break while we transitioned over, and to give us the ability to use a cert that we may pass externally into the template if we wanted it.

Fn::Equals

Compares if two values are equal. Returns true if the two values are equal or false if they aren't.

By using the Fn::Equals conditional it means that if no certificate has been passed through into the template it returns true as the string is empty, otherwise it'll return false.

"Conditions" : {
      "SpecifiedSSLCert" : {
        "Fn::Equals" : [
          {"Ref" : "SSLCertificateId"}, 
          ""
        ]
      }
    },

Fn::If

Returns one value if the specified condition evaluates to true and another value if the specified condition evaluates to false.

Using the Fn::If conditional I can then utilise the ACM certificates specified in the Mappings if the SpecifiedSSLCert condition returns true. If the SpecifiedSSLCert condition returns false it uses the certificate that was passed through into the template via our tooling.

{
  "SSLCertificateId" : {
    "Fn::If" : [
      "SpecifiedSSLCert",
      { "Fn::FindInMap" : [ "RegionMap", { "Ref" : "AWS::Region" }, "SSLCertificateId"] },
      { "Ref" : "SSLCertificateId" }
    ]
  }
}

Conclusion

The more and more I use CloudFormation the more voodoo and trickery I come across. All of these little bits add up and collectively make it such a simple yet powerful tool.

By using mappings and conditionals it helps make your templates modular and allows for flexibility, which then increases re-useability.