12
votes

I am attempting to follow the instructions here https://medium.com/@tom.cook/edge-lambda-cloudfront-custom-headers-3d134a2c18a2

I have CloudFront successfully sitting in front of a static S3 "hello world" HTML file, and I want to set additional headers using lambda edge, but I get an error. The really frustrating bit is that I cannot find any logs of the error to debug what is going wrong. Here is what the browser shows.

ERROR

The request could not be satisfied.

The Lambda function returned an invalid request or response to CloudFront. 
Generated by cloudfront (CloudFront)
Request ID: 2Cqex7euzH0Iigps58i9tMVxdqAaLznL2ZjwqR1sW1AZHz6x2EwfMA==

Here is the code for my simple lambda:

exports.handler = (event, context, callback) => {
    console.log(event)
    callback(null, 'Hello from Lambda');
};

The trigger type is viewer-response and is attached to my CloudFront distribution (with Cache Behavior: *, if that matters). The lambda has a role corresponding to AWSLambdaBasicExecutionRole, which gives write access to Cloudwatch.

As soon as I enable the trigger, the response to a web request changes from my "Hello world" HTML to the error above, so I know it is triggering the lambda. But in the lambda dashboard, it shows no invocations or errors. No logs appear in Cloudwatch. The CloudFront dashboard shows errors (5xx), but nothing from lambda.

If I then test my function within the lambda console by clicking to the deployed function, configuring the test event as "CloudFront Modify Response Header," and hitting Test, it is successful. And Cloudwatch shows logs and console output for the test! But still nothing in logs for the live invocation.

My only theory is something wrong with the permissions, that CloudFront cannot actually invoke the lambda (explains why there is nothing in the lambda dashboard). The last thing is that the CloudFront logs (in S3) show the web request with the 502 error and LambdaValidationError, but I cannot figure out if that helps.

2
If my answer below doesn't solve your issue, please condense your code down to an MCVE and edit that into the question.Michael - sqlbot
Your answer did solve my problem, @sqlbot. My remaining concern is that as I make changes to my function, I'm can't run them by you each time! The problem in my example was returning a string, but how could I know that from the errors/logs generated by CF and Lambda?lordbyron
Well, you know... you can run them by me any time, as long as you have a well-funded paypal account. :) In all seriousness, though, you are correct -- you would not have a way to know, because when there's no Node error yet CloudFront rejects a Lambda response, there is no place for the logs to go and of course it would not be good practice to return an error to the browser. But between the docs and the examples, there wasn't anything that couldn't be worked through. Be sure you check out all the "blueprints." They generate you valid responses formatted exactly the way CloudFront likes them.Michael - sqlbot

2 Answers

12
votes

The example in the blog post you're looking at was valid while Lambda@Edge was still in preview (limited access to specific customers, before the launch to general availability), but it is no longer correct. Shortly before the service launched, the data structures were changed.

The response header data structures formerly looked like this:

headers['Strict-Transport-Security'] = "max-age=31536000; includeSubdomains; preload";
headers['Content-Security-Policy']   = "default-src 'none'; img-src 'self'; script-src 'self'; style-src 'self'; object-src 'none'";
headers['X-Content-Type-Options']    = "nosniff";

The new structures look like this:

headers['strict-transport-security'] = [{
    key:   'Strict-Transport-Security', 
    value: "max-age=31536000; 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"
}];

The keys in the outer object must be the lowercase equivalent of the key value in each member of the inner objects. This change to the data structures was most likely needed in order to more accurately reflect the way HTTP headers have to be processed, since in HTTP/1.x they are not case sensitive, yet in a Javascript object, the keys are case sensitive.

If the structure returned by your code does not conform to what CloudFront requires Lambda@Edge to return, the error you see will indeed be thrown. There are no logs generated, because there is no place for the logs to go -- this error occurs outside of Lambda, on the CloudFront side of the interface boundary between Lambda and CloudFront, which doesn't generate any user-accessible logs.

See the documentation for the Response Event Structure.

6
votes

There are some common "gotchas" to Lambda@Edge and CloudFront. You need to:

  • Publish a new version of you Lambda function
  • Update the CloudFront Lambda association to your new version, e.g. arn:aws:lambda:us-east-1:572007530218:function:gofaas-WebAuthFunction:45
  • Look for Lambda@Edge logs in the region of the requestor

And as far as I know you can not see metrics about invocations from the "copies" of your main Lambda func distributed around to the "edges".

This is different from "normal" Lambda web console flow of saving a code change and jumping to logs from the monitoring tab.

Take a look at this boilerplate app that automates deploying a Lambda@Edge OAuth and Cookie handler, which takes a lot of the pain of setting this up away.