Improve your site’s security using Lambda@Edge
Tags: aws, serverless, python, lambdaedge
Published: 2021-01-07 19:49:55

---
title: Improve your site’s security using Lambda@Edge
published: true
date: 2021-01-05 00:00:00 UTC
tags: aws,serverless,python,lambdaedge
canonical_url: https://jurriaan.cloud/improve-your-sites-security-using-lambda-at-edge/
---

In this post I’ll show you how I improved my site’s security using Lambda@Edge.

I recently went through my list of bookmarks again and stumbled upon [https://securityheaders.com/](https://securityheaders.com/), a website that scans a URL and gives it a rating based on the security headers that are set in the response.

So I gave it a try with my personal website:

![Bad result from securityheaders.com](https://jurriaan.cloud/assets/images/security-headers-bad-score.png)

Oops.

Let’s improve that.

In a [previous post](https://dev.to/jurriaanpro/redirect-www-to-non-www-using-lambda-edge-1p9b) I already mentioned that I’m serving my blog using CloudFront and S3, which means I can easily add another Lambda@Edge function that add the required security headers to get this rating up.

The AWS documentation already gives quite [a complete example](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-edge-how-it-works-tutorial.html) on how to do this in Node.js, so I ported that to Python as that is what I use for the other Lambda@Edge function:

```python
def handle_event(event, context):
    response = event["Records"][0]["cf"]["response"]
    headers = response["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": "no-referrer-when-downgrade"
    }]
    
    # If you remove the Server header in your code, CloudFront will still add its own header but it does not leak 
    # information about the backend server and does not give an attacker new information because they already know that 
    # they are connecting to a CloudFront IP address. <https://stackoverflow.com/a/58609507>
    if "server" in headers:
        del headers["server"]

    return response
```

I then configured it to trigger on the **origin-response** event in my `serverless.yaml` (see the [previous post](https://dev.to/jurriaanpro/redirect-www-to-non-www-using-lambda-edge-1p9b) for the rest of the serverless.yaml):

```yaml
functions:
  originResponse:
    handler: src/functions/origin_response/handler.handle_event
    events:
      - cloudFront:
          eventType: origin-response
          origin: http://${self:custom.domainName}.s3-website-eu-west-1.amazonaws.com
```

and deployed it.

After invalidating the cache I ran the check again:

![Somewhat better result from securityheaders.com](https://jurriaan.cloud/assets/images/security-headers-somewhat-better-score.png)

Ok, getting somewhere!

I then fixed the error in the `Strict-Transport-Security` header (there’s a space in the value field in the example AWS gives) and added the following `Permissions-Policy` header.

```python
headers["permissions-policy"] = [{
    "key": "Permissions-Policy",
    "value": "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), "
             "payment=(), usb=()"
}]
```

Again invalidated the cache and ran the test again:

![Best result from securityheaders.com](https://jurriaan.cloud/assets/images/security-headers-best-score.png)

🎉

That’s it, in about 30 minutes I went from an F to an A+ rating!

The creator of [https://securityheaders.com/](https://securityheaders.com/) has written about all the headers above, so in case you’d like to learn more about those I suggest you check out these posts:

- [Hardening your HTTP response headers#Server](https://scotthelme.co.uk/hardening-your-http-response-headers/#server)
- [HSTS - The missing link in Transport Layer Security](https://scotthelme.co.uk/hsts-the-missing-link-in-tls/)
- [Content Security Policy - An Introduction](https://scotthelme.co.uk/content-security-policy-an-introduction/)
- [Hardening your HTTP response headers#X-Content-Type-Options](https://scotthelme.co.uk/hardening-your-http-response-headers/#x-content-type-options)
- [Hardening your HTTP response headers#X-Frame-Options](https://scotthelme.co.uk/hardening-your-http-response-headers/#x-frame-options)
- [Content Security Policy - An Introduction](https://scotthelme.co.uk/content-security-policy-an-introduction/)
- [A new security header: Referrer Policy](https://scotthelme.co.uk/a-new-security-header-referrer-policy/)
- [Goodbye Feature Policy and hello Permissions Policy!](https://scotthelme.co.uk/goodbye-feature-policy-and-hello-permissions-policy/)

### Other useful links

- [HTTP headers on MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers)