Configuring AWS Edge Compute with Lambda@Edge

Jonathan Stock
Coding in the Cloud
8 min readJun 19, 2020

Add HTTP security headers to a response

This tutorial is a continuation of Building a Simple, Secure Website in AWS with a GoDaddy Domain Name, where you’ll be using the simple website you built using AWS S3, Route 53 and CloudFront to create an AWS Lambda function that adds security headers.

Lambda is an AWS compute service that lets you run code without provisioning or managing servers. It fits within the serverless category. Lambda@edge takes the compute function of Lambda and pushes it into the CloudFront edge infrastructure so you can run code closer to users of your application without spinning up servers to do it.

Lambda uses Node.js but you don’t need to know it for this tutorial. We’ll provide the code for the function.

This tutorial will build a function on Lambda@Edge that inserts security headers into your website’s HTTP response. Security headers are instructions developers code into their web server to inform the client’s browser what it can and can’t do. They follow Content Security Policy standards that have been created to prevent hackers from exploiting vulnerabilities in website code. In today’s day of well-known website exploits, it’s a pretty important to understand and use them.

This is also a great example of the power of serverless and edge compute. Instead of coding security header rules in the web server, Lambda@Edge can do it for you, in the middle of the HTTP request. This has advantages such as being able to apply and adjust security policy on the fly without re-configuring the web server.

Let’s get started!

Step 1: Create a Lambda@Edge Function

Source: Tutorial: Creating a Simple Lambda@Edge Function

  1. Sign in to the AWS Management Console and open the AWS Lambda console at https://console.aws.amazon.com/lambda/

Important: Make sure that you’re in the US-East-1 (N. Virginia) Region (us-east-1). You must be in this Region to create Lambda@Edge functions.

  1. Choose Create function.

2. On the Create function page, choose Use a blueprint, and then filter for the CloudFront blueprints by entering cloudfront in the search field. The Keyword : cloudfront is shown, and all the blueprints that are tagged for CloudFront are listed.

Note: CloudFront blueprints are available only in the US-East-1 (N. Virginia) Region (us-east-1).

3. Choose the cloudfront-modify-response-header blueprint as the template for your function.

Enter the following information about your function:

Name — Enter a name for your function.

Execution role — Choose how to set the permissions for your function. To use the recommended basic Lambda@Edge permissions policy template, choose Create a new role from AWS policy templates.

Role name — Enter a name for the role that the policy template creates.

Policy template — Lambda automatically adds the policy template Basic Edge Lambda permissions because you chose a CloudFront blueprint as the basis for your function. This policy template adds execution role permissions that allow CloudFront to run your Lambda function for you in CloudFront locations around the world. For more information, see Setting IAM Permissions and Roles for Lambda@Edge.

4. Choose Create function. Lambda creates the function, and on the next page you see your function configuration.

5. In the Designer section of the page, choose your function name, as shown in the following image. In this example, the function name is SecurityHeader.

6. Scroll down to the Function code section of the page, as shown in the following image.

7. Replace the template code with a function that modifies the security headers that your origin returns. The code below is what AWS provided in their online tutorial. Let’s try it out and see how it works with our website.

‘use strict’;
exports.handler = (event, context, callback) => {
//Get contents of response
const response = event.Records[0].cf.response;
const headers = response.headers;
//Set new headers
headers[‘strict-transport-security’] = [{key: ‘Strict-Transport-Security’, value: ‘max-age= 63072000; includeSubdomains; preload’}];
headers[‘content-security-policy’] = [{key: ‘Content-Security-Policy’, value: “default-src ‘none’; img-src ‘self’; script-src ‘self’; style-src ‘self’; object-src ‘none’”}];
headers[‘x-content-type-options’] = [{key: ‘X-Content-Type-Options’, value: ‘nosniff’}];
headers[‘x-frame-options’] = [{key: ‘X-Frame-Options’, value: ‘DENY’}];
headers[‘x-xss-protection’] = [{key: ‘X-XSS-Protection’, value: ‘1; mode=block’}];
headers[‘referrer-policy’] = [{key: ‘Referrer-Policy’, value: ‘same-origin’}];
//Return modified response
callback(null, response);
};

8. Choose Save to save your updated code.

