Whenever a request is made to your CloudFront distibution, you respond with the body as well as a set of headers. These headers let the recipient know details such as how long to cache the data for, content types and other details about the request.

Security Headers

You can also use these headers to control the security of the endpoint. You can tell the requester what it is and isn't allowed to do with the resource that you've returned.

To test whether your endpoint has any of the applicable headers, you can use the Mozilla Observatory tool. This will test your endpoint, give you a score for security and a breakdown of how you did in each test.

As you can see, this endpoint has almost no security on it. This means that people can make unsecured http requests to it, embed the content in an i-frame, and there is no protection against cross site scripting.

Adding the Required Headers with an Edge@Lambda

There are many ways to add headers to your endpoints, but the quickest and most effective is to use an Edge Lambda.

This Lambda will take responses coming from CloudFront and attach the headers we need. Start by creating a new Lambda in the console, making sure that it is in the us-east-1 region as this is the only region that this will currently work in.

When selecting the role, choose Create a new role from AWS policy templates, give your new role a name and select the Basic Lambda@Edge permissions (for CloudFront trigger) template.

In the Lambda, paste this code:

'use strict';
exports.handler = (event, context, callback) => {
    //Get contents of response
    const response = event.Records[0].cf.response;
    const headers = response.headers;

    //Set new headers
    headers['strict-transport-security'] = [
        { key: 'Strict-Transport-Security', value: 'max-age=63072000; includeSubdomains; preload' },
    ];
    headers['content-security-policy'] = [
        {
            key: 'Content-Security-Policy',
            value:
                "default-src 'none'; img-src 'self'; script-src 'self'; style-src 'self'; object-src 'none'",
        },
    ];
    headers['x-content-type-options'] = [{ key: 'X-Content-Type-Options', value: 'nosniff' }];
    headers['x-frame-options'] = [{ key: 'X-Frame-Options', value: 'DENY' }];
    headers['x-xss-protection'] = [{ key: 'X-XSS-Protection', value: '1; mode=block' }];
    headers['referrer-policy'] = [{ key: 'Referrer-Policy', value: 'same-origin' }];

    //Return modified response
    callback(null, response);
};

This is doing a lot of things so I'll briefly describe each of them

  1. strict-transport-security - ensuring any future requests are made to https, not http
  2. content-security-policy - controlling where the resources can be loaded from
  3. x-content-type-options - tells browsers not to load scripts with incorrect MIME types
  4. x-frame-options - controls whether you content can be loaded in an i-frame
  5. x-xss-protection - prevents cross-site scripting
  6. referrer-policy - fine-grained control over how and when browsers transmit the HTTP Referer header

Now that we have the Lambda code set up, we need to trigger the Lambda to run whenever CloudFront is about to send a response. To do this, you need to deploy a version of the Lambda by clicking actions and deploy version.

Now in that version you can click add trigger and select CloudFront. This then opens a menu where you can select the CloudFront Distribution ID that you want to add the headers too. You then need to change the event from Origin request to Origin response and tick the deploy to Lambda@Edge checkbox.

Once this has been deployed, you can rerun the Mozilla Observatory test and see how your score has changed.

This is a massive improvement, but is still missing redirect to https. This needs to be done in the CloudFront console its self.

Adding Redirect to HTTPS

Open up the CloudFront console, find your distribution and select Behaviours. In there you will likely have a single behaviour, and you can see that the Viewer Protocol Policy is set to HTTP and HTTPS. This needs to change.

Select the behaviour and click edit.  There are loads of setting in here but the only one we care about for now is Viewer Protocol Policy. We need to set it to Redirect HTTP to HTTPS and then save this by clicking Yes, Edit at the bottom of the page.

Retest Time!

Boom! 115/100 isn't too bad!

Deploying to More CloudFront Distributions

To make the same changes to another distribution it's much easier.

  1. Go into the Lambda and add another CloudFront trigger
  2. Go into CloudFront and change the Viewer Protocol Policy.

If you are wanting to add different headers onto your endpoints, you could either create a new Lambda with those headers, or modify the Lambda code to set the headers based on the distribution that is triggering it.

Doing this Serverlessly

Doing all of this is great but wouldn't it be nice to be able to have this all in serverless where we can quickly and easily deploy it.

We need to define the function, making sure we've created the lambda in /src/lambdas/securityEdgeLambda.js

functions:
    securityEdgeLambda:
        handler: src/lambdas/securityEdgeLambda.handler
        role: EdgeLambdaSecurityRole

We now need to create the EdgeLambdaSecurityRole. This needs to allow it to be used by edge Lambdas.

resources:
    Resources:
        EdgeLambdaSecurityRole:
            Type: AWS::IAM::Role
            Properties:
                RoleName: EdgeLambdaSecurityRole
                AssumeRolePolicyDocument:
                    Version: '2012-10-17'
                    Statement:
                        - Effect: Allow
                          Principal:
                              Service:
                                  - lambda.amazonaws.com
                                  - edgelambda.amazonaws.com
                          Action: sts:AssumeRole
                Policies:
                    - PolicyName: EdgeLambdaSecurityPolicy
                      PolicyDocument:
                          Version: '2012-10-17'
                          Statement:
                              - Effect: Allow
                                Action:
                                    - logs:CreateLogGroup
                                    - logs:CreateLogStream
                                    - logs:PutLogEvents
                                Resource:
                                    - '*'

Unfortunately, at the time of writing, CloudFormation doesn't support edge Lambda triggers, so we have to either manually add the triggers, or use a plugin such as this. The cloudfront-lambda-edge plugin is really easy to use with your existing serverless CloudFront distribution:

functions:
    securityEdgeLambda:
        handler: src/lambdas/securityEdgeLambda.handler
        role: EdgeLambdaSecurityRole
        lambdaAtEdge:
        	distribution: **Your CloudFront Distribution**
        	eventType: 'origin-response'

Setting Redirect HTTP to HTTPS in Serverless

To set the Viewer Protocol Policy to Redirect HTTP to HTTPS, all you need to do it add ViewerProtocolPolicy: 'redirect-to-https' to your CloudFront config in Serverless:

WebsiteDistribution:
    Type: 'AWS::CloudFront::Distribution'
    Properties:
        DistributionConfig:
            DefaultCacheBehavior:
                TargetOriginId: 'WebsiteBucketOrigin'
                ViewerProtocolPolicy: 'redirect-to-https'
                ...