DEV Community

Ivonne Roberts for AWS Community Builders

Posted on • Updated on • Originally published at blog.ivonneroberts.com

Add Security Headers to your Serverless Static Site

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;
      }
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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]

Happy Coding!

Top comments (1)

Collapse
 
stefannastic profile image
Stefan Nastic

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.