Chris Martin

Web Technology, IT Infrastructure, and Other Puzzles

Securing AWS CloudFront with Free SSL Certificates from Let's Encrypt

Let's Encrypt is a new certificate authority that provides SSL/TLS certificates for free. Learn how to use its agent to create your own certificates for static websites on AWS S3 and CloudFront in this tutorial.

Note that at the time of writing, Let's Encrypt is in public beta and under rapid change. Please send me a comment if you notice any problems with the tutorial and I will correct it.

CloudFront Pricing

Also note that using CloudFront with HTTPS has pricing differences compared to using S3 static websites.

Overview

The Let's Encrypt project provides an agent tool that automates certificate management for a web server. Typically you can run the agent on the web server host itself, but in this guide we will be using CloudFront and S3 which does not have a runtime environment.

For environments where the agent cannot run, a manual workaround exists:

  1. User runs the agent in "manual mode" on local computer
  2. User inputs the domain name(s) for the certificate
  3. The agent pauses, and returns instructions on how to verify each domain
  4. User uploads a verification file to each webserver for each domain
  5. User continues the agent process, which verifies the domains
  6. Agent verification succeeds: Agent writes the certificate files on local computer
  7. User configures the webservers for HTTPS with the certificate files

The sequence flow for verifying custom domains in AWS S3/CloudFront, specifically, looks like the following:

Note that we will be securing two domain names in CloudFront:

  • domain.com : Naked domain (canonical)
  • www.domain.com: WWW subdomain, redirects to naked domain

If you only have one domain to configure, adjusting the process is easy. The setup for the second domain can be skipped.

Configure the AWS S3 Buckets

Head over to the Amazon documentation for a great walkthrough in setting up an insecure static website with S3 and Route 53 using custom domains. We will continue from where the AWS documentation ends, securing the domains in CloudFront and re-routing Route 53 to CloudFront.

Warning

When following the AWS docs, make sure the S3 Bucket name is the same as your domain name if using Route 53, or else the S3 Website Endpoint may not display as an option in the Route 53 Alias Target dropdown.

Once you are able to route your domains to the S3 buckets using HTTP, you're ready to continue below...

Let's Encrypt Agent Setup

You will be running the Let's Encrypt agent on your local workstation. The following software is required to be installed:

  1. Install the latest AWS Command Line Interface. This will be used to transfer the certificates to AWS and interact with the S3 buckets.

  2. Install the Docker Toolbox. The easiest cross-platform (Win/Mac) method for running the Let's Encrypt agent is to use Docker. Run the included Docker Quickstart Terminal (Bash shell).

  3. In the terminal, make a few directories to store the Let's Encrypt certificate files:

    $ mkdir -p letsencrypt/etc letsencrypt/lib
    $ cd letsencrypt
    
  4. (optional) Download the scripts shown in this tutorial into the letsencrypt/ folder:

    $ git clone https://gist.github.com/6c964fc3b099240f8563.git . 
    $ rm -rf .git  # remove the local git repo
    

Make a Backup

Be sure to back up this new letsencrypt folder after following this guide, as you will need it to revoke or update your certificates in the future.

Running the Agent

Run a Docker container from the Let's Encrypt Docker image, mounting the two directories that we created previously:

$ docker run -it --rm --name letsencrypt \
  -v "${PWD}/etc:/etc/letsencrypt" \
  -v "${PWD}/lib:/var/lib/letsencrypt" \
  quay.io/letsencrypt/letsencrypt:latest \
  certonly --manual -d domain.com -d www.domain.com  
  # replace the domains with your own

On first run it will take a few minutes to download the Docker image. Once complete, the container should start and a prompt will display:

Let's Encrypt agent interface

Enter your email address, and answer the next prompts:

  • Agree to Terms of Service
  • Allow IP to be logged

The agent will then enter a loop to verify each domain...

Be Ready To Stop

When you see:

Press ENTER to continue

...don't press ENTER just yet.

Separate commands need to be run in another terminal before proceeding.

The interface will display the following output:

Make sure your web server displays the following content at
http://domain.com/.well-known/acme-challenge/MWvD4U4PS9KOWQfG3udU-qoE5KOMVQzYPylUes5Gf55 before continuing:

MWvD4U4PS9KOWQfG3udU-qoE5KOMVQzYPylUes5Gf55.CKpe3m51zZGqRMHdykOUM1rXjyoNOyAvWNmmLUWt321

...
Press ENTER to continue

This information will be used in the next steps. Take note of the URI:

http://domain.com/.well-known/acme-challenge/MWvD4U4PS9KOWQfG3udU-qoE5KOMVQzYPylUes5Gf55

...and the content string:

MWvD4U4PS9KOWQfG3udU-qoE5KOMVQzYPylUes5Gf55.CKpe3m51zZGqRMHdykOUM1rXjyoNOyAvWNmmLUWt321

