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
- 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; } - 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.jsJot down the ETag value returned on the JSON response.
Test
- 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 } } - 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 DEVELOPMENTLook 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