Step 2: Add a CloudFront Trigger to Run the Function

Now that you have a Lambda function to update security headers, configure the CloudFront trigger to run your function to add the headers in any response that CloudFront receives from the origin for your distribution.

  1. In the Designer section of the page, choose CloudFront, as shown in the following image.

2. Scroll down to the Configure triggers section of the page, then choose Deploy to Lambda@Edge.

3. On the Deploy to Lambda@Edge page, under Configure CloudFront trigger, enter the following information:

Distribution: The CloudFront distribution ID to associate with your function. In the

drop-down list, choose the distribution ID.

Cache behavior: The cache behavior to use with the trigger. For this example, leave the

value set to *, which means your distribution’s default cache behavior. For more information, see Cache Behavior Settings in the Values That You Specify When You Create or Update a Distribution topic.

CloudFront event: The trigger that specifies when your function runs. We want the security headers function to run whenever CloudFront returns a response from the origin. So in the drop-down list, choose Origin response. For more information, see Adding Triggers for a Lambda@Edge Function.

4. Under Confirm deploy to Lambda@Edge, select the check box to acknowledge that the trigger will be deployed and run your function in all AWS locations.

5. Choose Deploy to add the trigger and replicate the function to AWS locations worldwide. Then, if necessary, close the Deploy to Lambda@Edge page.

Wait for the function to replicate. This typically takes several minutes.

You can check to see if replication is finished by going to the CloudFront console and viewing your distribution status as shown below. Wait for the distribution status to change from In Progress back to Deployed, which means that your function has been replicated.

Step 3: Verify the Function Runs

Now that you’ve created your Lambda function and configured a trigger to run it for a CloudFront distribution, check to make sure that the function is accomplishing what you expect it to. In this example, we check the HTTP headers that CloudFront returns, to make sure that the security headers are added.

To verify that your Lambda@Edge function adds security headers

  1. Enter your domain www.your-domain.com
  2. Open your browser’s Web Developer toolbar.
  3. Choose the Network tab.
  4. Reload the page to view your image, and then choose an HTTP request on the left pane. You see the HTTP headers displayed in a separate pane.
  5. Look through the list of HTTP headers to verify that the expected security headers are included in the list. For example, you might see headers similar to those shown in the following screenshot.

Step 4: Troubleshooting: Modifying security headers

If you are following the steps from the prior tutorial Building a Simple, Secure Website in AWS with a GoDaddy Domain Name, the image in our website does not load. We will troubleshoot in the following steps.

  1. Verify the index.html still works by going to S3 and clicking the URL of the file. This will request the website directly from the S3 bucket, bypassing CloudFront and the Lambda function.

The webpage is loading correctly (with the image). This confirms that the CloudFront Lambda@Edge function that you just coded is doing something to disrupt the HTTP request for that image. It’s likely that the Security headers we added in the above Lambda@Edge function need to be tweaked to work with our website.

2. To investigate which headers may be disrupting the website, go back to the browser with the tab of www.your-website.com, open developer tools and select console.

Notice that the image that is not loading is listed along with an explanation confirming that the content security policy we just added in the Lambda@Edge function is blocking the download of the image.

I did some quick research on Content Security Policy and found that the Content Security Policy image.src ‘self’; restricts the browsers ability to download images and may be the reason our webpage is unable to load the image from the S3 bucket. Here is the code from the AWS function that needs to be changed:

headers[‘content-security-policy’] = [{key: ‘Content-Security-Policy’, value: “default-src ‘none’; img-src ‘self’; script-src ‘self’; style-src ‘self’; object-src ‘none’”}];

Mozilla’s developer guide suggests changing this to a wildcard img-src *;

to allow the images to be loaded from anywhere.

3. Edit the function code as noted below, by navigating back to Lambda, selecting your function and navigating to the Function Code text editor and changing img.src ‘self’; to img.src *; and click save.

4. Test the website by entering www.your-domain.com

5. If the image now appears, repeat step 3 above by going to developer tools and confirming that the security headers are being added to your webpage. If yes, then the new Lambda@Edge SecurityHeaders function is now working properly, adding security headers to your website dynamically, as a user requests the webpage from your S3 bucket.

--

--