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.
Cloudformation added this property but it is not documented. You can use like this easily:
"ViewerCertificate": {
"SslSupportMethod": "sni-only",
"AcmCertificateArn": "CERTIFICATE_ARN"
}
Be aware that the certificate must be created in us-east-1 region, if not it won't be accepted.
(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.
I had a properly created certificate (public key 2048 bits), uploaded with the full chain. What was more challenging was the certificate was being used without problem in other AWS services (public ELB).
I was also passing the certificate Id (I had also tried with ARN but that's incorrect) correctly.
In my case, the problem was the certificate had been created with a "path": "/". After I uploaded a new certificate (with different name) with "Path": "/cloudfront/", everything worked without problem.
aws iam upload-server-certificate \
--server-certificate-name cert_cf \
--certificate-body file://cert.crt \
--private-key file://cert.key \
--certificate-chain file://chain.pem \
--path /cloudfront/
Another valid approach I now use just creates the stack with the default certificate as long as the certificate is not issued (Inspired by this post)
It looks like
"Conditions": {
"HasAcmCertificate": {
"Fn::Equals": [
{
"Ref": "CloudfrontCertificateArn"
},
"NOT_ISSUED"
]
}
},
...
"Cloudfront": {
"Properties": {
"DistributionConfig": {
...
"ViewerCertificate": {
"AcmCertificateArn": {
"Fn::If": [
"HasAcmCertificate",
{
"Ref": "AWS::NoValue"
},
{
"Ref": "CloudfrontCertificateArn"
}
]
},
"CloudFrontDefaultCertificate": {
"Fn::If": [
"HasAcmCertificate",
true,
{
"Ref": "AWS::NoValue"
}
]
},
"SslSupportMethod": {
"Fn::If": [
"HasAcmCertificate",
{
"Ref": "AWS::NoValue"
},
"sni-only"
]
}
}
}
},
"Type": "AWS::CloudFront::Distribution"
},
Took a few days but found the answer with some help from AWS support.
The information for:
"ViewerCertificate" : {
"IamCertificateId" : "********",
"SslSupportMethod": "sni-only"
}
is found using the CLI "aws iam list-server-certificates":
{
"ServerCertificateId": "ASCAXXXXXXXXXXXXXX",
"ServerCertificateName": "devops.XXXXXXX.com",
"Expiration": "2017-03-10T15:00:33Z",
"Path": "/cloudfront/",
"Arn": "arn:aws:iam::XXXXXXXXXXX:server-certificate/cloudfront/devops.XXXXXXXXXXX.com",
"UploadDate": "2016-03-14T16:13:59Z"
},
Once I found that I added a variable cloudfront.CloudFrontCertificateId with the ServerCertificateId and fed it into the ViewerCertificate:
"ViewerCertificate" : {
"IamCertificateId" : {{ cloudfront.CloudFrontCertificateId }},
"SslSupportMethod": "sni-only"
}