Static Sites on CloudFront

If it's a static site, and not especially popular, why use CloudFront?

TLS

S3 buckets support TLS, but not with your domain name. If you want your own domain and TLS you need to put CloudFront, um, in front. Why bother with TLS for a static site? To get rid of the "⚠ Not secure" warning and to offer your visitors some privacy.

Certificate Manager certificates are free as long as they're only used on AWS services. You never get access to the private key, and you never worry about renewals.

You can also use Let's Encrypt with CloudFront, but there's literally no reason to, and I never finished writing a renewal Lambda function.

Index Pages

I like URLs that end in / and not /index.html. CloudFront will let you specify a default root object, but not deeper index pages. You can point CloudFront at an S3 website endpoint to get around this but there's no need to put a web server between CloudFront and S3 just to get clean URLs. S3 objects don't actually have folders (though the web console makes it look that way) so you can store an object at about/ (with the / as part of the key).

Your filesystem can't do that though, so neither can your static site generator. My solution was to cobble together S3 and Lambda to copy anything named $prefix/index.html to $prefix/ and have this function trigger on ObjectCreated events in the site bucket when the suffix is /index.html.

Here's the function I wrote for this, which I named Indice.

# Copyright Andrew Jorgensen
# SPDX-License-Identifier: MIT-0
import json

import boto3

INDEX = "index.html"
INDEX_LEN = len(INDEX)
CLIENT = boto3.client("s3")


def copy_index(record):
    print(json.dumps(record))
    region = record["awsRegion"]
    bucket = record["s3"]["bucket"]["name"]
    key = record["s3"]["object"]["key"]
    index_key = key[:-INDEX_LEN] or "/"
    CLIENT.copy(
        CopySource={"Bucket": bucket, "Key": key},
        Bucket=bucket,
        Key=index_key,
        ExtraArgs={"ACL": "public-read"},
    )


def lambda_handler(event, context):
    for record in event["Records"]:
        copy_index(record)

This works great, and I'm charged almost nothing for it. I understand I'm at some risk of being popular enough that it costs me something but the chance that happens to me is pretty slim.

social