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
- strict-transport-security – ensuring any future requests are made to https, not http
- content-security-policy – controlling where the resources can be loaded from
- x-content-type-options – tells browsers not to load scripts with incorrect MIME types
- x-frame-options – controls whether you content can be loaded in an i-frame
- x-xss-protection – prevents cross-site scripting
- 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.
- Go into the Lambda and add another CloudFront trigger
- 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'
...