I’m adding security headers to my S3 and CloudFront hosted website — using Lambda@Edge. It’s pretty easy, here is how 👇
Table of contents
New function
In the AWS console; navigate to the Lambda service. Make sure the location is N. Virginia, this is required for Lambda@Edge.
Make a new function:
- Click Create function, top right
- Author from scratch
- Enter a function name
- Choose Node.js 12.x (latest one supported by Lambda@Edge)
- Click Create function
We only need one file; index.js
, and it looks like;
exports.handler = (event, context, callback) => {
const response = event.Records[0].cf.response;
const headers = response.headers || [];
// Website should only be access over HTTPS, never over HTTP
// We also allow browsers to use Google 'preloading' service.
headers['strict-transport-security'] = [{
key: 'Strict-Transport-Security',
value: "max-age=31536000; preload"
}];
// Tell the browser that the MIME types that we sent are
// correct and should not be questioned by the browser.
// This only applies to scripts and stylesheets.
headers['x-content-type-options'] = [{
key: 'X-Content-Type-Options',
value: "nosniff"
}];
// Dont allow the site to be rendered inside an iframe.
headers['x-frame-options'] = [{
key: 'X-Frame-Options',
value: "DENY"
}];
// Only send the shortened referrer to a foreign origin,
// full referrer to a local host.
headers['referrer-policy'] = [{
key: 'Referrer-Policy',
value: "strict-origin-when-cross-origin"
}];
// We don't need any features and APIs in the browser.
headers['permissions-policy'] = [{
key: 'Permissions-Policy',
value: "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=(), interest-cohort=()"
}];
callback(null, response);
};
Permissions
To have permission to deploy the function to the edge, we first need to add some trust relationships:
- Navigate to Identity and Access Management (IAM)
- Go to Roles
- Click the role for the function you just created, it will be named something like function_name-role-xxxxxxxx
- Go to the Trust relationships tab
- Click Edit trust relationship
- Add
edgelambda.amazonaws.com
to the Service array, making it look like this:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": [
"edgelambda.amazonaws.com",
"lambda.amazonaws.com"
]
},
"Action": "sts:AssumeRole"
}
]
}
Deploy
Alright — time to deploy! Navigate back to the Lambda function we created earlier.
On the function page;
- Click Add trigger
- Choose CloudFront
- Click Deploy to Lambda@Edge
- Leave it on Configure new CloudFront trigger
- Choose your distribution and cache behaviour
- Set CloudFront event to Origin response
- Check Confirm deploy to Lambda@Edge
Deployed!
If you now navigate to the CloudFront distribution and behaviour you selected, you should see it under Edge Function Associations.
Why the Origin response event?
From the AWS documentation:
- The function executes after CloudFront receives a response from the origin and before it caches the object in the response.
So CloudFront will cache the response from S3, with the added headers. Minimizing the cost of Lambda executions.
Redeploy
If you make any changes to the function, it needs to be redeployed to the edge.
You can do that on the function page:
- Click the Actions button, top right
- Click Deploy to Lambda@Edge
- Select Use existing CloudFront trigger on this function
- Click Deploy
Verify
After deploying it, you can verify that the security headers are being added on Security Headers, a project by Scott Helme.
Here are my results:
I also looked at implementing Content-Security-Policy, but it caused issues with video.js and lightgallery.js, and I haven’t taken the time to sit down and figure it out — yet.
Last commit 2024-11-11, with message: Add lots of tags to posts.