Skip to content

Ruairidh Codes

Securing an S3 bucket with Cloudfront and a Lambda

aws, devops2 min read


Recently at work, I needed to secure a component library we had built. It was displayed with Storybook, and deployed as a static site to an S3 bucket. This works really well, but we want to keep this collection of components internal to the company.

I had tried a few different ways of restricting access, but most were quite frustrating. For example, I could use the bucket policy to block everyone other than an allowed list of IPs which belonged to my colleagues. This doesn't scale very well though, since IP addresses frequently change, people travel, etc. I didn't want to keep needing to maintain a bucket policy full of random addresses.

The solution we ended up coming to was to put the S3 bucket behind an AWS Cloudfront distribution (effectively an Amazon CDN) and use a Lambda (a handy serverless function) to handle the auth.

Here's how to do it

Set up your bucket

Firstly you want your bucket with whatever it is you're providing access to. Ensure in your bucket properties, you set 'Static web hosting' to be available:

'Screenshot of S3 properties screen with static website hosting enabled'

You can have your public access settings on or off since they will be superseded by our later changes, but I always default to removing public access, just in case.

Set up CloudFront

Next up, we'll want to get CloudFront running. Create a new distribution and set the Origin Domain Path to your S3 bucket. I then enable GET, HEAD, OPTIONS as the allowed HTTP methods, and in the case of my work, pass in a custom SSL certificate matched to the domain we are using.

Once you've filled in your options, go ahead and create the distribution.

Now, go to the Distribution's settings and check the 'Origin and Origin Groups'. There should only be one Origin at this point which you can edit. Also, ensure you set your Default Root Object as 'index.html' since we're pointing to a static website in our case.

Select restrict bucket access and then create a new identity. The field should be automatically populated for you. Then check Yes, Update bucket policy so these changes are reflected in S3. Save your changes. The form should look similar to the image below:

'Screenshot of CloudFront origin policy settings'

Add your Lambda

Now we're accessing our bucket via CloudFront, we can secure it with a Lambda! This is super easy, just go to Lambdas, create a new function, and author it from scratch with the latest Node runtime.

In your function code, add the following:

1"use strict";
2exports.handler = (event, context, callback) => {
3 // Get request and request headers
4 const request = event.Records[0].cf.request;
5 const headers = request.headers;
7 // Configure authentication
8 const authUser = "user";
9 const authPass = "pass";
11 // Construct the Basic Auth string
12 const authString =
13 "Basic " + new Buffer(authUser + ":" + authPass).toString("base64");
15 // Require Basic authentication
16 if (
17 typeof headers.authorization == "undefined" ||
18 headers.authorization[0].value != authString
19 ) {
20 const body = "Unauthorized";
21 const response = {
22 status: "401",
23 statusDescription: "Unauthorized",
24 body: body,
25 headers: {
26 "www-authenticate": [{ key: "WWW-Authenticate", value: "Basic" }],
27 },
28 };
29 callback(null, response);
30 }
32 // Continue request processing if authentication passed
33 callback(null, request);

Thanks to lmakarov

The credentials are hardcoded in here. Adjust as you see fit. Lambdas also support network calls for viewer request/response events, so you can support checking credentials against an external source.

Now add a trigger to this code and select 'CloudFront' as per the image below:

'Screenshot of Lambda Triggers'

You will then need to deploy this to 'Lambda@Edge'. From here, make sure you have the correct distribution ID in the field, and set the CloudFront Event to 'Viewer request'.

Wait for this to propoagate via CloudFront.


That's it! You now have a secured S3 bucket which is accessed via CloudFront. Also worth noting that users cannot sneak around CloudFront if they know the S3 direct URL. Access gets restricted to CloudFront exclusively so you have to go through that, and the authentication we have put in place.

Found this useful? Tweet me @ruairidhwm and let me know!

© 2020 by Ruairidh Wynne-McHardy. All rights reserved.