RNG

thoughts on software development and everything else

404s and heartbreak

2018-11-24

Amazon S3 has a very easy-to-use static website hosting option.

But the downside is, this only works if you make the entire bucket public. That means that all of your assets are available over plain HTTP with the bucket link (e.g. http://ronniegane.kiwi.s3-website-ap-southeast-2.amazonaws.com/blog/index.html instead of https://ronniegane.kiwi/blog/)

To enforce HTTPS, the only option is to make the bucket private and allow access only through CloudFront as I described in my previous post.

The downside of that is that if you go to a page that doesn’t exist (https://ronniegane.kiwi/dsfjnlkjgdn/), instead of a nice 404 you get an ugly “access denied” error from S3:

S3 access denied error

So what to do?

Luckily, it is pretty easy to use your custom error page. Amazon provides pretty good instructions.

  1. Go to your CloudFront distribution settings page
  2. “Error Pages” > “Create Custom Error Response”
  3. Choose the code (404) in our case, select “Customize Error Response” and provide a path to your custom 404 page in s3.

Creating a Custom Error Response in CloudFront

There is one catch, and that is to make sure that your CloudFront distribution is getting 404s from S3, and not 403s. If your CloudFront Origin Access Identity only has the s3:GetObject permission (the default set up), every missing file will be a 403. You need to give the s3:ListBucket permission to allow it to know what files actually exist in the bucket.

This permission applies to the bucket itself rather than elements in the bucket, so the Resource value is the bare ARN of the bucket (as opposed to <arn>/* for the s3:GetObject permission).

{
    "Version": "2008-10-17",
    "Id": "PolicyForCloudFrontPrivateContent",
    "Statement": [
        {
            "Sid": "1",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ABC123"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::ronniegane.kiwi/*"
        },
        {
            "Sid": "2",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ABC123"
            },
            "Action": "s3:ListBucket",
            "Resource": "arn:aws:s3:::ronniegane.kiwi"
        }
    ]
}

Easy peasy!