Configuring a Content-Security-Policy with Lambda Edge

Recently I’ve been working on upgrading the security of my web applications. One of the easiest security features to add is a robust Content-Security-Policy.

According to MDN:

Content Security Policy (CSP) is an added layer of security that helps to detect and mitigate certain types of attacks, including Cross Site Scripting (XSS) and data injection attacks.
Configuring Content Security Policy involves adding the Content-Security-Policy HTTP header to a web page and giving it values to control what resources the user agent is allowed to load for that page. For example, a page that uploads and displays images could allow images from anywhere, but restrict a form action to a specific endpoint. A properly designed Content Security Policy helps protect a page against a cross site scripting attack.

Adding a CSP to your web application will show you just how many external resources are being loaded by other libraries. The first site I did had a number of js libraries running inside it like Google Maps, Stripe, Intercom and Google Analytics. When first adding the libraries I was really only aware of the api endpoints / classes that I wanted to use. Once I had my content security policy I saw all the image, javascript files, fonts, and even mp3 files that these libraries need to run correctly.

To configure my CSP, I used Lambda Edge. All of the hosting for my site was on AWS so it made perfect sense.

Step 1. Create Lambda Function

Image for post
Image for post

Provide a name and scroll down the the bottom.

Image for post
Image for post

Select create a new role from AWS policy templates
Give your role a name
Select the Basic Lambda@Edge option from the templates

Step 2. Add basic Content-Security-Policy

"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’; " +
"font-src ; " +
"form-action 'self'; " +
"frame-src ; " +
"media-src ; " +
"frame-ancestors 'self'; " +
"base-uri 'self'; " +
"manifest-src 'self'; " +
"img-src 'self'; " +
"style-src 'self' 'unsafe-inline'; " +
"script-src 'self'; " +
"connect-src ;"
}];
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);
};

Step 3. Configure CloudFront Distribution to use Lambda

Image for post
Image for post

Inside the selected distribution, click the Behaviours tab

Image for post
Image for post

Inside of behaviours, scroll to the bottom and select a new event from the Lambda Function Associations

Image for post
Image for post

Click Yes, Edit and your CloudFront distribution will automatically redeploy. You may need to manually invalidate the objects of your distribution as well.

Written by

Former golf instructor turned software engineer. 4 years ago I left my job teaching golf in Beijing to pursue my passion for writing code.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store