Deploy Cloudfront Functions to add security headers with AWS CDK
With the newly published CloudFront Functions, developers can leverage fast and short lived functions to handle simplistic tasks for viewer requests and responses.
The AWS article covers the differences between Lambda@Edge and CloudFront Functions in detail.
For a quick reference, here is the table from it:
Attribute | CloudFront Functions | Lambda@Edge |
---|---|---|
Runtime support | JavaScript (ECMAScript 5.1 compliant) | Node.js / Python |
Execution location | 218+ CloudFront Edge Locations | 13 CloudFront Regional Edge Caches |
CloudFront triggers supported | Viewer request / Viewer response | Viewer request /Viewer response / Origin request / Origin response |
Maximum execution time | Less than 1 millisecond | 5 seconds (viewer triggers) 30 seconds (origin triggers) |
Maximum memory | 2MB | 128MB (viewer triggers) / 10GB (origin triggers) |
Total package size | 10 KB | 1 MB (viewer triggers) / 50 MB (origin triggers) |
Network access | No | Yes |
File system access | No | Yes |
Access to the request body | No | Yes |
Pricing | Free tier available; charged per request | No free tier; charged per request and function duration |
The Cloudfront Function Code
Using Javascript (ECMAScript 5.1 compliant), the following code adds common security headers to viewer responses:
- permissions-policy
- referrer-policy
- strict-transport-security
- x-content-type-options
- x-frame-options
- x-xss-protection
Create a new file named headers.js
:
1function handler(event) {
2 var response = event.response
3 var headers = response.headers;
4
5 headers['permissions-policy'] = {
6 value: 'accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()',
7 }
8 headers['referrer-policy'] = { value: 'same-origin'};
9 headers['strict-transport-security'] = { value: 'max-age=63072000; includeSubdomains; preload' };
10 headers['x-content-type-options'] = { value: 'nosniff' };
11 headers['x-frame-options'] = { value: 'DENY' };
12 headers['x-xss-protection'] = { value: '1; mode=block' };
13
14 return response;
15};
This code was a modified version from the AWS documentation described here.
The AWS CDK Code
The production version of the code in this section can be found in the how.wtf open source repository.
Create a requirements.txt
For this tutorial, version 1.111.0 of the AWS CDK was used.
1aws-cdk.aws_cloudfront===1.111.0
2aws-cdk.aws_s3===1.111.0
3aws-cdk.core===1.111.0
Install the dependencies
1pip3 install -r requirements.txt
Create app.py
1from aws_cdk import core
2from stack import WebsiteStack
3
4app = core.App()
5WebsiteStack(app, "website")
6
7app.synth()
Create cdk.json
1{
2 "app": "python3 app.py"
3}
Create stack.py with a basic S3 Bucket
1from aws_cdk import core
2from aws_cdk import aws_cloudfront as cloudfront
3from aws_cdk import aws_s3 as s3
4
5class WebsiteStack(core.Stack):
6
7 def __init__(self, app: core.App, id: str) -> None:
8 super().__init__(app, id)
9
10 bucket = s3.Bucket(
11 self,
12 "bucket",
13 website_index_document="index.html",
14 public_read_access=True,
15 removal_policy=core.RemovalPolicy.DESTROY,
16 )
Add the CloudFront Function + Distribution
1from aws_cdk import core
2from aws_cdk import aws_cloudfront as cloudfront
3from aws_cdk import aws_s3 as s3
4
5class WebsiteStack(core.Stack):
6
7 def __init__(self, app, id):
8 super().__init__(app, id)
9
10 bucket = s3.Bucket(
11 self,
12 "bucket",
13 website_index_document="index.html",
14 public_read_access=True,
15 removal_policy=core.RemovalPolicy.DESTROY,
16 )
17
18 security_headers = cloudfront.Function(
19 self,
20 "security_headers",
21 code=cloudfront.FunctionCode.from_file(
22 file_path="headers.js",
23 ),
24 )
25
26 distribution = cloudfront.CloudFrontWebDistribution(
27 self,
28 "cdn",
29 origin_configs=[
30 cloudfront.SourceConfiguration(
31 s3_origin_source=cloudfront.S3OriginConfig(
32 s3_bucket_source=bucket,
33 ),
34 behaviors=[
35 cloudfront.Behavior(is_default_behavior=True),
36 cloudfront.Behavior(
37 path_pattern="*",
38 function_associations=[
39 cloudfront.FunctionAssociation(
40 event_type=cloudfront.FunctionEventType.VIEWER_RESPONSE,
41 function=security_headers,
42 ),
43 ],
44 ),
45 ],
46 )
47 ],
48 )
49
50 core.CfnOutput(
51 self,
52 "distribution-domain-name",
53 value=distribution.distribution_domain_name,
54 )
The directory structure should look like this:
project/
├── app.py
├── cdk.json
├── headers.js
├── requirements.txt
└── stack.py
Deploy the stack
1cdk deploy website
Because of the CfnOutput
, the distribution’s domain name is exposed via an output on the stack:
Outputs:
website.distributiondomainname = hostname.cloudfront.net
Add index.html document to the S3 bucket
1<h1>Security headers!</h1>
Test the headers
After adding the index.html
document, visit the distribution’s domain name to ensure it is working correctly.
To test the security headers, either use your favorite request tool or use securityheaders.com.