During the deployment of our frontend to CloudFront we encountered the problem of not being able to configure the HTTP Security Headers, which is an essential configuration for reducing the attack surface of web applications. We resolved this issue using Amazon’s new Lambda@Edge functions to attach the headers before the response is sent to the clients.
What are HTTP Security Headers, and why should I use them?
If you request a website from a web server, the server responds with the content (e.g. HTML or image) and a variety of HTTP Headers. These headers contain information like the content type of the response or the version of the webserver. A special category of headers is the HTTP Security Headers, attached to the web server’s response to enforce a certain set of security rules in the client’s browser.
The configuration of the security headers is an essential step in securing your web application against certain types of attacks and reduces the attack surface of your application. Attacks like Click-Jacking, Cross-Site-Scripting (XSS) and Cross-Site-Request-Forgery (CSRF) can be effectively mitigated by the right configuration HTTP Security Headers. For more details about the different HTTP Security Headers please refer to our wiki.
How to set HTTP Security Headers in CloudFront?
As mentioned before, we are relying on the content delivery network (CDN) CloudFront to host our frontend, which is located in an S3-Bucket. This enables the commonly known advantages of a heavily reduced load time and latency by distributing to a variety of local caching servers. However, as a security company, we are not compromising our applications’ security and required the ability to follow best practices like the configuration of the HTTP Security Headers.
Unfortunately, neither CloudFront nor S3 supports the configuration of HTTP Security Headers out of the box. There is the possibility to setup Cross-origin resource sharing (CORS) in S3, but this does not suffice to implement all best practices. Luckily Amazon released the relatively new Lambda@Edge, which allows implementing a (more or less) easy solution.
Lambda@Edge allows the implementation of Lambda functions, which are distributed to CloudFront’s caching servers and executed at “the edge” (= caching servers). Four different events could trigger the execution of the function (see Lambda@Edge Documentation):
- Viewer request: After CloudFront receives a request from a viewer.
- Origin request: Before CloudFront forwards the origin’s request (in our case, the S3-Bucket).
- Origin response: After CloudFront receives the origin’s response (in our case, the S3-Bucket).
- Viewer response: Before CloudFront forwards the response to the viewer.
The last event type is the one we need to attach the HTTP Security Headers. Whenever a viewer requests a CloudFront file, it locates the local cache file or fetches it from the S3-Bucket, which is our origin. Then Lambda@Edge intercepts the response before it is forwarded to the viewer and executes our Lambda function, which attaches the HTTP Security Headers. Finally, the altered response is forwarded to the viewer.
How to configure it?
Warning: Please be aware that an error in the Lambda function will cause your CloudFront distribution to crash and become unavailable! Do not use your production CloudFront distribution for the initial setup and testing.
Step 1: Create the Lambda function
- Open the AWS console and select the us-east-1 region.
- Navigate to Lambda in the AWS console.
- Click on Create Function and choose the CloudFront-modify-response-header blueprint.
Step 2: Configure the CloudFront trigger
- Select the appropriate Distribution ID for your CloudFront distribution.
- Select the CloudFront Event to Viewer Response.
- Check Enable trigger and replicate.
- Click on Next.
Step 3: Add the function
- Provide a name for your new Lambda function.
- Replace the existing code with the one below and adjust the headers to your needs.
- Select Choose an existing role if you already have an IAM role.
- Select Create a new role from template(s) if you have no IAM role yet and want to get started quickly.
- Provide a name for the new role.
- Under Policy, the template adds the Basic Edge Lambda permissions policy.
Step 4: Review
- Review your new Lambda function
- Enable it 🙂
What about the Content-Security-Policy?
We intentionally did not set the Content-Security-Policy (CSP) header via the Lambda function. Instead, we are using a Meta-tag in our HTML file. The reason for this decision was that the HTTP Security Headers configured above are not changing frequently. However, the CSP has to be adjusted regularly to accommodate the addition of new external services. Furthermore, we want to keep the CSP as strict as possible, which requires a different policy for our development, test and production system. It proved to be easier to dynamically adjust the Meta-tag within the frontend development workflow instead of maintaining multiple or a dynamic Lambda function.
Below you find an example of a stringent CSP. You’ll most probably need to add several sources for styles, scripts and remote connections to adjust the policy for your web application.
You have now increased your CloudFront distribution’s security by attaching several HTTP Security Headers to the viewer response and thereby implementing a security best practice. You can verify your configuration on pages like Mozilla Observatory.
- AWS Documentation — Cross-Origin Resource Sharing (CORS)
- AWS Documentation — Lambda@Edge
- Serving custom headers from static sites on CloudFront/S3 with Lambda@Edge (deprecated)
- Tutorial: Using CORS with CloudFront and S3