DE

Lambda@Edge to Configure HTTP Security Headers for CloudFront

In this article:

During our frontend deployment to CloudFront, we encountered the problem of not configuring 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 HTTP Headers. These headers contain information like the content type of the response or the version of the webserver. A particular category of headers is the HTTP Security Headers, attached to the web server’s response to enforce a specific 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 reducing your application’s attack surface. For example, Click-Jacking, Cross-Site Scripting attacks, and Cross-Site Request Forgery (CSRF) can be effectively mitigated by the proper configuration of HTTP Security Headers. 

If you want to run a vulnerability assessment that checks your security headers, just use our tool for free.

How to Set HTTP Security Headers in CloudFront?

As mentioned before, we rely 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 various local caching servers.

However, as a security company, we are not compromising our applications’ security and require 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 of setting up 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 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 to the HTTP Security Headers. Whenever a viewer requests a CloudFront file, it locates the local cache file or fetches it from the S3-Bucket, 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 HTTP Security Headers in CloudFront?

Warning: Please be aware that an error in the AWS 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.
Configure triggers in Cloudfront - Lambda screenshot
Configure triggers in Cloudfront – Lambda screenshot

Step 3: Add the function

  • Provide a name for your new AWS 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.

Or alternatively

  • Select Create a new role from template(s) if you have no IAM role yet and want to start quickly.
  • Provide a name for the new role.
  • Under Policy, the template adds the Basic Edge Lambda permissions policy.
'use strict';

exports.handler = (event, context, callback) => {

    const response = event.Records[0].cf.response;
    const headers = response.headers;

    // Add security headers
    const securityHeaders = [
        [{
            'value': 'max-age=31536000',
            'key': 'Strict-Transport-Security'
        }],
        [{
            'value': 'deny',
            'key': 'X-Frame-Options'
        }],
        [{
            'value': '1; mode=block',
            'key': 'X-XSS-Protection'
        }],
        [{
            'value': 'nosniff',
            'key': 'X-Content-Type-Options'
        }],
        [{
            'value': 'strict-origin-when-cross-origin',
            'key': 'Referrer-Policy'
        }]
    ];

    // Add all headers of the array to the response object in the correct format
    for(let header of securityHeaders) {
       headers[header[0].key.toLowerCase()] = header;
    }

    callback(null, response);
};

Step 4: Review

  • Review your new AWS Lambda function
  • Enable it ðŸ™‚

What about The Content-Security Policy?

We intentionally did not set the Content-Security-Policy (CSP) header via the AWS Lambda function. Instead, we are using a Meta-tagin our HTML file. This decision was made because 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, testing, and production system. Therefore, it proved 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 probably need to add several sources for styles, scripts, and remote connections to adjust the policy for your web application.

<!doctype html>
<html>
<head>
  <meta http-equiv="Content-Security-Policy" content="default-src 'none';">
</head>
<body>
    ...
</body>
</html>

Conclusion

You have now increased your CloudFront distribution’s security by attaching several HTTP Security Headers to the viewer response, thereby implementing a security best practice. In addition, you can verify your configuration on pages like Mozilla Observatory.

Sources

Get a quick security audit of your website for free now

We are analyzing https://example.com
Scanning target https://example.com
Scan status: In progress
Scan target: http://example.com/laskdlaksd/12lklkasldkasada.a
Date: 27/05/2023
Crashtest Security Suite will be checking for:
Information disclosure Known vulnerabilities SSL misconfiguration Open ports
Complete your scan request
Please fill in your details receive the
quick security audit by email.
Security specialist is analyzing your scan report.
То verify your identity please provide your phone/mobile:
Thank you.
We have received your request.
As soon as your security audit is ready, we will notify you.