Using AWS cloudfront with S3 to host an angular-based web client.
Is there any rewrite rule or settings allowing one of the following examples? It is so unclear from what AWS are trying to exaplain.
Using friendly route, for example:
domain.com?lang=en&fun=no => domain.com/en/no
Configuration folders to have a default file, for example:
domain.com\en => domain.com (but now the client knows it has a parameter lang=en)
Obviously both of the example can be done with an html file which routes to the desired url BUT it doesn't work well with some sort of analytics models such as google's.
I would suggest using 'AWS Lamda at the Edge' functionality to provide the custom rewriting you want:
Using CloudFront with Lambda#Edge
Lambda#Edge is an extension of AWS Lambda, a compute service that lets you execute functions that customize the content that CloudFront delivers. Lambda#Edge scales automatically, from a few requests per day
to thousands per second. Processing requests at AWS locations closer
to the viewer instead of on origin servers significantly reduces
latency and improves the user experience.
When you associate a CloudFront distribution with a Lambda#Edge
function, CloudFront intercepts requests and responses at CloudFront
edge locations. You can execute Lambda functions when the following
CloudFront events occur:
When CloudFront receives a request from a viewer (viewer request)
Before CloudFront forwards a request to the origin (origin request)
When CloudFront receives a response from the origin (origin response)
Before CloudFront returns the response to the viewer (viewer response)
and here is an aCloudGuru blog post with lots of good examples, including one specifically about url rewriting:
https://read.acloud.guru/supercharging-a-static-site-with-lambda-edge-da5a1314238b
In a multipage web app (say 12 pages), you will want to use an automated and worry-less strategy via AWS Lamda#Edge. It solves this completely.
First, create an AWS Lambda function and then attach your CloudFront as a trigger.
In the code section of this AWS Lamda page, add the snippet in the repository below.
https://github.com/CloudUnder/lambda-edge-nice-urls/blob/master/lambdaRewrite.js
Content delivery will still be as fast as you can blink your eyes.
PS: Note the options in the readme section of the repo above
Related
I have a CloudFront distribution that serves my React app. (myreactapp.com)
I also have a Lambda Function (an Express app) to receive the POST request that comes from auth service. I want to serve this Lambda Function under the same URL as my CloudFront distribution on /auth endpoint.
So every request that comes to myreactapp.com should serve my React app but if a POST request comes to myreactapp.com/auth, my Lambda function should serve that endpoint.
myreactapp.com is a custom domain added to the CloudFront distribution. I want to use the same domain for the /auth endpoint.
So far, I've tried adding an origin to my CloudFront distribution, selected my Lambda Function as the Origin domain. Then under Behaviors, I added a new behavior, put /auth for the Path pattern, selected my previously created Origin for the Origin/Origin Groups option, and selected GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE for the allowed methods.
I am deploying my Lambda Function with serverless deploy CLI command.
I am new with AWS and couldn't figure this out. I am not even sure if what I am looking for is possible. Could you give me some insights on this?
I would like to use AWS's Server Side Encryption (SSE) with the AWS Key Management Service (KMS) to encrypt data at rest in S3. (See this AWS blog post detailing SSE-KMS.)
However, I also have the requirement that I use Cloudfront Presigned URLs.
How can I set up a Cloudfront distribution to use a key in AWS KMS to decrypt and use S3 objects encrypted at rest?
(This Boto3 issue seems to be from someone looking for the same answers as me, but with no results).
This was previously not possible because CloudFront didn't support it and because (as I mentioned in comments on John's answer -- which was on the right track) there was no way to roll-your-own solution with Lambda#Edge because the X-Amz-Cf-Id request header --generated on the back side of CloudFront and visible only to S3, not to the trigger invocation -- would invalidate any signature you tried to add to the request inside a Lambda#Edge trigger, because signing of all X-Amz-* headers is mandatory.
But the X-Amz-Cf-Id header value is now exposed to a Lambda#Edge trigger function in the event structure -- not with the other request headers, but as a simple string attribute -- at event.Records[0].cf.config.requestId.
With that value in hand, you can use the execution role credentials and the built-in SDK in the Lambda#Edge environment to generate a signature and and add the necessary headers (including an Authorization header with the derived credential identifier and freshly-generated signature) to the request.
This setup does not use an Origin Access Identifier (OAI) because the Lambda#Edge trigger's IAM Execution Role is used instead of an OAI to persuade S3 that the request is authorized.
Achraf Souk has published an official AWS blog post explaining the solution from start to finish.
https://aws.amazon.com/blogs/networking-and-content-delivery/serving-sse-kms-encrypted-content-from-s3-using-cloudfront/
Use S3 Presigned URLs. This AWS article discusses how to generate urls using Java, but this is easily ported to another language.
Server-Side Encryption with AWS Key Management Service (SSE-KMS)
The following setup works for us:
In your application, generate a signed URL that Cloudfront can validate (https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-signed-urls.html).
Instead of using OAI, you create a Lambda#Edge origin request function as per: https://aws.amazon.com/blogs/networking-and-content-delivery/serving-sse-kms-encrypted-content-from-s3-using-cloudfront/
Please note that if your bucket contains an '.' (ours did), there's a bug in the JS code that can be mitigated with something like:
// Infer the region from the host header
// const region = options.host.split('.')[2];
const hostArr = options.host.split('.');
const region = hostArr [hostArr.length - 3];
Lastly, we added an origin response Lambda#Edge to wash away headers that we do not want exposed. Esp the X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id that includes the AWS Account ID.
Lastly I'd like to comment on the above statement/comment that Lamda#Edge response bodies are limited to 1 MB, this only applies to content generated (or modified if you include the body) by the lambda function.
When using the Lambda#Edge function above, the response from the S3 origin has no such limit, we are serving objects >> 1MB (normally 100+ MB).
I had plans to use the AWS API Gateway for three purposes. All of these endpoints are configured with custom domain names, with AWS issued SSL certificates and I have CNAME records configure to match the could front urls.
api.my-domain.com (REST api calls that return json data) (Working as expected)
images.my-domain.com (Proxy pass through of binary image data from S3) - Working as expected.
videos.my-domain.com (DOH!... )
Unfortunately dealing with videos I've run into a few issues. Smaller videos start to work but then generate an error. But.. that's not the main issue.
There is a 10MB max payload size on the response data from an API integration endpoint, so I must come up with another solution for the videos.
I don't want to host the images, or videos via cloundfront. And I want to use the same AWS issues wildcard certificate *.my-domain.com on all the endpoints. I wanted to use the API gateway for the image request because the images are small, and won't exceed the limit, and I can cache them at the api level.
a CNAME pointed to my video S3 bucket works, but can't use the same SSL certificate, and I wanted all traffic to originate vie the API gateway and not have request going directly to the bucket endpoint.
```
So.. what are my options?
It seems like my best option will be to transcode the MP4 videos to HLS, and host the S3 bucket via cloudfront. I hadn't really wanted to incur the charges of using cloudfont, but I don't see any better option, for the design I want.
The most recent videos will be viewed occasionally, not high demand, older videos will be viewed rarely so hosting them in cloudfront seems like a waste.
Typical setup for Video streaming in AWS is to Stream the Video stored in S3 through AWS CloudFront RTMP Distribution.
Going forward with CloudFront hosted content from my S3 bucket to see how it works.
The reason I picked CloudFront overall was the tight integration with other AWS services, and the ability to have complete control over the path/name of the assets in the S3 bucket, whereas with Vimeo you don't have very good control over the name of the assets.
I have a website distributed with CloudFront, with S3 as an origin. I've written a Lambda function that takes a contact form submission and sends the email along with SES. The Lambda test out just fine : )
But, I'm clueless when it comes to routing POST requests from CloudFront to that backend Lambda function. How do I do this?
Update: Okay, I've got the API Gateway test triggering the Lambda function just fine, but I can't seem to call it from CloudFront (or rather using a curl command to my domain set up with CloudFront).
Do I need to list my domain as a custom domain in API Gateway?
If I list the path /api/* in my CloudFront Behaviors, do I have to mirror that in my API Gateway set up? So, does my API Gateway need to start with /api before I add specific resources?
Update 2 I think I needed to leave or remove the /dev off the end of the API Gateway URL. dev being my stage.
Update 3 Okay, it feels one step away now. I've got everything hooked up. The test request hits cloudfront, it forwards to api gateway, gateway calls lambda (at this point I'm shaking my head at how complicated we've made all this), and lambda sends back success or failure to api gateway, and we're peachy. Except, I get MethodNotAllowed when I do it from curl or the browser. Do I need to add an IAM role to CloudFront to access API Gateway?
Update 4 Still not working. And now, I'm back to getting my usual 404 error page that my Default Origin (S3). Seems like serverless is a fading dream.
Update 5 Trying a different approach, recommended here: https://serverfault.com/a/839368 The idea is to use API Gateway's Custom Domain name features with a subdomain like api.example.com and then use a Route53 Alias record to direct subdomain traffic to API Gateway. This could work. Then CloudFront would handle traffic to example.com and www.example.com, and API Gateway would get requests to api.example.com. Now the challenging bit is that in HTML forms the action attribute will have to go to a different subdomain. Let's see what kinds of errors and crazy behavior we get : (
First you would setup API Gateway in front of your Lambda function so it can be called via a POST request. It sounds like you may already have that part done?
Then if you want the POST to go through CloudFront you would add a second origin in CloudFront that points to your API Gateway.
This is all possible, but its tricky to configure. To help I created an open-source boilerplate app that correctly sets up:
A static site with CloudFront and S3
An API with API Gateway and Lambda
CORS between the static site and API
Optional OAuth 2.0 and JWT cookie for the static site
See this static JavaScript app for an example of a static site POSTing to an API backed by Lambda.
Depends on what you're using as your backend (which language, framework, etc.), there are different ways, but 'em all about one thing: Invoke
Kind-of the most generic call - HTTP is right there, the API call examples by language are referenced at the end of the doc.
Let say I need an API Gateway that is going to run Lambdas and I want to make the best globally distributed performing infrastructure. Also, I will use Cognito for authentication, Dynamodb, and S3 for user data and frontend statics.
My app is located at myapp.com
First the user get the static front end from the nearest location:
user ===> edge location at CloudFront <--- S3 at any region (with static front end)
After that we need to comunicate with API Gateway.
user ===> API Gateway ---> Lambda ---> S3 || Cognito || Dynamodb
API Gateway can be located in several regions, and even though is distributed with CloudFront, each endpoint is pointing to a Lambda located at a given region: Let say I deploy an API at eu-west-1. If a request is sent from USA, even if my API is on CloudFront, the Lambda it runs is located at eu-west-1, so latency will be high anyway.
To avoid that, I need to deploy another API at us-east-1 and all my Lambdas too. That API will be pointing to those Lambdas
If I deploy one API for every single region, I would need one endpoint for each one of them, and the frontend should decide which one to request. But how could we know which one is the nearest location?
The ideal scenario is a single global endpoint at api.myapp.com, which is going to go to the nearest API Gateway which runs the Lambdas located in that region too. Can I configure that using Route 53 latency routing with multiple A records pointing to each api gateway?
If this is not right way to do this, can you point me in the right direction?
AWS recently announced support for regional API endpoints using which you can achieve this.
Below is an AWS Blog which explains how to achieve this:
Building a Multi-region Serverless Application with Amazon API Gateway and AWS Lambda
Excerpt from the blog:
The default API endpoint type in API Gateway is the edge-optimized API
endpoint, which enables clients to access an API through an Amazon
CloudFront distribution. This typically improves connection time for
geographically diverse clients. By default, a custom domain name is
globally unique and the edge-optimized API endpoint would invoke a
Lambda function in a single region in the case of Lambda integration.
You can’t use this type of endpoint with a Route 53 active-active
setup and fail-over.
The new regional API endpoint in API Gateway moves the API endpoint
into the region and the custom domain name is unique per region. This
makes it possible to run a full copy of an API in each region and then
use Route 53 to use an active-active setup and failover.
Unfortunately, this is not currently possible. The primarily blocker here is CloudFront.
MikeD#AWS provides the info on their forums:
When you create a custom domain name it creates an associated CloudFront distribution for the domain name and CloudFront enforces global uniqueness on the domain name.
If a CloudFront distribution with the domain name already exists, then the CreateCloudFrontDistribution will fail and API Gateway will return an error without saving the domain name or allowing you to define it's associated API(s).
Thus, there is currently (Jun 29, 2016) no way to get API Gateway in multiple regions to handle the same domain name.
AWS has no update on providing the needful since confirming existence of an open feature request on July 4, 2016. AWS Form thread for updates
Checkout Lambda#Edge
Q: What is Lambda#Edge? Lambda#Edge allows you to run code across AWS
locations globally without provisioning or managing servers,
responding to end users at the lowest network latency. You just upload
your Node.js code to AWS Lambda and configure your function to be
triggered in response to Amazon CloudFront requests (i.e., when a
viewer request lands, when a request is forwarded to or received back
from the origin, and right before responding back to the end user).
The code is then ready to execute across AWS locations globally when a
request for content is received, and scales with the volume of
CloudFront requests globally. Learn more in our documentation.
Usecase, minimizing latency for globally distributed users
Q: When should I use Lambda#Edge? Lambda#Edge is optimized for latency
sensitive use cases where your end viewers are distributed globally.
Ideally, all the information you need to make a decision is available
at the CloudFront edge, within the function and the request. This
means that use cases where you are looking to make decisions on how to
serve content based on user characteristics (e.g., location, client
device, etc) can now be executed and served right from the edge in
Node.js-6.10 without having to be routed back to a centralized server.