best way to access req.body in policy - express-gateway

I am currently working on a custom plugin realizing an oauth solution. I decided to implement a proper policy that forwards the incoming login post to an external service. Therefore I have to access the body of the request (property req.body), which is only possible when the required body parser is enabled as express - middleware. Unfortunately, I could not find a comfortable way to enable body parsing within the gateway application. Consequently, I made a workaround by registering a proper route in order to access the underlying expressapp object.
pluginContext.registerGatewayRoute(app => { app.use(express.json()); }
I do not want to substitute the policy by simply registering a route, because I did not find a way to apply other policies (e.g.: CORS, RATE LIMITER...) to this route.
Please let me know if I oversee something and there is an easier way to enable body parsing.

yes, using "registerGatewayRoute" will apply middleware to every route in EG. What you can do is create a body parser policy. Policy in EG is just a wrapper around ExpressJS middleware so
so body parser policy can contain code like
{
name: 'bodyparser',
policy: (actionParams) => express.json()
}
https://www.express-gateway.io/docs/plugins/policy-development/
Now just add this policy as first one in the pipeline and it should provide req.body for all routes going through that pipeline

Thanks a lot for your helpful answer. As suggested I created a bodyParser policy. Only a small modification of the previous answer was necessary: Instead of express.json I had to call the function express.json() in order to get required middleware functionality.
const express = require('express');
module.exports = {
name: 'bodyParser',
policy: (actionParams) => express.json()
};
Works fine now and body parsing is only enabled where it is really required.

Related

API Gateway Lambda CORS handler. Getting Origin securely

I want to implement CORS for multiple origins and I understand I need to do so via a lambda function as I cannot do that via the MOCK method
exports.handler = async (event) => {
const corsUrls = (process.env.CORS_URLS || '').split(',')
const requestOrigin = (event.headers && event.headers.origin) || ''
if (corsUrls.includes(requestOrigin)) {
return {
statusCode: 204,
headers: {
"Access-Control-Allow-Headers": 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Requested-With',
'Access-Control-Allow-Origin': requestOrigin,
'Access-Control-Allow-Methods': 'POST,DELETE,OPTIONS'
}
}
}
return {
statusCode: 403,
body: JSON.stringify({
status: 'Invalid CORS origin'
})
}
}
Firstly, does the above looks ok? Then I am getting origin from headers event.headers.origin. But I find that I can just set that header manually to "bypass" cors. Is there a reliable way to detect the origin domain?
Firstly, does the above looks ok?
Your code looks good to me at first glance, and other than your point But I find that I can just set that header manually to "bypass" cors, I don't see any major problems with it.
Then I am getting origin from headers event.headers.origin. But I find that I can just set that header manually to "bypass" cors. Is there a reliable way to detect the origin domain?
The code you are currently using is the only way I can think of how to detect the origin domain off the top of my head. Although as you said, you can just set that header manually, and there is 0 assurances that header is correct or valid. It shouldn't be used as a layer of trust for security. For browsers, they restrict how this header can be set (see Forbidden header name). But if you control the HTTP client (ex. curl, postman, etc.) you can easily can send whatever headers you want. There is nothing technology wise preventing me from sending any headers with whatever values I want to your web server.
Therefor, at the end of the day, it might not be a huge concern. If someone tampers with that header, they are opening themselves up to security risks and unexpected behavior. There are a ton of ways to bypass CORS, like this, or this, or this maybe. So at the end of the day, it's possible to bypass CORS, despite your best efforts to enforce it. Although all of those tricks are hacks, and probably won't be used by normal users. Same with changing the origin header, not likely to be done by normal users.
There are a few other tricks you could look into tho to try to enforce it a little bit more. You could look into the refer header, and see if that is the same as the origin header. Again, possible to send anything for any header, but will make it a bit harder and enforce what you want a little bit more.
If you assume that your origin header should always equal the domain of your API Gateway API then the other thing you can look into is the event.requestContext object that API Gateway gives you. That object has resourceId, stage, accountId, apiId, and a few other interesting properties attached to it. You could look into building a system that will also verify those and based on those values, determine which API in API Gateway is making the request. This might require ensuring that you have separated out each domain into a separate API gateway API.
I don't see anyway those values in the event.requestContext could be tampered with tho since AWS sets them before passing the event object off to you. They are derived from AWS and can not be easily tampered with by a user (unless the entire makeup of the request changes). For sure a lot less tamperproof than headers which are just sent with the request, and AWS passes through to you.
Of course you can combine multiple of those solutions together to create a solution that enforces your policy more. Remember, security is a spectrum, so how far down that spectrum you go is up to you.
I would also encourage you to remember that CORS is not totally meant to hide information on the internet. Those methods I shared about how you can bypass CORS with a simple backend system, or plugin, show that it's not completely foolproof and if someone really wants to fake headers they will be able to. But of course at the end of the day you can make it as hard as possible for that to be achieved. But that requires implementing and writing a lot of code and doing a lot of checks to make that happen.
You really have to ask yourself what the objectives and goals are. I think that really determines your next steps. You might determine that your current setup is good enough and no further changes are necessary. You might determine that you are trying to protect sensitive data from being sent to unauthorized origins, which in that case CORS probably isn't a solid solution (due to the ability to set that header to anything). Or you might determine that you might wanna lock things down a bit more and use a few other signals to enforce your policy a bit more.
tldr You can for sure set the Origin header to anything you want, therefor it should not be completely trusted. If you assume that your origin header should always equal the domain of your API Gateway API, you can try to use the event.requestContext object to get more information about the API in API Gateway to gain more information about the request. You could also look into the Refer header to see if you can compare that against the Origin header.
Further information:
Is HTTP Content-Length Header Safe to Trust? (disclaimer: I posted this question on Stack Overflow a while back)
Example Amazon API Gateway AWS Proxy Lambda Event
Bypass CORS
https://github.com/Rob--W/cors-anywhere
https://chrome.google.com/webstore/detail/allow-control-allow-origi/nlfbmbojpeacfghkpbjhddihlkkiljbi
https://www.thepolyglotdeveloper.com/2014/08/bypass-cors-errors-testing-apis-locally/
Refer Header
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referer
https://en.wikipedia.org/wiki/HTTP_referer
The only way to validate multiple origins is as you did, with your lambda read the Origin header, compare that to the list of domains you would like to allow, and if it matches, return the value of the Origin header back to the client as the Access-Control-Allow-Origin header in the response.
An information: the Origin header is one of the headers that are set automatically by the user agent.. so anyone can't altered programatically or through extensions. For more details, look at MDN.

Avoid CORS with AWS Cloudfront, and clean SPA urls

I have a single-page app that lives in S3, fronted by Cloudfront. There's also a backend that the SPA talks to via AJAX requests. I'm trying to both:
have clean URLs (a la https://keita.blog/2015/11/24/hosting-a-single-page-app-on-s3-with-proper-urls/), by asking cloudfront to rewrite 403 and 404 errors into 200s that produce the index page, and
avoid CORS issues by adding a cloudfront behavior to proxy /api/* to the server (like https://stackoverflow.com/a/42883221/1586229).
Is it possible to accomplish both of these? The problem is that cloudfront will change even 403s and 404s that come from the api into 200 index.html responses.
If this can't be done, can you recommend another way to accomplish what I'm trying to do?
This behavior can be accomplished with Lambda#Edge. Here's the plan:
Create a Lambda function that will be triggered on Origin Request (see diagram for where in the lifecycle that lands). Be sure to create it in us-east-1, as that's the only region where Lambdas to be used in Lambda#Edge can be defined.
The function has the following job: rewrite requests to paths like /login, /profile, /anything_other_than_assets into /index.html. For me, I was able to make the rule:
Something is an asset if it has an extension. Otherwise, it's a path
Hopefully, you can do the same, or similar. Here's how my function body looked (I used node 8)
const path = require('path')
exports.handler = (evt, ctx, cb) => {
const {request} = evt.Records[0].cf
if (!path.extname(request.uri)) {
request.uri = '/index.html'
}
cb(null, request)
}
Make sure you "Publish a new version", and then copy the arn (found at the top of the page)
paste it in the "Lambda function associations" section of your S3 Origin's Behavior.
Because Lambda#Edge function associates are scoped to the Origin level, this redirect behavior won't affect your /api/* behavior.
Make sure to remove the custom error handlers. You won't need them anymore for your S3 behavior, and you don't want them for your api behavior.

Lambda function custom domain

I have been messing around with AWS lambda today trying some things out. I am currently trying to trigger the function from a url in a browser.
The URL looks similar to this: https://abcdef.execute-api.eu-west-2.amazonaws.com/default/test
As I understand it I can assign a custom domain to my endpoint, but can I also get rid of the path part of the url, so for example:
GET: https://example.com/
GET: https://example.com/somefile.txt
POST: https://example.com/ ['some_post_field' => 'some data']
Will all be passed to my function, or do I need to configure an EC2 instance with NGINX to proxy-pass the requests to lambda?
Any thoughts would be useful.
There are now a couple different ways you can accomplish this in AWS:
The newest (arguably coolest!) is to use Cloudfront to run your code using their Lambda#Edge service. You can completely customize your URL path and have portions used as variables like any other REST endpoint. You attach your Lambda fn to "behaviour" endpoints which give you full URL control. Its fairly deep and beyond the scope of your question to explain it all here, but read through the docs at the link provided and you'll likely see lots of stuff you like.
Another older, more expensive but more documented method is to use AWS's API Gateway as you have eluded to in your question's tags. It has a great front end console and is easy to connect API endpoints to your Lambda backend logic by attaching them to REST methods. The console helps you "variable-ize" your URL with form field data. This service helps you the most with custom domains to trigger from. Setting up custom domains is a snap in API Gateway. Be sure to use AWS's SSL Certificate Manager for free SSL certs on your custom domain too!
How you specifically setup your endpoints depends on which service you choose. Personally, given your desire to serve up different types of content, I would lean towards CloudFront, and define a "behaviour" URL for your dynamic Lambda content. If the URL request does not match one of your defined behaviours, it defaults to the Cloudfront cache/origin to serve your static assets (somefile.txt). Only matches go to your attached Lambda fn with form data. Very slick!
A lot of example Lambda#Edge fn's are available here.
I have used both and have clients on both now. Lambda#Edge is ridiculously faster and less expensive, BUT is less documented, has a steeper learning curve, and console is not nearly as helpful. I would honestly try both to see which fits your situation and experience level best. Both will get the job done. EC2 is most definitely NOT needed (nor desired perhaps). Hope that helps — good luck!
Instead of directly exposing the Lambda function via URL, expose it through AWS API Gateway where you can define your own paths and map to a Domain.

API Gateway CORS Issue

So I have CORS enabled going through the basic setup given by AWS Gateway. However for this API I need to allow Control Origins for all requests and allow credentials.
Here is what it looks like
The issue as you may have guessed is this setup is not allowed by CORS, you can not have a wildcard for Origin and have credentials as true. Normally the work around for this is to just grab the requesting domain and add it into the Origin Header. Which is more or less what I want to do. But I don't know how to get that information and add it as a mapping value. Where does API Gateway store that information and how do i get it?
UPDATE:
I have to pass through HTTP Header Host to my Lambda Function which I should have mentioned earlier, I have tried implementing the Answer below but I cannot access the header to pass it to the Lambda function using the instructions provided. Any more assistance with this is greatly appreciated.
Okay After hours of research and finding bits of information across the internet I have a solution and hopefully it is useful for other people.
To pass an HTTP Header that is not a default value provided by AWS API Gateway and then access that data via a Lambda Function and return that data in the Response Header follow the steps below
In "Method Request" go to "HTTP Request Headers" and add your desired header to capture. ie. if we want to get the host value of the API url you can enter "Host" here. if you want to get the website host of the caller use "Origin"
In "Integration Request" go to mapping templates and create a new template if an "application/json" does not exist, if it does just update it.
This is the important part, pass the header value you set in step 1. To do that write something similar to the following in the Template Box.
{
"origin" : "$input.params().header.Origin",
"host" : "$input.params().header.Host"
}
You can also pass in any url parameters you have defined in the same JSON.
Access the data from Lambda, The integration request passed the information into the "Event" parameter if using Node as the Lambda Backend code. to retrieve the value of any header just use the following within your handler.
event.origin;
When sending back your response from Lambda to API Gateway, it is best to format the response in JSON. Something similar to this.
{
"origin" : event.origin,
"host" : event.host,
"nonHeaderOutput" : "Hello World"
}
In "Integration Response" go to "Header Mappings", if the header you need is not listed you may add it in "Method Response" and it will then appear here. For this example I used "Access-Control-Allow-Origin" and edited the Mapping Value to be integration.response.body.origin
now go to "Mapping Templates and select the content type you want to use, and then edit the template to access the non header responses by adding this to the Template Box
$input.path("$.nonHeaderOutput")
That is it now the header that was sent to the API can be used in your method Response.
Jurgen from API Gateway here.
Thanks for reporting this issue. There is currently no simple way to set it up via the "Enable CORS" feature in the console. However, we are looking into it to improve the user experience for this use case.
As a potential workaround, you could passthrough the Origin header from the client to your backend and parse / create the value for the Access-Control-Allow-Origin header there. Then you would map the Header in the Integration Response from 'integration.response.header.Access-Control-Allow-Origin' to Access-Control-Allow-Origin and return it to the client.
Hope this helps.
Best,
Jurgen

Testing WebAPI actions that have authorization filters

I’ve got a Web API that I’ve added [Authorize] attributes to, which means that the unit tests I had previously now fail due to them being unauthorised. Here’s a sample of a basic test along with an initialiser method:
[TestInitialize]
public void CreateServer() {
var config = new HttpConfiguration();
WebApiConfig.Configure(config); // Create the routes
var server = new HttpServer(config);
this.client = new HttpClient(server);
}
[TestMethod]
public void MyThings_GET_Returns_All_MyThings() {
var response = this.client.GetAsync("http://localhost/api/1.0/mythings").Result;
var mythings = response.Content.ReadAsAsync<IEnumerable<MyThing>>().Result;
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
Assert.AreEqual(4, mythings.Count());
}
What I’m wondering is if there’s any way that I can either make my test log in so that it passes the authorization filter, or if there’s any way that I can pass as ASPXAUTH cookie along with the HttpClient request? Or another way of passing authorization that I haven’t thought of?
Nothing I’ve tried seems to work and I’m struggling to find any helpful info anywhere.
Thanks in advance.
What does your Authorize attribute do when it performs the authorization check? There are quite a few options that come to mind:
Have the authorize filter support multiple means of getting the "authorization token" that it requires (e.g. through an HTTP header or a querystring parameter, etc)
Right after your test initialization, clear out the filter from the configuration (so that it is not called at all). If you choose to go this route then you may wish to pop in a new filter that sets any authorization values that might be used further along the pipeline
If you are using dependency injection, move the "authorization check" into some sort of IAuthorize location that can be updated in your configuration
I would also recommend using RestSharp for making queries to your endpoints as it does a very good job of specifying headers, parameters, etc.
I decided that the way I was going about the problem was fundamentally wrong. Using cookie-based authorisation with Web API is just not a good idea, so I’ve decided to get rid of the authorize attributes and perform API-Key-based authentication instead. This makes it easier to test as I can just pass the correct API key in the request, but also means that I’m not relying on cookies for authorisation.