Unable to add custom headers to CloudFront distribution - using s3 as backend

We are trying to put some custom headers to our cloud fount distribution, but its not working as expected. Following is the scenario.

We have a s3 bucket named "example-images" with all static contents, s3 bucket is exposed for public access. Has following CORS policy

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
    <CORSRule>
        <AllowedOrigin>*</AllowedOrigin>
        <AllowedMethod>GET</AllowedMethod>
        <ExposeHeader>Access-Control-Allow-Credentials</ExposeHeader>
        <AllowedHeader>*</AllowedHeader>
    </CORSRule>
</CORSConfiguration>

We are able to access s3 contents via URL following the sample output using curl.

x-amz-id-2: xxxxxxxxxxxx
x-amz-request-id: xxxxxxxxxxxx
Date: Wed, 05 Oct 2016 04:10:26 GMT
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET
Access-Control-Expose-Headers: Access-Control-Allow-Credentials
Vary: Origin, Access-Control-Request-Headers, Access-Control-Request-Method
Last-Modified: Mon, 26 Sep 2016 13:12:58 GMT
ETag: "xxxxxxxxxxxxxx"
Accept-Ranges: bytes
Content-Type: image/png
Content-Length: 1630
Server: AmazonS3

Then we created a cloudfront distribution for serving these contents.

As per documentation we have following values set in "Default Origin"

  • Restrict bucket access = no
  • Oringnal custom headers Some custom header

    e.g. "Access-Control-Allow-Credentials = true"

In CF distribution "Behavior" we have following settings.

  • Protocol policy = http & https
  • Allow http methods = GET, HEAD, OPTIONS.
  • Forward Headers = whitelist
  • Whitelist headers = Origin
  • Object Caching = Use origin cache headers

Still we are not able to view custom headers when using CF Distribution url.

curl -sI -H "Origin: example.com" -H "Access-Control-Request-Method: GET" -H "Access-Control-Allow-Credentials: GET"
HTTP/1.1 200 OK
Content-Type: image/png
Content-Length: 1630
Connection: keep-alive
Date: Wed, 05 Oct 2016 04:03:20 GMT
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET
Access-Control-Expose-Headers: Access-Control-Allow-Credentials
Last-Modified: Mon, 26 Sep 2016 13:12:58 GMT
ETag: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Accept-Ranges: bytes
Server: AmazonS3
Vary: Origin
Age: 1231
X-Cache: Hit from cloudfront
Via: 1.1 xxxxxxxx.cloudfront.net (CloudFront)
X-Amz-Cf-Id: xxxxxxxxxxxxxxxxxxxxx

Can you help us indentifying what setting we are missing here.


You can add custom headers to the response from CloudFront / S3 using a Lambda@Edge function. The lambda code runs within the local edge locations, but needs to be created and maintained in the us-east-1 region.

The example code here uses nodeJS 6.10 to add the x-frame-options response header, but you can add any header that is not restricted by AWS.

'use strict'; 
 exports.handler = (event, context, callback) => {
   const response = event.Records[0].cf.response; 
   const headers = response.headers; 
   response.headers['x-frame-options'] = [{"key":"X-Frame-Options","value":"SAMEORIGIN"}]; 
   console.log(response.headers); 
   callback(null, response);
 }; 

Create a definitive version of the Lambda, then set the Lambda Version's trigger configuration as the CloudFront origin-response Event type for your path pattern behavior.

The example code logs events to CloudWatch logs service for debugging purposes. If you don't already have one you will need to setup a lambda execution IAM role that allows a policy allowing CloudWatch logs actions to be assumed by edgelambda.amazonaws.com and lambda.amazonaws.com.

Basic Lambda Execution Policy allowing logs to be written to CloudWatch:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "arn:aws:logs:*:*:*",
            "Effect": "Allow"
        }
    ]
}

Trust Relationship allowing Lambda and Lambda@Edge to assume the role :

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": [
          "edgelambda.amazonaws.com",
          "lambda.amazonaws.com"
        ]
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

It would be better if AWS simply allowed the custom headers to be set using the CloudFront GUI but until then this solution should satisfy your requirement.