Using Let's Encrypt certificates on AWS CloudFront
This is more or less a how-to on installing and renewing an SSL (or better, TLS) certificate, on Amazon Web Services (AWS) CloudFront (Dutch blogpost about Amazon AWS services).
These instructions are targetted to do it unattended, so you can automate it, for example with Let's Encrypt certificates.
It requires some digging in the AWS docs to find out how this all works. So here you go, full instructions! I will be using the non-existing subdomain "https://example.intracto.com" in this article.
Install the AWS CLI
Installing the AWS CLI is easy with the Python "pip" package manager.
pip install awscli
Note that AWS recently released "aws-shell". An interactive shell for working with the AWS CLI, featuring autocomplete and inline documentation. Very cool, but not further discussed in this article because it's an interactive shell and we are going for full automation.
Next, configure the AWS CLI and enable CloudFront preview stage. Configure will ask for an Access Key ID and Secret Access Key. You receive these values when you create a user in AWS Identity and Access Management (IAM). Configure will also ask for a default region, this is not important for this article. CloudFront and IAM are global so they do not require a region selection.
aws configure
aws configure set preview.cloudfront true
Configuring the initial certificate
Certificates are stored in AWS IAM. Make sure your user has the "iam:UploadServerCertificate" policy on the AWS IAM server-certificate/cloudfront/intracto-example ARN. You can create this policy easily with the "IAM Policy Generator" from the AWS web management console and attach it to your user.
Then upload the certificate:
aws iam upload-server-certificate \
--server-certificate-name intracto-example \
--certificate-body file:///etc/letsencrypt/live/example.intracto.com/cert.pem \
--private-key file:///etc/letsencrypt/live/example.intracto.com/privkey.pem \
--certificate-chain file:///etc/letsencrypt/live/example.intracto.com/chain.pem \
--path /cloudfront/
Go to your CloudFront distribution via the AWS web management console and choose the certificate named "intracto-example" as your custom certificate.
You are now using the Let's Encrypt certificate on AWS CloudFront.
Renew the certificate
For the renewal process, your user needs more policies. A more or less tied down, complete list is:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Stmt1451762030000",
"Effect": "Allow",
"Action": [
"iam:UploadServerCertificate"
],
"Resource": [
"arn:aws:iam::070742282322:server-certificate/cloudfront/intracto-example",
"arn:aws:iam::070742282322:server-certificate/cloudfront/intracto-example-new"
]
},
{
"Sid": "Stmt1451764648000",
"Effect": "Allow",
"Action": [
"cloudfront:ListDistributions"
],
"Resource": [
"*"
]
},
{
"Sid": "Stmt1451765213000",
"Effect": "Allow",
"Action": [
"cloudfront:GetDistributionConfig"
],
"Resource": [
"*"
]
},
{
"Sid": "Stmt1451765388000",
"Effect": "Allow",
"Action": [
"iam:ListServerCertificates"
],
"Resource": [
"arn:aws:iam::070742282322:server-certificate/"
]
},
{
"Sid": "Stmt1451765553000",
"Effect": "Allow",
"Action": [
"cloudfront:UpdateDistribution"
],
"Resource": [
"*"
]
},
{
"Sid": "Stmt1451765763000",
"Effect": "Allow",
"Action": [
"iam:DeleteServerCertificate"
],
"Resource": [
"arn:aws:iam::070742282322:server-certificate/cloudfront/intracto-example"
]
},
{
"Sid": "Stmt1451766901000",
"Effect": "Allow",
"Action": [
"iam:UpdateServerCertificate"
],
"Resource": [
"arn:aws:iam::070742282322:server-certificate/cloudfront/intracto-example",
"arn:aws:iam::070742282322:server-certificate/cloudfront/intracto-example-new"
]
}
]
}
If you have to renew the certificate, upload it under a new name, for example "intracto-example-new". It's not possible to overwrite an existing certificate.
Then you have to replace it in CloudFront. This feels a bit clumsy. First you need to get the key of the CloudFront distribution, and get the distribution config:
DISTRIBUTION=$(aws cloudfront list-distributions \
--query "DistributionList.Items[?Aliases.Items[0]=='example.intracto.com'].{DitributionId: Id}" \
--output text \
)
aws cloudfront get-distribution-config \
--id $DISTRIBUTION \
--output json | jq '. | .DistributionConfig' > intracto-example.json
The CLI is using JMESPath as the underlying JSON-processing library so dig into that to find out how the query syntax works.
Now get the ID of your old and new certificate:
OLDCERT=$(aws iam list-server-certificates \
--output text \
--query "ServerCertificateMetadataList[?ServerCertificateName=='intracto-example'].{IAMCertificateId: ServerCertificateId}" \
)
NEWCERT=$(aws iam list-server-certificates \
--output text \
--query "ServerCertificateMetadataList[?ServerCertificateName=='intracto-example-new'].{IAMCertificateId: ServerCertificateId}" \
)
Replace the certificate ID in the distribution config:
sed -i "s/$OLDCERT/$NEWCERT/" intracto-example.json
And upload the new distribution config to AWS. You need to get the version of the current configuration first, which is stored under the ETag:
DISTRIBUTION_VERSION=$(aws cloudfront get-distribution-config \
--id $DISTRIBUTION \
--query "{ETag: ETag}" \
--output text \
)
aws cloudfront update-distribution \
--id $DISTRIBUTION \
--if-match $DISTRIBUTION_VERSION \
--distribution-config file://intracto-example.json
Cleaning up
Finally, remove the old certificate and rename the new one. Not only because the number of certificates allowed in IAM is limited, but just to keep things clean.
aws iam delete-server-certificate \
--server-certificate-name intracto-example
aws iam update-server-certificate \
--server-certificate-name intracto-example-new \
--new-server-certificate-name intracto-example
This makes the renewal process complete. This is automatable, which is especially usefull with Let's Encrypt certificates that expire every 90 days.
See this article to learn more about using custom HTTPS certificates with cloudfront.