Skip to content
This repository has been archived by the owner on Apr 17, 2023. It is now read-only.

A plugin that runs before every request is received to GW #33

Closed
hamxabaig opened this issue Jul 1, 2020 · 12 comments
Closed

A plugin that runs before every request is received to GW #33

hamxabaig opened this issue Jul 1, 2020 · 12 comments

Comments

@hamxabaig
Copy link

We're running our Graphql services in production using a micro services approach. We're using Hasura to combine everything together but we would like a gateway which we can configure and add our complex authorization to the queries/mutations before they're run.

Is this something graphql-gw can do? e.g flow:

-> Clients call query getUsers
-> GW receives request
-> GW runs beforeReq plugin
-> Plugin runs our code
-> Our code calls to our auth service to determine if current user is authorized to do this
-> If not, throws error without accessing upstream graphs
-> If yes, forward request to upstream graphs
-> Response to clients
@wtrocki
Copy link
Contributor

wtrocki commented Jul 1, 2020

Hi
Thank you so much for logging issue.
This makes sense to me (it is similar to #8).
Do you look for field level middlewares/relations etc.?
The easiest way this could be done that plugins could define some logic identified by label and then recall it when mounting upstreams + define some variables like role in your case. Alternatively there could be one middleware that recieves all requests and based on query info Devs could execute specific actions.

As workaround now you can add any code do gateway when building schema and wrap some specific elements with functions you want and compile your own gateway binary

Note: I'm community member and do not represent maintainer. Just throwing some ideas

@hamxabaig
Copy link
Author

I think if the query is parsed and is available to plugin/middleware, that'd be very good and will open a lot of possibilities. But it'll come with performance issue since all the query need to be parsed and then some AST needs to be made (not sure if we can do this, but would be very powerful for other use cases)

As workaround now you can add any code do gateway when building schema and wrap some specific elements with functions you want and compile your own gateway binary

Yes, we could do this but right now we're researching if something out of the box is available already by some open source solution.

But after researching, there seems to be no gw for GraphQL right now.

@wtrocki
Copy link
Contributor

wtrocki commented Jul 1, 2020

@hamxabaig - graphql-gw is community-driven solution. We are accepting ideas and PR's as well.
GraphQL-gw is unique as it offers bare metal performance and simplicity of creating gateways.
That is the core feature - fast and simple to have a gateway (and as you have mentioned there is nothing out there like it).

Once middlewares are added this can be affected so there needs to be wider discussion how to do it most efficient way, but if you have any suggestion that works for your case feel free to suggest anything in issue or PR. I will really look forward for @chirino opinion but middlewares are really something that most of the gateway will need. Let's work together on prototype on how this could work for you and we if it will make sense to @chirino I believe that we could work together to implement it in near future.

@hamxabaig
Copy link
Author

@wtrocki Sounds like a good idea. Though i'm not that proficient in Go but will surely help out in any way i can.

Our use case involves running a function or our own piece of code before every request and would like to know something about the queries/mutations (what entity the user is requesting and whats in the arguments). e.g

query {
  order(where: { order_id: 245 }) {
    id
   total_price
   ....
  }
}

For us, we'd like to know whats the query is calling e.g order and whats the input where.order_id so that we can run some authorization checks. We have standards in place for the arguments of the queries/mutations etc.

Then for our function that we'll run before each request. We'll send a request to ory/keto to check if the loggged in user has access to the resource he's trying to access. https://github.com/ory/keto

We'd like to put authorization as much we can in the gateway so that our services don't have to deal with authorization stuff (though its unavoidable and we understand there will be cases in which each service have to also implement authorization)

@wtrocki
Copy link
Contributor

wtrocki commented Jul 1, 2020

No need for golang knowledge - what we want to know is the specific use case to make sure we have good understanding.

This looks like typical middleware where you can inspect document (query name and arguments)
and maybe edit it - especially important to supply userId that cannot be provided by client for security reasons. I think that will be trivial to implement, but since this is golang it will require us to compile code or use plugin mechanism

As for go - there is plugin engine for dynamic loading of the code so there is technical capability to do so. I know the case we want something similar to the approach we have used in keycloak-connect-graphql(https://github.com/aerogear/keycloak-connect-graphql) library.

@chirino WDYT?

@hamxabaig
Copy link
Author

Cool, keycloak-connect-graphql looks very interesting but unfortunately its tied to keycloak. I'm gonna research more on them as well. Thanks for the useful resource 🙌

@chirino
Copy link
Collaborator

chirino commented Jul 2, 2020

Just spit balling an idea here. Perhaps we create a new 'Access Check' gateway action type. This would let us use an external service to do that field authorization check that you want. You would configure it to be triggered for specific Schema type and field and have it post something like the following to a URL of your choosing:

{
  "source": {
    "address": "10.25.95.68"
  },
  "request": {
    "http": {
      "host": "yourhost.com",
      "method": "POST",
      "path": "/graphql",
      "protocol": "HTTP/1.1",
      "headers": {
        "accept": "*/*",
        "authorization": "Bearer eyJhbGciOiJIUzI",
        "user-agent": "curl/7.68.0"
      }
    }
  },
  "graphql":{
      "fields":[
          {
            "path": "order",
            "type": "Query",
            "field":"order",
            "args": {
                "where": { "order_id": 245 }
            }        
          },
          {
            "path": "order/total_price",
            "type": "QueryResult",
            "field":"total_price"                  
          }
      ]
  }
}

The posted request would contain a all the original http request information, along with a list of graphql fields that had ‘Access Check’ actions enabled on them. and it would look for response like:

{
  "fields": [
    {
      "path": "order/total_price",
      "error": "not authorized"
    },
    {
      "path": "order",
      "add_headers": {
        "myheader": "foo"
      }
    }
  ]
}

This would allow your policy agent URL to disable access to fields or do things like add/remove headers to the request going to the upstream server. Thoughts?

@hamxabaig
Copy link
Author

@chirino so kinda like a webhook right? The gw will call the webhook to authorize the user with the body as you described? I think looks good but isn't it too specific for being a middleware (tied to authorization)?

@wtrocki
Copy link
Contributor

wtrocki commented Jul 3, 2020

I have played with the plugin's - created simple plugin and dynamically loaded it. It really works quite nicely (although there are some issues with that on windows platform).
This will work for community that understands and works with golang (for GraphQL this is still very small amount of people comparing to entire JS/Prisma/Hasura/AppSync guys.

Going this path I have tried is the sandboxed meta language. For example you could have pre/post actions that load some files from directory (or specified in UI) where you write some top level language like javascript with some limited syntax. Keycloak has something similar done using javascript for ACL lists. I have spiked that using https://github.com/robertkrimen/otto
It looks like compiling otto before requests and reusing it will work. There is also the ability to call some network requests:

https://play.golang.org/p/ptAOcgryW7

robertkrimen/otto#185

But this comes with some performance penalty:

https://nachiueno.tumblr.com/post/134866701373/golang-otto-vs-v8worker-vs-native-go-code

@chirino
Copy link
Collaborator

chirino commented Jul 3, 2020

@hamxabaig yeah like a webhook. The reason I'm shying away from implementing it as a middleware at the moment, is that I want to avoid opening up the gateway internal APIs at the moment. I agree that this type of webhook is limited to authorization or validation type use cases. But that seem to be what your were asking for in the issue.

Were there other use cases that you had in mind?

BTW this style of callback is what projects like the envoy proxy in istio use to authorize requests.

@wtrocki another sandboxed meta language that might be interesting is rego implemented by the Open Policy Agent which I played with a little here:

f3fbfb0#diff-55da125156dd1e612ed47c4ff4b79b74R41

Basically we can use that to run the policy expression in the gateway, instead of doing a webhook, but the down side is that I think the rego policy language is not sure easy to understand for most folks IMHO.

@wtrocki
Copy link
Contributor

wtrocki commented Jul 3, 2020

So having semaphore like system for access control can be useful and individual services can extend it with RBAC. I think you convinced me to not build any meta language here and keep it simple for users by definition of webhook.

Challenge I see is what format of webhook we will support - 200 ok 401 not ok to forward? We probably want to avoid situation when someone will need to setup wrapper for their IDM service to work with webhook. Eventually all auth stuff can be done anyway in individual services.

This needs to be simple by definition as if we allow some meta lang like js we can quickly end up with performance degradation which is the main target of gateway.

Another crazy idea I have is that maybe we should just define some standard for webhook and provide sample service written in nodejs where people can do those crazy ass checks, but to not degrade performance we can just pass data as some simplified json rather than query - so no graphql will be required to build this.
However we should then allow some strict timeouts and handle better errors to avoid taking down service.

@hamxabaig
Copy link
Author

@chirino the webhook implementation works for our use case atm yes. I was just curious about middleware implementation that @wtrocki mentioned in #8 but i agree that for now simple is better and faster.

Another thing that @wtrocki pointed out is performance which i agree on as well. Because the webhook will be called by gateway then it becomes bottleneck for gateway thereby degrading it; so some strict timeouts, good error handling and docs on how to build better webhooks should do trick.

chirino added a commit that referenced this issue Jul 14, 2020
…horizing field level access for GraphQL requests. Fiexes #33
chirino added a commit that referenced this issue Jul 14, 2020
…horizing field level access for GraphQL requests. Fixes #33
chirino added a commit that referenced this issue Jul 14, 2020
…horizing field level access for GraphQL requests. Fiexes #33
chirino added a commit that referenced this issue Jul 14, 2020
…horizing field level access for GraphQL requests. Fiexes #33
chirino added a commit that referenced this issue Jul 14, 2020
…horizing field level access for GraphQL requests. Fiexes #33
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants