
This is a requirement in one of the projects I’m working on. I thought this would be straightforward as there is plenty of documentation and examples. But alas, it led to a lot of head-banging and cursing. Why? Because the documentation is all wrong (or I can’t read, and that’s very possible)! AWS changed the Cloudfront payload format some time ago and the examples did not work for me.

These two errors came up when I was trying the examples. I’m adding them here for SEO purposes so that people may find the answer.
The Lambda function result failed validation: The function tried to add, delete, or change a read-only header.
and
The Lambda function returned an invalid entry in the headers object: Each header entry in the headers object must be an array.
Show me how
My AWS lamdba is pretty simple. Note that the headers are not just an object but also an array. This was crucial.
import json
def lambda_handler(event, context):
# Get the request from the CloudFront event
response = event['Records'][0]['cf']['response']
# Set the CORS headers
response_headers = {
'access-control-allow-origin': [
{
'value': '*'
}
],
'access-control-allow-methods': [
{
'value': 'GET'
}
],
'access-control-allow-headers': [
{
"value": 'content-type'
}
],
}
response['headers'].update(response_headers)
return response
If you’re using terraform, it’s very easy to deploy this function to AWS. The function must be deployed to us-east-1 and to the edge. This is my code.
provider "aws" {
region = "us-east-1"
alias = "us"
}
module "cf_lambda" {
providers = {
aws = aws.us
}
source = "terraform-aws-modules/lambda/aws"
version = "6.5.0"
function_name = "${var.env}-cloudfront-cors"
description = "Injects CORS to requests"
handler = "main.lambda_handler"
runtime = "python3.9"
publish = true
cloudwatch_logs_retention_in_days = 3
lambda_at_edge = true
create_package = true
source_path = "${path.module}/lambda"
attach_policy_statements = true
policy_statements = {
lambda = {
effect = "Allow",
actions = ["lambda:GetFunction", "lambda:EnableReplication*", "lambda:DisableReplication*"],
resources = ["*"]
},
cloudfront = {
effect = "Allow",
actions = ["cloudfront:CreateDistribution"],
resources = ["*"]
}
}
}
output "lambda_function_arn" {
value = "${module.cf_lambda.lambda_function_arn}:${module.cf_lambda.lambda_function_version}"
}
Cloudfront Config
Obviously, you should do this with Terraform or whichever automation tool you use but in broad strokes, this is what you need:
- Create a Cloudfront distribution
- Create a S3 bucket for the assets for which you want to add CORS
- Add an Origin to the Cloudfront distribution of type
s3
and point it to your bucket using the format
BUCKET_NAME.s3.REGION.amazonaws.com - Under Behaviours, add a new entry and configure there the cache policy.
- Finally, attach the Lambda function to the viewer-response. Please note that Cloudfront requires the lambda function ARN to point to a version and not just the main arn.

Final words
I was quite surprised that such a complex setup is required for something as simple as CORS. Even more so that it took me ages to configure it. Yes, I perhaps did not read the documentation or the examples properly but I found them confusing. I hope this helps somebody else.
As always, reach out to us if you need help.
I for one welcome our new overlords