Blog

Hardening AWS hosted websites' security using AWS CloudFront functions

19 Jun 2022

This is a short guide to harden AWS hosted websites injecting security headers in their HTTP response using AWS CloudFront functions.

Have you got AWS CLI version 2 installed and configured? If you haven’t, please follow this guide and get it.

Build

  1. Create a text file named security-headers.js with this code.
    function handler(event) {
     var response = event.response;
     response.headers["strict-transport-security"] = {
         value: "max-age=15768000; includeSubDomains"
     };
     response.headers["x-frame-options"] = {
         value: "deny"
     };
     response.headers["x-xss-protection"] = {
         value: "1; mode=block"
     };
     response.headers["permissions-policy"] = {
         value: "accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), cross-origin-isolated=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(), geolocation=(), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), navigation-override=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=()"
     };
     response.headers["x-content-type-options"] = {
         value: "nosniff"
     };
     response.headers["content-security-policy"] = {
         value: "default-src 'none';style-src 'self';script-src 'self';img-src 'self';"
     };
     response.headers["referrer-policy"] = {
         value: "no-referrer-when-downgrade"
     };
     return response;
    }
    
  2. Upload the file to AWS using aws cloudfront create-function like this:
    aws cloudfront create-function --name security-headers --function-config Comment="Security headers",Runtime="cloudfront-js-1.0" --function-code fileb://security-headers.js
    

    Jot down the ETag value returned on the JSON response.

Test

  1. Create a text file named event-object.json with this code.
    {
     "version":"1.0",
     "context":{
        "eventType":"viewer-response"
     },
     "viewer":{
        "ip":"1.2.3.4"
     },
     "request":{
        "method":"GET",
        "uri":"/index.html"
     },
     "response":{
        "statusDescription":"OK",
        "statusCode":200
     }
     }
    
  2. Test the function before issuing this command replacing Exxxxxxxxx with the ETag you jotted down when you build the function.
    aws cloudfront test-function --name security-headers --if-match Exxxxxxxxx --event-object fileb://event-object.json --stage DEVELOPMENT
    

    Look for the field FunctionOutput on its response. You should see the injected headers in it if the function is correct.

Publish

To publish the function, just run this:

aws cloudfront publish-function --name security-headers --if-match Exxxxxxxxx

where Exxxxxxxxx must be replaced with the ETag you jotted down when you build the function.

Deploy

I recommend to use jq for JSON data filtering. With jq you can get just a property from a long JSON response. You can get it from here.

An AWS CloudFront function is associated with a website including its ARN on the AWS CloudFront distribution configuration. To do so, we need to get and save the following data into shell variables:

Distribution Id

distributionId=$(aws cloudfront list-distributions | jq -r ".DistributionList.Items[] | {Id,AliasICPRecordals} | select(.AliasICPRecordals[].CNAME==\"example.com\")" | jq -r .Id) 

replacing example.com with the name of the website you want to inject the security headers.

ETag

ETag=$(aws cloudfront describe-function --name security-headers | jq -r ".ETag")

ARN

ARN=$(aws cloudfront describe-function --name security-headers | jq -r ".FunctionSummary.FunctionMetadata.FunctionARN")
  • Get the current AWS CloudFront distribution configuration
    aws cloudfront get-distribution-config --id $distributionId --output yaml > dist-config.yaml
    
  • Replace ETag with IfMatch. This can be done with sed like this
    sed -i 's/ETag/IfMatch/g' dist-config.yaml
    
  • Open the file dist-config.yaml and edit the tag FunctionAssociations like this
FunctionAssociations:
  Items:
    - EventType: viewer-response
      FunctionARN: xxxxxxx
  Quantity: 1

replacing xxxxxxx with the value you get from running echo $ARN.

  • Update the AWS CloudFront distribution
    aws cloudfront update-distribution --id $distributionId --cli-input-yaml file://dist-config.yaml
    

Verify

After a few minutes, use curl to see the security headers you set

curl -IX GET https://example.com

replacing https://example.com with your website