Amazon S3 itself does not do SSL for a static website, but you can get SSL from Amazon CloudFront. In theory this should not cost much more than regular S3 since Amazon does not charge for moving S3 data to CloudFront, so your S3 request charges are replaced by CloudFront request charges, but AWS charges are really hard to unravel, so I can’t be sure of that.
You can connect an S3 bucket to CloudFront in two different ways, one is pretty simple to implement, the other is a bit more complex but allows for greater security. For these examples I am assuming the S3 bucket already exists and behaves as you would want the site to behave.
In both cases you must log in to a sufficiently authorized IAM user or root user and you will need an SSL certificate configured to work with your website on S3. First, make sure you are in the us-east-1 region (“N. Virginia” in the navigation bar at the top), which is the only region with free certificates. Go to the AWS Certificate Manager and get started with provisioning a certificate. “Request a public certificate” and give it your domain name. Follow the instructions for validating your ownership of the domain. Wait for the status to change from “Pending Validation” to “Issued”, which can take a while.
Then run this “stack” to create the CloudFront instance. Once the creation of the stack is done, make note of the URL in the output tab, this is the URL you will need to use as the value of the appropriate CNAME in your DNS records for your custom domain.
Once your CloudFront distribution has been created, click on it to go to the “Distribution Settings”, then edit the “General” settings. Here you will add your domain name in the “Alternate Domain Names” and choose a “Custom SSL Certificate” which should be the same certificate you created above. Click the “Yes, Edit” button to save these changes. Note it will take a few minutes for these changes to be deployed.
One thing you may want to change is the default TTL (time to live) for your cached objects. To do so, go to your distribution settings, choose the “Behaviors” tab, choose the default behaviors, and click the “Edit” button. I usually set the minimum TTL to 0, the maximum TTL to 86400 (24 hours), and the default TTL to 180 (3 minutes). Don’t forget to save with the “Yes, Edit” button. You can increase that default once you are done debugging, but remember that any changes to your site may have to wait that long before they are seen by others.
Simple: Let S3 handle the webish stuff
If your static site really does not need security, but only needs SSL to fit into the new always SSL web environment, then you can connect S3 to CloudFront in a way that forces S3 to keep handling and resolving web requests. It is not clear to me how this impacts pricing; it may be that this method undermines the free S3 to CloudFront retrievals and results in each request generating both S3 and CloudFront charges, so keep an eye on the costs.
You will need to know your S3 endpoint URL for this method. Click on your bucket in the buckets list, then click on the “Properties” tab, click on the “Static website hosting” item, and then copy the “Endpoint” URL.
To use this method you simply have to edit the “Origins and Origin Groups” of the CloudFront distribution create by the stack above. Click the checkbox next to the only origin, then click the “Edit” button. Change the “Origin Domain Name” to the S3 endpoint URL you copied. Save this change by clicking the “Yes, Edit” button.
That’s it! Your site should now work as it did in S3, but now with SSL enabled.
Complex: Using Lambda
In order to secure your site fully, you may want to use an OAI to protect the requests between CloudFront and S3. While I won’t get into setting up OAI here, just know that if you do want to use OAI you will have to leave the S3 origin alone. This has a couple disadvantages: (1) CloudFront will not resolve naked directory requests to “index.html” and (2) CloudFront’s default error pages are very ugly.
To resolve to “index.html” automatically you will need to use an AWS Lambda function. Go to AWS Lambda and create a new function. Choose to “Author from scratch”, give it a name like defaultIndexHtml, and click the “Create function” button.
Replace the script in index.js with the following script…
'use strict';
var path = require('path');
exports.handler = (event, context, callback) => {
var request = event.Records[0].cf.request;
console.log('Request URI: ', request.uri);
const parsedPath = path.parse(request.uri);
var newUri = request.uri;
console.log('Parsed Path: ', parsedPath);
if (parsedPath.ext === '') {
newUri = path.join(parsedPath.dir, parsedPath.base, 'index.html');
}
console.log('New URI: ', newUri);
// Replace the received URI with the URI that includes the index page
request.uri = newUri;
// Return to CloudFront
return callback(null, request);
};
This script will look for any requests that do not have extensions and add /index.html to them. You can use whatever default file name you like. Make sure to click the “Save” button before trying the next step.
Now you add a trigger by clicking the “+ Add trigger” button. Select “CloudFront” as the trigger configuration and then click the “Replay to Lambda@Edge” button. If you’ve moved very quickly you might get to this step before the necessary AWS role has been deployed. If you get a complaint about the role, just wait a few minutes and try again. Once the trigger is added, it can also take a few minutes for CloudFront to update and include the lambda function. Once it does, your naked directory requests should resolve properly.
To improve the error pages, you can return to your CloudFront distribution settings, click on the “Error Pages” tab, and create a custom error response. You can point to any page of your static S3 site as the landing page for errors.
A note about the cache
CloudFront does cache pages, and while I do advise that you reduce the period of the cache above, you might forget to do this or otherwise find the cache gets in the way.
You can invalidate portions of the cache in the CloudFront distribution settings from the “Invalidations” tab. However, one thing can be very hard to invalidate: an empty directory. If you happen to test a naked directory with a URL like http://aws.tenseg.net/test before you have the default index pages set up, then your browser will download a zero length file instead of showing the index page. This zero length file gets cached. I found that I could not get rid of it by invalidating the directory with test and I had to instead invalidate the whole cache with *.
This should not be a problem once the index pages work.