Being able to create a static website hosted on AWS S3 and fronted by Amazon CloudFront has become the serverless standard these days. Both of these AWS services facilitate a lot of the heavy lifting for you by giving you performant web hosting, with features like caching at edge locations closer to your end users, and security features like mitigating DDoS attacks.
One thing that required a little more effort then I’d prefer was the ability to add security headers to responses. That used to have to be done through Lambda@Edge which, while still serverless, ideally it should be reserved for more computationally involved origin resolution or response manipulation. Today you can now do that with Amazon CloudFront Functions.
Amazon CloudFront Functions provide short lived simple JavaScript functions that can manipulate your response/request. Some use cases could be url rewrites/redirects, access authorization and what I will show you here, header manipulation. See Danilo Poccia’s blog post for more details around Amazon CloudFront Functions.
Keep reading to see how we add the following security headers to our requests using AWS Cloudformation and Amazon CloudFront Function
- Strict-Transport-Security
- Content-Security-Policy
- X-Content-Type-Options
- X-Frame-Options
- X-XSS-Protection
Create your Serverless function
In CloudFormation there are a few things you need to define. 1) The name of your function, in my case add-security-headers
. 2) that you want this to be published automatically. 3) Your function configuration which includes what run time it will use, which at the time of writing this only includes cloudfront-js-1.0
. And lastly 4) your actual code. See the complete resource definition below
addSecurityHeadersFunction:
Type: AWS::CloudFront::Function
Properties:
Name: add-security-headers
AutoPublish: true
FunctionConfig:
Comment: Adds security headers to the response
Runtime: cloudfront-js-1.0
FunctionCode: |
function handler(event) {
var response = event.response;
var headers = response.headers;
// Set HTTP security headers
// Since JavaScript doesn't allow for hyphens in variable names, we use the dict["key"] notation
headers['strict-transport-security'] = { value: 'max-age=63072000; includeSubdomains; preload'};
headers['content-security-policy'] = { value: "default-src 'none'; img-src 'self'; script-src 'self'; style-src 'self'; object-src 'none'"};
headers['x-content-type-options'] = { value: 'nosniff'};
headers['x-frame-options'] = {value: 'DENY'};
headers['x-xss-protection'] = {value: '1; mode=block'};
// Return the response to viewers
return response;
}
Link your CloudFront Distribution
Now you can update your existing CloudFront Distribution by associating the function to your CacheBehavior as below and deploy your cloudformation. In my example below I’m importing the ARN from a separate template that includes all my CloudFront Functions.
FunctionAssociations:
- EventType: viewer-response
FunctionARN: !ImportValue
'Fn::Sub': ${CloudFrontStackName}-AddSecurityHeadersFunction
Test your implementation
Once completed head over to your terminal window and use curl to test out our headers:
ivonne@my-machine ~ % curl -i https://static.ivonneroberts.com
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 87
Connection: keep-alive
Date: Fri, 28 May 2021 23:59:19 GMT
Last-Modified: Fri, 28 May 2021 23:06:46 GMT
Etag: "7875df45e56965139099615f6c5c907b"
Accept-Ranges: bytes
Server: AmazonS3
Via: 1.1 3dc5af024af63cc0e8b9cf31fd852ecf.cloudfront.net (CloudFront)
Content-Security-Policy: default-src 'none'; img-src 'self'; script-src 'self'; style-src 'self'; object-src 'none'
Strict-Transport-Security: max-age=63072000; includeSubdomains; preload
X-Xss-Protection: 1; mode=block
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
As you can see our 5 security headers are now displayed:
Content-Security-Policy: default-src 'none'; img-src 'self'; script-src 'self'; style-src 'self'; object-src 'none'
Strict-Transport-Security: max-age=63072000; includeSubdomains; preload
X-Xss-Protection: 1; mode=block
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Conclusion
Adding a Amazon CloudFront function is pretty simple and you can see the updates almost immediately. If you would like to see the full CloudFormation templates head on over my github (link below). Feel free to modify it to fit your use case. As always if you have any questions reach out!
Github: [https://github.com/ivlo11/serverless-patterns/tree/main/static-site-with-cloudfront-function]
Documentation: [https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cloudfront-functions.html]
CloudFormation Documentation: [https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudfront-function.html]
CloudFront Function Sample Code: [https://github.com/aws-samples/amazon-cloudfront-functions]
Top comments (1)
Very nice read! Thank you. I also did a deep dive on how we secured our platform to get a great score on a security review. I will link it here if you are interested to have a look.