Serving static content from S3 using CloudFront and Origin Access Identity
Tags: aws, cloudfront, s3, tutorial
Published: 2020-02-09 19:35:09

This tutorial demonstrates how to restrict access to an S3 bucket so that your html, css, and images, are only accessible through CloudFront.

> *This post is part of a wider series documenting my journey in migrating a traditional LAMP website over to a Single Page Application running on the Amazon Web Services platform. Here, I will focus on the static content and setting up CloudFront as my CDN.*

Using Amazon Simple Storage Service (**Amazon S3**) is a cheap and effective way to host static websites and other web content.

You can do so directly from the S3 console by enabling the Static website hosting feature and you’ll get a website of the form **http<nolink>://my-bucket-name.s3-website-eu-west-1.amazonaws.com**. You can also create an A-record in Route 53 to use your own custom domain.

![Basic set up — hosting a static website in S3](https://dev-to-uploads.s3.amazonaws.com/i/0spzc5iuzygafl4b0m6q.png)
<figcaption>Basic set up — hosting a static website in S3</figcaption>

Some solutions may stop here. For some, this is *good enough*. (but this is not the purpose of our article; it would be incredibly short if it was).

<hr>

More likely the solution will evolve toward serving content from edge cache locations using **CloudFront** — Amazon’s low-latency Content Delivery Network (CDN). Using a CDN both speeds up the distribution of content to visitors and will also reduce the overall cost for a busy site.

![Introducing CloudFront as our Content Delivery Network](https://dev-to-uploads.s3.amazonaws.com/i/5aicichnvl9tzg50jwe3.png)
<figcaption>Introducing CloudFront as our Content Delivery Network</figcaption>

Even with the CDN our visitors can still access the S3 static website directly, and the Solution Architect will now be asked **“how do we restrict access to the S3 bucket so that our html, css, and images, are _only_ accessible through CloudFront?”** (*this question* is the purpose of this tutorial).

![](https://dev-to-uploads.s3.amazonaws.com/i/h77gesq8igjdutnq1auy.png)

The answer is to use **Origin Access Identity** (OAI).

We can restrict public access to objects in the S3 bucket — as of today, this is the default setting when creating a new bucket — and we grant permission to the OAI to distribute the content through CloudFront to our visitors from around the world.  We no longer need to configure S3 to host a static website.

The steps we follow to achieve this solution are;

1. Create the S3 bucket with default settings and upload an index.html file (index.html will not be accessible directly from S3).
1. Create a CloudFront distribution with the S3 bucket as its origin (index.html still cannot be accessed).
1. Set up the OAI, and configure a policy that permits CloudFront to serve the index.html file (now it all works).

Steps 2 and 3 would normally be applied at the same time, but I’ll demonstrate separately to show the individual steps and how the OAI is the bit of magic sugar in the solution.

<hr>

**Step 1**. Create the bucket in S3 that will hold the static content and use all the default settings. The bucket and its objects are *not accessible* to the public.

![S3 Bucket holding our static website content](https://dev-to-uploads.s3.amazonaws.com/i/wz4o664wz5fwu26v3dov.png)
<figcaption>S3 Bucket holding our static website content</figcaption>

When we attempt to reach the index.html file in a browser, we get an Access Denied error as expected.

![Hitting index.html on S3 endpoint receives an Access Denied error](https://dev-to-uploads.s3.amazonaws.com/i/ndwlqywxp15k9d9wudaq.png)
<figcaption>Hitting index.html on S3 endpoint receives an Access Denied error</figcaption>

<hr>

**Step 2**. In CloudFront, create a Web distribution and select the S3 bucket as the origin. At this stage, leave everything else as the default settings, scroll to the bottom and create the distribution. You’ll have to wait until it’s deployed and as this can take 10 minutes, go grab a coffee...

![Our CloudFront distribution for our S3 origin](https://dev-to-uploads.s3.amazonaws.com/i/r3u6qf4xft1pq3zkqw9c.png)
<figcaption>Our CloudFront distribution for our S3 origin</figcaption>

Once its status has changed to “Deployed”, it’s ready. At this stage, the index.html page is not accessible on the CloudFront domain.

![Hitting index.html on CloudFront endpoint receives an Access Denied error](https://dev-to-uploads.s3.amazonaws.com/i/w1wzbr0zp4r900qfupcw.png)
<figcaption>Hitting index.html on CloudFront endpoint receives an Access Denied error</figcaption>

<hr>

**Step 3**. Create the Origin Access Identity and configure the policy in S3 that grants the OAI permission to access objects.

Since we’re doing this in two stages, we have to edit our existing Origin to access the OAI option; however, usually you would do this at the same time as creating your Web distribution and it’s on the initial list of options.

![Select our origin and click Edit](https://dev-to-uploads.s3.amazonaws.com/i/v5f3w0juxrisesab29w1.png)
<figcaption>Select our origin and click Edit</figcaption>

On the next page, select **Restrict Bucket Access**, allow Amazon to **Create a New Identity**, and choose **Yes, Update Bucket Policy**.

![Select the options and save](https://dev-to-uploads.s3.amazonaws.com/i/onjmnxoxztflj7lbviz5.png)
<figcaption>Select the options and save</figcaption>

Wait for the status to change to “Deployed” again, and then refresh the page and the index.html page will now be displayed.

![Our static content is now served correctly via CloudFront](https://dev-to-uploads.s3.amazonaws.com/i/vg78uhblyysabbgqd40a.png)
<figcaption>Our static content is now served correctly via CloudFront</figcaption>

All set. Success. 

:heavy_check_mark: CloudFront edge locations are serving our static content in S3. 
:heavy_check_mark: And no one can hit the content in S3 directly.

Check the direct S3 endpoint again just to be sure that remains blocked, and view the Bucket Policy in S3 that was added automatically to learn more.

<hr>

**What we didn’t cover today**. Most solutions are also likely to require;

* a custom domain that is configured in Route 53 and CloudFront; 
* an SSL certificate from Amazon Certificate Manager so that content can be served over https; 
* and have cache expiry limits set on the objects. 

{% medium https://medium.com/@davelms/using-a-custom-domain-in-cloudfront-with-an-ssl-certificate-and-route-53-253a72f51056 %}

In addition, if you have built a Single Page Application (SPA), like Angular or ReactJS, then you may need to configure CORS. 

Finally, we didn’t cover off creating signed URLs, which is useful if you are distributing paid-for content and want to limit access to your edge location caches.

These will be subject of further posts in this series.

<hr>