So I want a same-origin policy which only allows my API to be called from the same-origin in browser, I don't want CORs.
After hours testing whether nginx or my node web app was setting Access-Control-Allow-Origin:* , it turns out that AWS EC2 is setting CORs headers without my permission. I can override this using Nginx to remove response headers and replace (if necessary)...
However I do not believe this is how it should be done, why is AWS putting extra strain on my web server without giving me the option to customise their default "allow all origins"?
This is such an unnecessary problem AWS is creating for me and was wondering if anyone else is experiencing the same and how we should go about it?
What I've tried:
In local development without AWS, neither nginx nor my node app add any access control headers (without my permission) - there is no mention of it. I even disabled CORS on my node app to make sure!
Turning on cors in my node app to see if I can override the response that is being set by AWS EC2 downstream.
This results in two separate Access-Control-Allow-Origin headers, the AWS one taking precedence over mine.
Using Nginx to respond to Options, so AWS knows that I have considered CORs requests and that I want to reject them... However my nginx response to Options is once again overrided by AWS downstream on the response! Additionally I would add CORs options to my responses using NGinx but they are still overrided by AWS.
when I say AWS overrides my response I mean that, my response is included but so is AWS response.
[example AWS with Nginx response][1]
[1]: https://i.stack.imgur.com/9xnlr.png
Maybe AWS are saying something, that all API's should be accessible from all origins? just doesn't make sense to me!
Btw here is what amazon have to say about cors, that it is "standardised" https://docs.aws.amazon.com/AWSEC2/latest/APIReference/cors-support.html
I don't understand the difference between an EC2 instance running MY API, vs and EC2 API? my main concern is changing the AWS cors headers, which I cant find any help on!
So after playing around more and realising that the Access-Control-Allow-Origin:* is being returned only by all GET requests, and all successful POST requests (from my own domain - no CORs, as i have it disabled), I thought something has to be up (all my get requests are allowed cross domain).
This post explains it beautifully by Amazon themselves, pretty annoyed they have taken the liberty to take my web security into their own hands, but atleast we know what side they are on now!
https://docs.aws.amazon.com/AWSEC2/latest/APIReference/cors-support.html
"For all simple requests such as GET & POST(simple post), the Access-Control-Allow-Origin * is returned" by AMAZON.
"Therefore, Amazon EC2 allows any cross-domain origin, and never allows browser credentials, such as cookies." because " Access-Control-Allow-Credentials is never returned" - so cookies should not be at risk of cross-site attacks from this setup I hope. However this setup also encourages spam botting from any domain, which will make amazon more money - which is the only plausible reason for why they have decided to enabled CORs for all simple requests.. #jeff.
If you prefer images, and so we can quote amazon on this!
Simple Requests Response from EC2
Finally, Amazon EC2 even have the courtesy to accept OPTIONS requests on our behalf, for more dangerous requests - to which they send a response of:
Access-Control-Allow-Origin: * "This is always returned with a * value."
Access-Control-Allow-Credentials is NOT returned. Setting browser for default false which will not send any cookies with the now permitted cross origin requests.
Access-Control-Expose-Headers is NOT returned. EC2 doesn't permit your browser to read response headers?
Access-Control-Allow-Methods: GET, POST, OPTIONS, PUT, DELETE - "this depends on whether you use our Query API or REST" have no idea what that means but a cross site preflight for my REST API returned GET, OPTIONS, POST - thankfully.
Access-Control-Allow-Headers "Amazon EC2 accepts any headers in preflight requests."
The general sense is that Amazon wants us to outsource basic CORs security to the EC2 instance? Would love to test out the pre-flight they provide better but it seems they do most of the boilerplate and let you decide if you want to accept any complex/notsimple CORs requests - as detailed here https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-cors.html
Took me a while and a bit of shock, but glad we got here!
Related
On AWS official documentation it is written:
For HTTP APIs and REST APIs, you pay only for the API calls you
receive and the amount of data transferred out.
Do I also have to pay if CORS is enabled and the request comes from another origin?
Thus, can I use CORS as kind of a free restriction rule and reject the request because of CORS reasons? How could I do that?
I have two paths in a CloudFront distribution which have the following Behaviors:
Path Pattern
Origin
Viewer Protocol Policy
/api/*
APIOriginGroup
Redirect HTTP to HTTPS
/*
S3OriginGroup
Redirect HTTP to HTTPS
And these Origins:
Origin Name
Origin Domain
Origin Group
S3Origin1
us-east-1.s3.[blah]
S3OriginGroup
S3Origin2
us-east-2.s3.[blah]
S3OriginGroup
APIOrigin1
a domain
APIOriginGroup
APIOrigin2
a domain
APIOriginGroup
This setup works fine for GET requests, but if I add POST requests into the Cache Behavior's Cache Methods I get an error:
"cannot include POST, PUT, PATCH, or DELETE for a cached behavior"
This doesn't make sense to me. If CloudFront really is used by AWS customers to serve billions of requests per day and AWS recommends using CloudFront Origin Failover, which ?requires origin groups?, then it follows that there must be some way to configure CloudFront to allow origin behaviors which allow POST requests. Is this not true? Are all of these API requests being made by this customer GET requests?
To be clear, my fundamental problem is that I want to use CloudFront Origin Failover to switch between my primary region and secondary region when an AWS region fails. To make that possible, I need to switch over not only my front end, S3-based traffic (GET requests), but also switch over my back-end traffic (POST requests).
Note: CloudFront supports routing behaviors with POST requests if you do not use an Origin Group. It seems that only when I added this Origin Group (to support the second region) that this error appeared.
Short Answer: You can't do origin failover in CloudFront for request methods other than GET, HEAD, or OPTIONS. Period.
TL; DR
CloudFront caches GET and HEAD requests always. It can be configured to cache OPTIONS requests too. However it doesn't cache POST, PUT, PATCH, DELETE,... requests which is consistent with the most of the public CDNs out there. However, some of them might provide you with writing some sort of custom hooks by virtue of which you can cache POST, PUT, PATCH, DELETE,... requests. You might be wondering why is that? Why can't I cache POST requests? The answer to that question is RFC 2616. Since POST requests are not idempotent, the specification advises against caching them and sending them to the end server intended, indeed, always. There's a very nice SO thread here which you can read to have a better understanding.
CloudFront fails over to the secondary origin only when the HTTP method of the viewer request is GET, HEAD, or OPTIONS. CloudFront does not fail over when the viewer sends a different HTTP method (for example POST, PUT, and so on).
Ok. POST requests are not cached by CloudFront. But, why does CloudFront not provide failover for POST requests?
Let's see how does CloudFront handle requests in case of a primary origin failure. See below:
CloudFront routes all incoming requests to the primary origin, even when a previous request failed over to the secondary origin. CloudFront only sends requests to the secondary origin after a request to the primary origin fails.
Now, since POST requests are not cached CloudFront has to go to the primary origin each time, come back with an invalid response or worst a time-out, then hit the secondary origin in the origin group. We're talking about region failures here. The failover requests from primary to secondary would be ridiculously high and we might expect a cascading failure due to high load. This would lead to CloudFront PoP failures and this defeats the whole purpose of high availability, doesn't it? Again, this explanation is only my assumption. Of course, I'm sure folks at CloudFront would come up with a solution for handling POST requests region failover soon.
So far so good. But how are other AWS customers able to guarantee high availability to their users in case of AWS region failures.
Well other AWS customers only use CloudFront region failover to make their static websites, SPAs, static contents like videos (live and on demand), images, etc failure proof which by the way only requires GET, HEAD and occasionally OPTION HTTP requests. Imagine a SaaS company which drives its sales and discoverability via a static website. If you could reduce your downtime by the method above which would ensure your sales/growth doesn't take a hit, why wouldn't you?
Got the point. But I do really need to have region failover for my backend APIs. How can I do it?
One way would be to write a custom Lambda#Edge function. CloudFront hits the intended primary origin, the code inside checks for time-out/response codes/etc and if failover has to be triggered, hits the other origin's endpoint and returns the response. This is again in contradictory to the current schemes of CloudFront.
Another solution would be, which in my opinion is much cleaner, is to make use of latency-based routing support of Route53. You can read about how to do that here. While this method would surely work for your backend APIs if you had different subdomain names for your S3 files and APIs (and those subdomains pointing to different CloudFront distributions) since it leverages CloudFront canonical names, I'm a bit skeptical if this would work in your setup. You can try and test it out, anyways.
Edit: As suggested by OP, there is a third approach to achieve this which is to handle this on the client side. Whenever client receives an unexpected response code or a timeout it makes an API call to another endpoint which is hosted on another region. This solution is a bit cheaper and simpler and easier to implement with current scheme of things available.
I have an aws lambda setup using nodejs to basically receive a request with query parameters, trigger another https request and then send the response back.
Configuration for this otherwise is essentially default.
I have then added a trigger to this lambda in the form of an api gateway HTTP api (not REST api).
I have managed to get the api itself to work however I am getting blocked with the usual CORS issues. (i verified the path with Moesif CORS and origin changer to make sure everything else works and it does).
My CORS configuration in the api gateway is basically set to have
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: *
Access-Control-Allow-Methods: *
Access-Control-Expose-Headers: No Expose Headers are allowed
Access-Control-Max-Age: 0 seconds
Access-Control-Allow-Credentials: No
I keep finding different pages explaining how to enable cors and so on but mostly seem to be either for an old version of the configuration or for REST api's which look to be quite different.
As it stands, I get this error so i never am allowed to use my api:
Access to fetch at 'https://path.to.my.api?query1=a' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
I would want to either allow all origins or disable CORS completely for this really.
Disclaimer: I am quite new to the whole aws infrastructure so some terminology related to it might still be not yet understood.
Edit 1:
After some more digging. I have realised that the call that is failing with the cors error is the first of the two calls happening.
That is to say, this is the call that is ending up on my google domain (which normally would redirect temporarily to my aws gateway - this was setup following instrctions on aws to make a "synthetic record" on the domain settings to return a 302 to the execute-api.eu-central-1.amazonaws.com url), not the call that returns the actual data.
Edit 2:
I have tried adding a route in my api gateway for OPTIONS on the same path, pointed to my lambda which returns the appropriate headers when triggered, however this doesn't seem to get called at all in this case. So i imagine api gateway is trying to handle it on its own but failing somehow
Is it possible to enable/disable caching a request through the AWS API Gateway in the response of the request?
According to this document: http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-caching.html It appears that the most granular one can get in defining cache settings is enabling/disabling caching for a specific API function. What I am wanting to do is allow the response for the API request to dictate whether or not it is to be cached. (i.e. I want my end API program to be able to determine if a response for a given request should be cached).
Is this possible, and if so how can it be accomplished?
Configure your own CloudFront distribution, with the API Gateway endpoint as the origin server. CloudFront web distributions respect Cache-Control headers from the origin server. If you customize that response, this should accomplish your objective.
API Gateway, as you may already know, runs behind some of the CloudFront infrastructure already, so this might seem redundant, but this appears to be the only way to take control of the caching behavior.
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.