How to provision a CloudFront distribution with an ACM Certificate using Cloud Formation

Toby Hede picture Toby Hede · Mar 10, 2016 · Viewed 10.2k times · Source

I am attempting to set a certificate in my CloudFrontDistribution using Cloud Formation.

My certificate has been issued via Certificate Manager. It has been approved, and I have validated that the certificate works by manual configuration directly through the CloudFront console.

Within my CloudFormation template, I have attempted to use both the Identifier and ARN values associated with the certificate in the IamCertificateId property:

"ViewerCertificate" : {
  "IamCertificateId" : "********",
  "SslSupportMethod": "sni-only"
}

But in both cases I receive the following error:

The specified SSL certificate doesn't exist, isn't valid, or doesn't include a valid certificate chain.

Reading the docs for the DistributionConfig Complex Type it looks like there is a 'ACMCertificateArn' property, but this does not seem to work via CloudFormation.

Any help would be appreciated.

Answer

wjordan picture wjordan · Mar 18, 2016

(Update: As of Aug 9 2016, AWS CloudFormation now supports ACM using the AcmCertificateArn property, so the custom resource described below is no longer needed.)


Although the AWS::CloudFront::Distribution resource hasn't been updated to support the ACMCertificateArn property yet, it is currently possible to use a custom CloudFormation resource to implement the functionality needed using the AWS API directly until the official resource is updated.

See Ryan S. Brown's post, CloudFormation To Build A CDN With (Free) Custom SSL where he describes his implementation of a Custom::CloudFrontAcmAssociation resource that associates an ACM certificate with a CloudFront distribution. The code is available at ryansb/acm-certs-cloudformation.

To use it, you need to make the CloudFormation resource's implementation available through an AWS Lambda function. Ryan's implementation is already published to a public S3 bucket, so you can reference this directly for testing purposes in your CloudFormation template like so:

"AcmAssociationFunction": {
  "Type": "AWS::Lambda::Function",
  "Properties": {
    "Handler": "cloudfront_associator.handler",
    "MemorySize": 128,
    "Runtime": "python2.7",
    "Code": {
      "S3Bucket": "demos.serverlesscode.com",
      "S3Key": "acm-certificate-resource-functions.zip"
    },
    "Role": {"Fn::GetAtt": ["ExecRole", "Arn"]},
    "Timeout": 300
  }
},

The Lambda::Function resource has a dependency on an IAM service Role and associated Policy to delegate the necessary permissions to the lambda function (the ExecRole reference above), so you need to add that too:

"ExecRolePolicies": {
  "Type": "AWS::IAM::Policy",
  "Properties": {
    "PolicyName": "ExecRolePolicy",
    "PolicyDocument": {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Action": [
            "acm:*",
            "cloudfront:List*",
            "cloudfront:Get*",
            "cloudfront:UpdateDistribution"
          ],
          "Resource": [ "*" ],
          "Effect": "Allow"
        },
        {
          "Action": [ "logs:*" ],
          "Resource": "arn:aws:logs:*:*:*",
          "Effect": "Allow"
        }
      ]
    },
    "Roles": [{"Ref": "ExecRole"}]
  }
},
"ExecRole": {
  "Type": "AWS::IAM::Role",
  "Properties": {
    "AssumeRolePolicyDocument": {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Action": ["sts:AssumeRole"],
          "Effect": "Allow",
          "Principal": {"Service": ["lambda.amazonaws.com"]}
        }
      ]
    }
  }
},

With the lambda function in place, finally add the Custom::CloudFrontAcmAssociation resource, providing the distribution ID, certificate ARN, and the custom resource lambda function's ARN:

"DistributionCertificateSetting": {
  "Type": "Custom::CloudFrontAcmAssociation",
  "Properties": {
    "DistributionId": {
      "Ref": "SiteCDN"
    },
    "CertificateArn": {
      "Ref": "AcmCertificate"
    },
    "ServiceToken": {
      "Fn::GetAtt": [
        "AcmAssociationFunction",
        "Arn"
      ]
    }
  }
},

tldr: copy all the code above into your CloudFormation template, set the appropriate SiteCDN and AcmCertificate properties (or edit the template with hard-coded values), and you should have a custom resource workaround until Amazon updates the official CloudFront resource.