ik

Ivan Kusalic - home page

Adding HTTPS Support to Static Site Hosted in S3

In the first post I showed how to host a blog with Octopress on Amazon’s S3. This post will expand on that explaining how to add HTTPS support to a static website hosted in S3.

Motivation

Why should you serve the content over HTTPS1?

Every single page on the internet should support HTTPS. Ideally all internet traffic should be encrypted. Martin Fowler explained why better than I could.

In my particular case I’ve also decided to add the PGP key to the about page. And there would be no point in hosting public key without a secure connection.

Rough Sketch

If you’re trying to add HTTPS support, the problem you are facing is S3’s lack of the same.

This can be mitigated if you additionally use CloudFront, Amazon’s CDN solution. CloudFront supports HTTPS for custom domains.

This does not even need to incur any additional costs. There is one caveat to though – clients accessing the content via HTTPS need to support Server Name Indication.

In my case this is not a problem – the blog is ment to be read by humans and not accessed by random outdated programming libraries. Validate that your situation also allows you to use SNI. Otherwise you’ll need to pay for dedicated IPs. More details can be found in CloudFront documentation.

The basic idea is quite simple: keep the webpage in S3, just put CloudFront in front2.

Ingredients

I’ll assume your webpage is already being served from a S3 bucket. If that’s not the case read this post.

To enable HTTPS support you’ll need to:

  • get an HTTPS certificate
  • set up Amazon’s CloudFront
  • update DNS records

In case you also want to use the root domain (e.g. to redirect your root domain to “www” subdomain), you’ll additionally need to transfer DNS records to Amazon’s Route53

Although the idea is quite simple, there are a few bumps along the road.

Certificate

First you’ll need to buy a certificate. There are infinite choices available, with prices varying by a few orders of magnitude. Literally. After a bit of research and an HN question later I’ve decided to get an PositiveSSL cert. It is cheap, people seem to have good experience with it and it covers both root domain and “www” subdomain.

Be careful to use 2048-bit key as AWS does not support 4096-bit keys. I found out the hard way3, but luckily Namecheap, allowed me to reissue the certificate without additional costs.

Obtaining the certificate

My assumption is that you’re using an Unix-like machine.

First you’ll need to generate CSR – Certificate Signing Request and accompanying key. Run the following command:

1
openssl req -new -newkey rsa:2048 -nodes -out <your_domain>.csr -keyout <your_domain>.key

Use CSR to order the certificate.

You should validate the certificate once your chosen Certificate Authority has issued it.

If you want to use both root domain and “www” subdomain check that the certificate includes both in “Subject Alternative Name” section.

To inspect your new certificate run:

1
openssl x509 -in <your_certificate>.crt -text -noout | less

To use the certificate with AWS it needs to be in PEM format. If needed you can convert you certificat to PEM format by running:

1
openssl x509 -in <non_pem_cert> -out <pem_cert> -outform PEM

You’ll also need to create a certificate chain. This is the chain of intermediate certificates that connect your certificate to a top-level certificate. Read more here.

Next you’ll need to upload the certificate, key and certificate chain to the AWS. You can do this with AWS CLI.

Once you have AWS CLI set up, run:

1
aws iam upload-server-certificate --server-certificate-name <your_domain> --certificate-body file://<full_path_to_your_cert> --private-key file://<full_path_to_your_key> --certificate-chain file://<full_path_to_your_chain> --path /cloudfront/

Validate that AWS sees your certificate:

1
aws iam list-server-certificates

CloudFront

Now that you have the certificate ready, you’ll need to set up the AWS CloudFront.

Login to AWS Console and select CloudFront service.

You need to create new CloudFront Distribution. Select “Web” delivery method and fill out the form.

Enter the bucket endpoint in the “Origin Domain Name” field. This was a bit confusing as I couldn’t select the proper entry in the drop-down list. I’ve entered it manually as per this post. (for reference mine was: “www.ikusalic.com.s3-website-eu-west-1.amazonaws.com”.)

If you’ve successfully uploaded the certificate, you should be able to select it in “Custom SSL Certificate (stored in AWS IAM)” field.

Enter your domain(s) in “Alternate Domain Names (CNAMEs)”. If you’re going to use both root domain and “www” subdomain, enter both.

Select “Custom SSL Client Support: Only Clients that Support Server Name Indication (SNI)” if applicable.

I’ve enabled logging to S3 bucket, the same one I already use to log the S3 access, but with dedicated prefix.

I’ve also set “Viewer Protocol Policy: Redirect HTTP to HTTPS”, as I want to enforce HTTPS traffic.

Note that it will take some time to crate the distribution. For me it took approximately 20 minutes.

After you’ve created the distribution go to “Origins” in distribution settings and check that “Origin Protocol Policy” is set to “HTTP Only” – S3 can only use HTTP.

DNS records

Next you need to set up the DNS records. The exact setup is specific to your use-case. You probably want to have at least one CNAME record that points to the distribution’s domain name.

With this you’re done unless you plan on using root domain as well.

I wanted to redirect my root domain (ikusalic.com) to “www” subdomain (www.ikusalic.com). This counts as using the root domain.

Migrating DNS records to Route53

The problem with root domain is that you need to have a DNS record that points to distribution’s domain name. For subdomains this would be a CNAME record. But you can’t have CNAME record on the root domain.

There is a solution, a non-standard ALIAS record. At this point you probably want to read this great StackOverflow answer. Maybe you also want to understand why ALIAS record is not standard DNS record. If that’s the case, read the following post.

As both DNS records and Cloudfront are under the Amazon’s control, there is no problem with using infamous ALIAS record.

Finally it’s the time to migrate your DNS records to Route53. Read AWS documentation about the topic.

In my particular case this ment creating the following DNS records:

  • CNAME from “www” subdomain to CloudFront distribution’s domain name
  • A type ALIAS from root domain to CloudFront distribution’s domain name
  • AAAA type ALIAS from root domain to CloudFront distribution’s domain name
  • MX record to preserve my existing mail setup
  • TXT record to preserve my existing mail setup

And then migrating DNS from Namecheap to Route53.

Redirecting root domain traffic

By this point all traffic was encrypted (because of “Viewer Protocol Policy: Redirect HTTP to HTTPS” setting in CloudFront).

I also want visitors to use “www” subdomain to access this blog. So the last thing I needed to do was redirecting root domain traffic to “www” subdomain.

This can be done with the following snippet of JavaScript:

Redirecting to “www” subdomain
1
2
3
4
5
6
7
8
9
10
function redirectNonHttpsWww() {
    var l = this.location.href;
    if (l.indexOf("https://www.") == -1 && l.indexOf("0.0.0.0:4000") == -1) {
        var newL = l.replace(/.*ikusalic.com/, "https://www.ikusalic.com")
        console.log("current location: " + l + ", redirectiong to: " + newL);
        this.location.replace(newL);
    }
}

redirectNonHttpsWww();

(exception for 0.0.0.0:4000 enables me to test page locally without HTTPS)

With this I was done. Hopefully by this point you’ve managed to secure your S3 website as well.

Conclusion

Enabling secure traffic for S3-backed website was a bit more involved than I initially thought it’s going to be, but in the end it was manageable enough.

I hope you’ve succeeded in applying this guide to your particular setup. As always, whatever the reason is, feel free to drop me an email.


  1. As broken X.509 public key infrastructure is, currently we don’t have anything better

  2. Pun intended :)

  3. I was greeted with a frinedly message: “AWS Error Code: InvalidViewerCertificate, AWS Error Message: The specified SSL certificate doesn’t exist in the IAM certificate store, isn’t valid, or doesn’t include a valid certificate chain.”