Content Security Policy with Amazon CloudFront: Part 1
Exploring Content Security Policy (CSP) issues when deploying a React web application using Amazon CloudFront.

A seasoned colleague of mine who is learning modern web application development with React, Redux, and webpack, was lamenting how complicated web development has become; can be summed up in the article Everything Easy is Hard Again.
Having invested a lot of time learning, writing, and teaching on these topics I reassured him that once you get over the initial learning hump, things get easier. I was feeling a little smug.
Later that day, was chatting up a parent at a soccer match and he was explaining how he was carefully crafting a Content Security Policy (CSP) for a WordPress site that he was working on. I looked at him with a blank stare; I had heard of the term but really had not dug into it. He encouraged me to test my sites using Observatory by Mozilla.
Observatory by Mozilla has helped over 125,000 websites by teaching developers, system administrators, and security professionals how configure their sites safely and securely.
— Mozilla Team

What?! My carefully crafted web application got an F.
So, I dug into what CSP is all about…
A primary goal of CSP is to mitigate and report XSS attacks. XSS attacks exploit the browser’s trust of the content received from the server. Malicious scripts are executed by the victim’s browser because the browser trusts the source of the content, even when it’s not coming from where it seems to be coming from.
— Mozilla Team — Content Security Policy (CSP)
and then worked to increase my grade (striving for a perfect 100 score).
Setup
For a simple example while learning, I started with a web application generated by create-react-app and served it as a web site using Amazon S3.
note: If you continued down this path (we will not), you would finally want to create a DNS CNAME record pointing your subdomain, e.g., www.my-domain.com to the Amazon S3 website address.
Running this web application through the Observatory, I got the same F score.
HTTPS
The first bit of advice the Observatory gave was:
Wondering where to start?
Adding HTTPS protects your site’s visitors from tracking, malware, and injected advertising.
— Observatory by Mozilla
It was a bit more involved to get the web application to be running over HTTPS. The steps generally were:
- Using the Amazon S3 bucket we created earlier (actually you only need to grant the public read permission; not needing to enable website hosting)
- Requesting a certificate for your subdomain using Amazon Certificate Manager.
- Create a Amazon CloudFront distribution using the Amazon S3 bucket and Amazon certificate.
- Creating a CNAME record pointing your subdomain to the generated Amazon CloudFront distribution address, e.g. dxxxxxxxxxx.cloudfront.net (keep your TTL short while tweaking your CSP setup; you will need to change this record often)
note: When creating the distribution, we use the option to Redirect HTTP to HTTPS.
Even with HTTPS enabled, the site still gets an F; but the Observatory gives us encouragement and a next step.
Sidebar into Lamda@Edge
Many of the improvements that we will apply for the remainder of this series will come in the form of HTTP response headers, e.g., Strict-Transport-Security.
The Amazon S3, Certificate, and CloudFront solution does support HTTP response headers; albeit in a slightly round-about way. The solution involves running an Amazon Lamda function to add the HTTP response headers; triggered in this context is referred to as Lamda@Edge.
I found the following article an excellent overview on this topic: Serving custom headers from static sites on CloudFront/S3 with Lambda@Edge.
Even with this article, I found the process unusually complicated. The trick to getting Lamda@Edge working for me involved:
- Creating an Amazon Lamda function from the cloudfront-modify-response-header blueprint (search for cloudfront when creating the function)
- While creating the Amazon Lamda function, also creating a new Amazon role from the Basic Edge Lambda permissions policy template
- Publish a version of the Amazon Lamda function.
- Re-creating the Amazon CloudFront distribution using the Amazon S3 bucket, Amazon certificate, and the versioned Amazon Lamda function triggered off the Viewer Response event type.
At this point, we have an Amazon Lamda function that we can extend to add specific HTTP response headers.
HTTP Strict Transport Security
The specific advice that the Observatory gave was:
Fantastic work using HTTPS! Did you know that you can ensure users never visit your site over HTTP accidentally?
HTTP Strict Transport Security tells web browsers to only access your site over HTTPS in the future, even if the user attempts to visit over HTTP or clicks an http:// link.
— Observatory by Mozilla
We implement the Strict-Transport-Security (aka, HSTS) feature by adding the following HTTP response header.
Strict-Transport-Security: max-age=31536000
Observations:
- The max-age is the required number of seconds (in this case 3153600 seconds equals a year) that the browser will remember that the site requires HTTPS.
- I did not use the optional includeSubDomains or preload directives in this example as they have broader consequences; explained best at HSTS Preload List Submission.
The specific changes to the Amazon Lamda function are:
'use strict';exports.handler = (event, context, callback) => {
...
const headerNameDst = 'Last-Modified';
const headerHSTS = 'Strict-Transport-Security';
if (headers[headerNameSrc.toLowerCase()]) {
...
}
headers[headerHSTS.toLowerCase()] = [{
key: headerHSTS,
value: 'max-age=31536000',
}];
callback(null, response);
};
Observations:
- In order to use the updated code, you will need to publish a new version of the Amazon Lamda
- As you cannot change the Amazon Lamda configuration of a CloudFront distribution, you will need to re-create it
- As re-creating a CloudFront distribution generates a new distribution address, you will need to re-create your CNAME record (good we had short TTLs)
Woohoo, I moved from an F to a D+ (really?!).

The Observatory does give us next steps though.
Next Steps
In the next article, Content Security Policy with Amazon CloudFront: Part 2, we continue to follow the guidance given by the Observatory striving for our perfect 100 score.