Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lambda@Edge function not being called on Cloudfront error page

I have an Angular app's static files being served on an S3 bucket through Cloudfront. My Cloudfront distribution has error pages set up so it still renders the Angular's index.html. This means that if I request <cloudfront-distribution>.cloudfront.net/home-page, instead of saying that it didn't find a file named home-page on the S3 bucket, it will still render the angular app and the angular app will handle that /home-page route.

I needed to include some security headers on the app server so I set up a Lambda@Edge function to inject those headers on a viewer response event (like described here https://aws.amazon.com/blogs/networking-and-content-delivery/adding-http-security-headers-using-lambdaedge-and-amazon-cloudfront/).

The Lambda@Edge is working for routes that actually correspond to a file in the S3 bucket (if I have a file called image.png on the root folder of my S3 bucket, and I request <cloudfront-distribution>.cloudfront.net/image.png, I see the response headers that I injected via the Lambda@Edge function. The issue is when accessing a route that doesn't correspond to a file in the S3 bucket. If I access <cloudfront-distribution>.cloudfront.net/home-page, S3 will return a 404, Cloudfront will handle the 404 and act accordingly to the Error Pages configuration, i.e., respond with a 200 status code and render the index.html file. And when this happens, I don't see any of the headers I injected via the Lambda@Edge function, while all the other script files of my Angular app have the headers.

How can I make all responses go throught the Lambda@Edge function?

like image 634
Guilherme Cabral Avatar asked Nov 02 '20 16:11

Guilherme Cabral


People also ask

How do I use lambda@edge with CloudFront?

Lambda@Edge lets you use CloudFront triggers to invoke a Lambda function. When you associate a CloudFront distribution with a Lambda function, CloudFront intercepts requests and responses at CloudFront edge locations and runs the function. Lambda functions can improve security or customize information close to your viewers, to improve performance.

What happens when lambda returns an invalid response to CloudFront?

When Lambda returns an invalid response to CloudFront, error messages are written to log files which CloudFront pushes to CloudWatch in the Region of where the Lambda function executed. It's the default behavior to send the log files to CloudWatch when there's an invalid response.

How do I troubleshoot lambda@edge errors?

If you use Lambda@Edge functions, you can use graphs in the CloudFront console to help track down what's causing the error, and then work to fix it. For example, you can see if HTTP 5xx errors are caused by CloudFront or by Lambda functions, and then, for specific functions, you can view related log files to investigate the issue.

Why is CloudFront not redirecting end users to error pages?

No URL or DNS changes so end users do not know they have been redirected to an error page The CloudFront distribution was configured with two origins in a CloudFront Origin Group, the primary was the AWS Load Balancer, fronting the customer’s application dynamic and personalized content.


2 Answers

I just went through this exact same situation. I found this page in the AWS docs to be most helpful: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-cloudfront-trigger-events.html

While the current answer is definitely correct for this question, I had another constraint that might be helpful to discuss:

  • CloudFront Functions are only triggered for Viewer Request/Response events; As opposed to Lambda@Edge Functions which can be trigger by Origin or Viewer events. In my case I wanted to keep using the CloudFront function as they are lighter/faster/cheaper.

As you observed (and called in the docs) the viewer response event are not trigger when there is as error at the origin - including when a custom error page is returned as a 200.

Option #1 - Swapping to the Origin Request event

If you're already using Lambda@Edge and don't want to change anything this is probably the simplest change.

  • keep in mind this happens before any caching in CloudFront and therefore won't be called again for cache hits. So may not work for all use cases.
  • but, that might also be a good thing as it's fewer lambda executions and the headers should be included in the cache.

Option #2 - Stop Using Custom Error Responses

For me, I went this route. I needed to create a second function for the Viewer Request event that re-writes any requests that are not specifically for an s3 resource so React Router paths return my index.html. (Something like this: https://stackoverflow.com/a/60012469/4413888). finally, removed my existing custom error responses in CloudFront.

  • yes this is another function executing on each resource request, but at about 1/6 the cost of lambda@edge invocations (after free tier), I think I'm still coming out ahead.
  • get the benefit of discovering actual 403/404s from s3 - like when users try to load old webpack chunks and dead links
like image 183
philarmour Avatar answered Oct 16 '22 23:10

philarmour


Based on the answer of @philarmour I will show a concrete example for a solution.

Remove the custom 404 page that returns /index.html in your CloudFront Distribution. Then add a new CloudFront Function (customize the code below to your needs)

var rootPaths = {
    css: 1,
    favicon: 1,
    fonts: 1,
    img: 1,
    js: 1,
    'robots.txt': 1,
};
function handler(event) {
    var firstPart = event.request.uri.split('/')[1];
    if (!(firstPart in rootPaths)) event.request.uri = '/index.html';
    return event.request;
}

Associate the function with your CloudFront Distribution on VIEWER_REQUEST and don't forget to publish it.

like image 2
select Avatar answered Oct 16 '22 23:10

select