Dave Kerr Software

Secure Your Static CloudFront Website With Security Headers Using Lambda@Edge

March 08, 2020

Using CloudFront to host your static website can be a very cost-effective and scalable solution, but it does require a little extra work to follow some common website security best practices. Using Mozilla’s great Observatory tool, you can get a security grade for your website and identify which headers you can add to your CloudFront responses that will close some of these security gaps. I will walk through the headers I added to this blog’s Cloudfront distribution, how they improved security, and how my site’s Observatory security grade improved.

The first Observatory scan result yielded some not-so-great results:

Observatory Score Failure

My site got a failing grade due to the following 5 missing headers:

Let’s examine how browsers use each of these headers to improve security, and how we can update our Lambda@Edge Function code to always ensure that these headers are returned from our CloudFront distribution. All Lambda@Edge code will be Node.js and the functions will be attached to the OriginResponse CloudFront event.

Strict-Transport-Security

The Strict-Transport-Security header tells the browser that it should never load your site using HTTP and should instead convert any HTTP attempts into HTTPS requests. Adding this header will help protect your end user from scenarios where a malicious man-in-the-middle (say, when connected to public wifi) is proxying & reading the user’s traffic. Telling your user’s browser to always use HTTPS protects against these potential future user sessions in untrusted environments.

Lambda@Edge:

response.headers['strict-transport-security'] = [{
  key: 'Strict-Transport-Security',
  value: 'max-age=63072000; includeSubdomains; preload'
}];

X-Content-Type-Options

The X-Content-Type-Options header tells the browser to respect the MIME type indicated by the server and not try to infer (or “sniff”) the MIME type. This particular header is not required for my blog since it protects against malicious user-uploaded content from being interpreted by the browser as a different file type - however, including it is trivial and is still best practice to include, and sometime in future this site may support user-uploaded content.

Lambda@Edge:

response.headers['x-content-type-options'] = [{
  key: 'X-Content-Type-Options',
  value: 'nosniff'
}];

X-Frame-Options

The X-Frame-Options header tells the browser whether or not the site is allowed to be embedded in a <frame>, <iframe>, <embed> or <object> element. Unless you have a specific need to have your site embedded in this fashion, it is best practice to deny frame embedding to protect against clickjacking.

Lambda@Edge:

response.headers['x-frame-options'] = [{
  key: 'X-Frame-Options',
  value: 'DENY'
}];

Referrer-Policy

The Referrer-Policy header tells the browser what information to provide as a referer header (sent to destination) when the user clicks on a link on your site. If the destination of the link is a third party server, then your site may be subject to leaking any information included in the current user’s URL to the third party server via the referer header. To protect against this, you can set a Referrer-Policy of same-origin to include referer information when the destination is the same server the user is already visiting.

Lambda@Edge:

response.headers['referrer-policy'] = [{
  key: 'Referrer-Policy',
  value: 'same-origin'
}];

Content-Security-Policy

The Content-Security-Policy (CSP) is the most important security header for your website, and helps to protect against a variety of Cross-Site Scripting (XSS) attacks by restricting how and where your site’s content can be loaded. Due to the way my Gatsby site is currently being built, I was not able to restrict the CSP to its most secure level - but I will follow up with a secondary post on how to update your Gatsby site to enable that. For the time being, I am setting script-src and style-src to allow unsafe-inline and for all others restricting to self (with one additional exception being data: for base64-encoded images).

Lambda@Edge

response.headers['content-security-policy'] = [{
  key: 'Content-Security-Policy',
  value: "default-src 'none'; img-src 'self' data:; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; object-src 'none'"
}];

Summary

After adding these headers to my OriginResponse lambda@edge function, I re-ran the Observatory scan with the following result:

Observatory Score Passing

Much better! Not the best score possible, but definitely a big improvement. To get to an A rating I will need to further restrict my Content Security Policy.

In conclusion, Mozilla’s Observatory tool is a great way to quickly identify missing or misconfigured security headers on your static website - and with Lambda@Edge you can ensure that all responses from your CloudFront distribution include them to improve security for your users.