I previously wrote about Deploying a Jekyll website to AWS S3 with GitHub Actions and AWS CloudFormation. However, as I continue to learn more about AWS, the more tweaks I realize we can make.

What I want to walk through today is removing the public access to our S3 bucket which hosts our static site. We will configure an Origin Access Control to allow only CloudFront to access the S3 bucket. There’s one caveat to our use case, but we can resolve that using CloudFront Functions.

We’ll be performing these steps through the AWS Console, but you could likely tweak the CloudFormation template that we created in the previous article.

Disclaimer

The method we are implementing requires the use of a CloudFront function. The Always Free Tier includes 2 million function invocations per month. You should be able to determine cost based on the number of requests you are receiving per month, as each request will invoke our function.

Adding an Origin Access Control policy to the CloudFront distribution

Navigate to your Distribution and click Edit under the Settings. Now go to Origins and click Edit on your existing origin. Scroll down and change Origin access from Public to Origin access control settings (recommended). Now click Create new OAC. Leave all the values as their defaults and click Create.

Make sure your newly created Origin access control is selected in the dropdown.

Click the Copy policy button.

Clicking the conveniently placed Go to S3 bucket permissions link will open a new tab, so don’t forgot to go back and click Save changes on this page.

Updating S3 Bucket Policy

Scroll down to the Bucket Policy and click the Edit button. Paste in the policy you copied from the previous step and click Save changes At this point, we’ll be disabling both the static website hosting feature and public access on our S3 bucket.

Disabling static website hosting

Navigate to Properties of your S3 bucket and scroll down to Static website hosting, then click Edit. Go ahead and Disable the feature and click Save changes.

Disabling public access

Navigate to the Permissions of your S3 bucket and scroll down to the Block public access (bucket settings), then click Edit. Check Block all public access and click Save changes. Type confirm in the confirmation popup and click Confirm.

Now there’s one caveat to this method. With static website hosting, if we had subdirectories on our website, they would be served properly. Unfortunately with this method, we receive an Access Denied page.

Let’s fix that…

Setting up a CloudFront Function

Navigate to the CloudFront dashboard and click Functions, then click Create function.

Name your function, then leave the defaults selected, and click Create function.

Scroll down to Function code and paste the following code in the editor. This code will append index.html to URIs, which will give us the desired behavior.

function handler(event) {
    var request = event.request;
    var uri = request.uri;

    if (uri.endsWith('/')) {
        request.uri += 'index.html';
    } else if (!uri.includes('.')) {
        request.uri += '/index.html';
    }

    return request;
}

Click Save changes. Once you do this, you’ll see a gray pill labeled “Unpublished” next to the Publish tab. Click on Publish, then click Publish function.

Attach the CloudFront Function to a Distribution

Now, if you scroll down, you should see an Associated distributions section. Click the Add association button so that we can associate the function with our distribution.

Pick your distribution from the Distribution dropdown list.

Select Viewer request from the Event type dropdown list.

Choose Default(*) as your cache behavior.

Now click Add association.

That’s all there is to this. We’ve locked down our S3 bucket so that only CloudFront can access it.

An alternative to CloudFront functions would be to implement Lambda@Edge, however it would be more expensive (and overkill for our case) as you would pay for both the number of requests and the duration that your function runs.