Cloudfront: Adding HTTP security headers with Response Headers Policies
By Laurynas Tumosa
- 4 minutes read - 746 wordsAnother day, another blog post about adding security headers to Cloudfront HTTP responses. Actually, it’s my third post about this topic, which is the same as the number of AWS services that can be used to modify Cloudfront headers. As of today we have: Lambda@Edge, Cloudfront Functions, and the newly introduced Response Headers Policies
Again, ability to easily add HTTP headers to Cloudfront was very commonly requested feature:
https://t.co/BGzCyi8LtU headers without needing to use lambda@edge would be great
— Laurynas Tumosa (@LaurynasT2) November 18, 2020
2. Better support for react/angular apps hosted on s3 backend
Almost all Cloudfront distributions require custom security headers and adding them with Lambda@Edge simply wasn’t a very elegant solution. So, you can imagine how happy I was when I heard that AWS announced Cloudfront Functions. I tried and wrote about this feature. However, it was not what I expected. Firstly, it still requires writing and maintaining a piece of code(although extremely simple). Secondly, it is not suitable for javascript applications (react/angular/vue) hosted on S3 (I explained why in my 2nd post).
Therefore, after migrating my Lambda@Edge to Cloudfront Functions, security headers weren’t added as expected. I checked with AWS support but they just confirmed that this functionality is by design and I was forced to go back to Lambda@Edge.
Enter a new era with Cloudfront Response Headers Policies
As of today Cloudfront Response Headers Policies are also available. Looking through the launch blog it looks like it should be able to add response headers even if Cloudfront origin returns HTTP 403 status code. But it’s time to check if it actually works!
As a starting point I have already a cloudfront distribution with a custom error response for 403 error codes:
# Curl on index.html security headers added by CF Function as expected
# Not relevant HTTP headers were redacted from the responses
$ curl -I https://d33vf9zl6um5ej.cloudfront.net/
HTTP/2 200
server: AmazonS3
content-security-policy: default-src 'self'
# Curl on object that doesn't exist on s3, no security header added
# by CF Function.
$ curl -I https://d33vf9zl6um5ej.cloudfront.net/sfdsfs
HTTP/2 200
server: AmazonS3
As you can see, Cloudfront Function adds the response security header only when object exists in the s3 origin. Therefore, security header is not added to the second response.
Let’s check if adding Cloudfront Response Headers Policies(OK, I will call it RHP from now) would help. I can associate an RHP with a Cloudfront distribution by clicking on the Behaviors tab and then clicking edit:
# Not relevant HTTP headers were redacted from the responses
$ curl -I https://d33vf9zl6um5ej.cloudfront.net/
HTTP/2 200
x-xss-protection: 1; mode=block
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
x-content-type-options: nosniff
strict-transport-security: max-age=3153600
content-security-policy: default-src 'self'
$ curl -I https://d33vf9zl6um5ej.cloudfront.net/sfdsfs
HTTP/2 200
x-xss-protection: 1; mode=block
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
x-content-type-options: nosniff
strict-transport-security: max-age=3153600
We can see that the security headers are successfully added for both responses. However, as you can see the second response doesn’t include the content-security-policy header. This happens because the managed SecurityHeadersPolicy leaves this header empty.
To fix this problem, I can simply create my own custom Security Headers Policy and assign it to my distribution. After doing that, I can finally get rid of the Cloudfront Function code because it’s no longer needed!
Summary
It looks like AWS listened to feedback and provided a really simple and intuitive way to add HTTP headers to Cloudfront responses/requests. It was very easy to setup and it worked even for scenarios where Cloudfront functions failed me.
I haven’t tested the performance of this but it should probably be faster then lambda@edge and maybe even that CF functions.
It was very productive week for me and this is my third blog post this week. If you liked the post please check two other posts: how to block access to certain aws regions and limit your blast radius and another post where I go through the differences between RDS read replicas and Multi-AZ deployments.