We now need to add this verification endpoint to S3 for the naked domain.com domain...

Verifying the First Domain

In a separate terminal, use the following bash script to transfer the verification endpoint to your S3 bucket:

Example usage (notice the input we use here is from the Agent output above):

$ le-s3-auth-upload.sh

Let's Encrypt Auth URI: 
  http://domain.com/.well-known/acme-challenge/MWvD4U4PS9KOWQfG3udU-qoE5KOMVQzYPylUes5Gf55

Let's Encrypt Auth Content: 
  MWvD4U4PS9KOWQfG3udU-qoE5KOMVQzYPylUes5Gf55.CKpe3m51zZGqRMHdykOUM1rXjyoNOyAvWNmmLUWt321

AWS S3 Bucket Name: 
  domain.com
...

The script will verify that the file was successfully transferred to S3 by sending a GET request to the URI.

We are ready to continue the Let's Encrypt agent process in the first terminal and Press ENTER to continue.

Verifying the Second Domain

The same procedure above that was used for the first domain can be run again for the second www. domain name.

But before running the le-s3-auth-upload.sh script for the second domain, read below:

Disable Bucket Redirects

The AWS "static website" documentation instructs the user to configure the www.domain.com S3 bucket for redirecting traffic to the domain.com domain.

Temporarily disable redirects for this bucket. Change the "Static Website Hosting" setting to "Enable website hosting".

After verification is complete, change the "Static Website Hosting" setting back to "Redirect all requests to another hostname": domain.com.

After continuing the verification process, the agent should display the following and exit:

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/domain.com/fullchain.pem. Your cert will
   expire on ????-??-??. To obtain a new version of the certificate in
   the future, simply run Let's Encrypt again.
 - If you like Let's Encrypt, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le

Your new certificate files will be located under the local ./etc/live/domain.com/ directory, based on the docker volume mappings we used earlier.

Uploading the Certificate to AWS

Now that the new certificate has been generated for both domains, upload it to AWS for use with CloudFront.

Use the following script, entering the naked domain name (domain.com) at the prompt:

Example usage:

$ le-aws-upload-cert.sh 
Current list of certificates in AWS
-----------------------------------
{
    "ServerCertificateMetadataList": []
}

Domain name: domain.com
...

After running the le-aws-upload-cert.sh script, a new certificate record should now exist in AWS named similar to: domain.com-20160102

Securing the CloudFront Distributions

Now configure two CloudFront Distributions:

  • The first distribution will have:
    • an origin that points to the static website domain of the domain.com S3 bucket 1
    • an alternate domain name set to domain.com
  • The second distribution will have:
    • an origin that points to the static website domain of the www.domain.com S3 bucket 1
    • an alternate domain name set to www.domain.com
  • In both distributions, choose the Custom SSL Certificate option and select the cert name that was used in the previous section of this guide
  • Consider also choosing the redirect HTTP to HTTPS option for both distributions

1 Do not use the default S3 bucket domain itself (ie. domain.com.s3.amazonaws.com) for the Origin Domain Name, but rather the full static website domain of the bucket (ie. domain.com.s3-website-us-east-1.amazonaws.com).

If you do not use static website domain as the origin you will run into problems accessing the default index.html for directory paths (ie. https://domain.com/archives/).

The AWS CloudFront docs have much better detail on setting up CloudFront distributions:

Configure DNS for CloudFront

The steps regarding CloudFront alternate domain names in the previous section are important for the Route 53 setup:

The name of your Amazon Route 53 hosted zone (such as example.com) must match an alternate domain name in the CloudFront distribution.

AWS docs

Configure the record set for each domain with an Alias that points to the corresponding CloudFront domain name. A dropdown should appear when clicking on the Alias Target text box with the CloudFront domain.

More information about routing to CloudFront distributions can be found in the AWS docs.

Double Check the S3 Buckets

Finally, in the www.domain.com S3 Bucket properties, ensure that the "Static Website Hosting" setting has the "Redirect all requests to another host name" option selected, and the correct redirect domain entered:

You can also safely delete the /.well-known/ folder and files in the buckets, which were left over from the Let's Encrypt verification process.

Conclusion

The free Let's Encrypt certificates are really nice, but I wish there weren't so many steps to accomplish this. As the Let's Encrypt agent improves, I hope to see more automation for AWS and other cloud providers.

Set a Reminder

The Let's Encrypt certificates have a 90 day lifetime. Set a calendar reminder to renew the certificate. To renew, re-run the agent and follow the same steps in the guide, pointing the CloudFront distributions to the new certificate.

And don't forget to make backups of your certificate files!

If you found this article useful, I'd love to hear it. Send me a tweet @c_g_martin.

References