event-gateway icon indicating copy to clipboard operation
event-gateway copied to clipboard

Looking for authorizer specification example

Open fubar opened this issue 6 years ago • 21 comments

I'm in the process of evaluating this project for our API. It looks promising, but one core aspect is unclear to me.

Could you provide an example of how I would specify/reference an authorizer function in serverless.yml?

Thanks!

fubar avatar Jul 05 '18 02:07 fubar

Hey @fubar,

sure, here is the example:

# serverless.yml

custom:
  eventTypes:
    user.created:
      authorizer: authFunc

functions:
  hello:
    handler: handler.hello
    events:
      - eventgateway:
          type: sync
          eventType: user.created
          path: /hello
          method: GET
  authFunc:
    handler: handler.auth

mthenw avatar Jul 05 '18 12:07 mthenw

Closing for now. Let me know if you have more questions.

mthenw avatar Jul 05 '18 12:07 mthenw

Thanks @mthenw, that is helpful! Am I understanding it right that authorizers can only be associated with events? If I wanted to call a function based on an HTTP request with authentication as well as based on a non-HTTP event without any authentication, I would need two different event types? I initially expected authorizers to be specified in the function definition alongside eventType as to allow for easy composition.

fubar avatar Jul 05 '18 20:07 fubar

I took a deeper dive into this today and tried out a few things, and would like to share a few observations. Now I am aware that this project is still in its early stages and under active development, but I did not want to miss the chance to point out these things and get your thoughts and help.

  1. I cannot get an authorizer to get called. Your example doesn't work. Any time I reference an authorizer, all HTTP calls return a 403 Forbidden, and by means of Cloudwatch logs I can see that the authorizer function was not called. The same is true for non-HTTP events triggered through the command line (e.g. serverless gateway emit --event "test.foo" --data "{}"). On top of that, it looks like the first deploy of an authorizer (when it's new or after I've changed its name) causes the function to execute successfully, as if no authorizer was specified at all. Subsequent deploys cause the 403 behaviour described above.
  2. Any function that should be accessible over HTTP needs to have eventType: http.request. This means that an authorizer (once working) needs to be set on the generic http.request event type, which in turn means that either all of my API endpoints have to use the same type of authentication, or I need to make the authorizer aware of which endpoint is called and use different logic accordingly, which defeats the purpose of the original intention to specify different authorizers for different events/endpoints. This is somewhat related to my initial expectation that authorizers would be linked to endpoint definitions, and not to event types.
  3. For functions that are subscribed to "regular", non-HTTP events, it doesn't seem to make a difference whether I use type: sync or type: async: Event emission returns immediately in both cases, with the same response payload, whether I emit the event from command line or in code through the SDK. It would be incredibly useful if I could fire events in code and wait for the response. It did however affect the parameters that need to be passed to the SDK method: When using async, this works:
eventGateway
  .emit({
    event: "test.foo",
    data: { foo: "bar" }
  })

When using sync, the above throws an error. It requires parameters like specified at https://github.com/serverless/event-gateway-sdk#emit:

eventGateway
  .emit({
    eventType: "test.foo",
    eventTypeVersion: "1.0",
    source: "/foo/bar",
    contentType: "application/json",
    data: {
      foo: "bar"
    }
  })

I'd be happy to help debug further.

Also, when are you planning on publishing a stable release version?

Thanks!

fubar avatar Jul 06 '18 05:07 fubar

@fubar thanks for your feedback.

ad. 1. I will look into that and let you know ad. 2. Specifying authorizer on event type is the first step. We are aware of limitations that you mentioned. We are thinking about allowing defining authorizers either on endpoints (method + paths) or on subscription level. ad. 3. For custom events EG should behave differently depending on type of subscription. For async subscription it should return 202 Accepted for sync subscriptions it should return response from subscribed functions. I will investigate this weird behavior that you mentioned.

mthenw avatar Jul 11 '18 07:07 mthenw

@fubar Thanks for the detailed feedback. This is really helpful.

I did some digging today and found some of the root causes of your issues.

  1. Most importantly, the Framework plugin wasn't registering functions in the Event Gateway that didn't have an eventgateway event attached to them. This meant the authFunc wasn't registered in the Event Gateway. There's an issue tracking this bug, and it should be fixed soon.

    Semi-related, there's an issue for the Event Gateway SDK that will allow users to specify a path and headers for an emit() request. This will be helpful as subscriptions are set up on paths, and headers may be the mechanism for authorization.

  2. You're right on the authorization for http.request. Our initial goal for authorizers was to set them up for Event Types and solve the asynchronous / pub-sub auth issue. It didn't make sense for each downstream subscriber to implement auth for an asynchronous event that's emitted, so it need to be on the Event Type.

    We'll still working on the mechanisms for allowing auth on http.request. As Maciej noted, it will likely be on the subscription.

  3. Still getting to the last issue around sync and async for emitting. Will follow up when I know more.

Thanks again, and feel free to reach out if you have more questions.

alexdebrie avatar Jul 13 '18 21:07 alexdebrie

Thanks for the responses @alexdebrie and @mthenw and for addressing the issues. Looking forward to further updates!

fubar avatar Jul 13 '18 22:07 fubar

@fubar I tested the sync / async emit behavior with the event-gateway-sdk, and I was able to get it to work.

I set up two subscriptions -- an async one for user.deleted, and a sync one for user.created. Then I ran this script:

// Construct your client
const SDK = require('@serverless/event-gateway-sdk');
const eventGateway = new SDK({
  url: process.env.EVENT_GATEWAY_APP_URL
})

// Test the async subscription
eventGateway.emit({
  eventType: 'user.deleted',
  eventTypeVersion: '1.0',
  cloudEventsVersion: '0.1',
  eventID: 'e58961a5-f53b-4849-8ae5-cb06c031919e',
  source: '/services/users',
  contentType: 'application/json',
  data: {
    userID: 123
  }
}).then(res => console.log(res.status))


// Test the sync subscription
eventGateway.emit({
  eventID: '1',
  eventType: 'user.created',
  cloudEventsVersion: '0.1',
  source: '/services/users',
  contentType: 'application/json',
  data: {
    userID: 'foo'
  }
}).then(res => res.json())
  .then(json => console.log(json))

The first one returns immediately with a 202 status code and an empty body. The second one returns with a 200 status code and the response from my Lambda function.

In the collapsed sections below, I have my serverless.yml and handler.py if you'd like to reproduce. The synchronous user.created also has an authorizer on it to show how to get that to work. You might need to use the master branch of the serverless-event-gateway-plugin as I've fixed a few bugs but haven't released a new version yet.

Let me know if that solves your problem! I'll keep this ticket open until you give the sign off 👌.

serverless.yml
# serverless.yml

service: event-gateway-auth

plugins:
  - "@serverless/serverless-event-gateway-plugin"

custom:
  eventgateway:
    url: ${env:EVENT_GATEWAY_APP_URL}
    accessKey: ${env:EVENT_GATEWAY_ACCESS_KEY}
  eventTypes:
    user.created:
      authorizer: auth
    user.deleted:

provider:
  name: aws
  runtime: python3.6
  stage: dev
  region: us-east-1

functions:
  auth:
    handler: handler.auth
  main:
    handler: handler.main
    events:
      - eventgateway:
          type: sync
          eventType: user.created
          path: /
          cors: true
  delete:
    handler: handler.main
    events:
      - eventgateway:
          type: async
          eventType: user.deleted
          path: /
          cors: true
handler.py
# handler.py

import json


def auth(event, context):
    print(event)
    body = {
        "message": "Go Serverless v1.0! Your function executed successfully!",
        "input": event
    }

    response = {
        "authorization": {
            "principalId": "user123",
            "context": {
                "name": "Bill Gates"
            }
        }
    }

    return response

def main(event, context):
    print(event)
    body = {
        "message": "Go Serverless v1.0! Your function executed successfully!",
        "input": event
    }

    response = {
        "statusCode": 200,
        "body": json.dumps(body)
    }

    return response

alexdebrie avatar Jul 16 '18 15:07 alexdebrie

@alexdebrie I can confirm the above example works for a local Event Gateway instance (latest master) with Serverless version 1.29.2 and serverless-event-gateway-plugin version 0.7.8, but not for the platform version (app and tenant specified in the serverless.yaml file), where I get the "Failed to update user.created event type due the error: Authorizer function doesn't exists." error.

Edit I get the same error on my local EG instance. Looks like the eg serverless plugin tries to update the user.created event type before the authorizer function gets registered

baniol avatar Aug 05 '18 23:08 baniol

Hi, Is there any update on the issue? As per what @baniol reported, I'm similarly getting `Authorizer function doesn't exists." errors. Thanks.

johnlim avatar Sep 13 '18 13:09 johnlim

Hey @johnlim, sorry to hear that. I just tested a few times and it worked for me.

Can you share the following things:

  1. the version of the Serverless Framework you're using.
  2. the version of the serverless-event-gateway-plugin you're using.
  3. the serverless.yml you're trying to deploy.

Thanks!

alexdebrie avatar Sep 13 '18 18:09 alexdebrie

Hi @alexdebrie thanks for looking into this.

 My Environment Information -----------------------------
     OS:                     darwin
     Node Version:           8.11.3
     Serverless Version:     1.30.3
--------serverless-event-gateway-plugin
  "version": "0.7.8"
# -------------serverless.yml

functions:
  createUser:
    handler: handler.createUser
    events:
      - eventgateway:
          type: sync
          eventType: http.request
          path: /users
          method: POST
  userCreated:
    handler: handler.userCreated
    events:
      - eventgateway:
          type: async
          event: USER_CREATED
  authorizerFunc:
    handler: handler.authorizerFunc

custom:
  eventTypes:
    http.request:
    USER_CREATED:
      authorizer: authorizerFunc

My current use case though is to do auth on http.request. Is there any update on this as well? Thanks!

johnlim avatar Sep 14 '18 02:09 johnlim

Hi @alexdebrie, were you able to duplicate this? Do let me know if you need any further info. Cheers.

johnlim avatar Sep 18 '18 02:09 johnlim

Hey @johnlim, I think I found the issue. I was using an older version of the plugin.

I've got a pull request open for it in the plugin repo here. Can you test and let me know if it solves your problem?

Thanks for your patience with this!

alexdebrie avatar Sep 18 '18 14:09 alexdebrie

@alexdebrie Thanks for the fix! I've verified that it fixes the aforementioned issue. Really appreciate it

johnlim avatar Sep 19 '18 05:09 johnlim

@alexdebrie If I understand correctly, the current design for authorizers on http requests require them to be tied to http:request event types? For example:

custom:
  eventTypes:
    http.request:
       authorizer: authorizerFunc
    USER_CREATED:
      

If so, how would I declare a public http endpoint if I also need to declare some private ones (i.e with authorizer functions)?

We'll still working on the mechanisms for allowing auth on http.request. As Maciej noted, it will likely be on the subscription.

Any update on this? Thanks.

johnlim avatar Sep 19 '18 05:09 johnlim

We'll still working on the mechanisms for allowing auth on http.request. As Maciej noted, it will likely be on the subscription.

@alexdebrie Are there use cases for multiple subscribers to a single http event? If not, instead of supporting authorizers on subscribers of http.request event types, why not simply have users implement http endpoints via the "traditional" serverless way like so

functions:
  main:
    handler: handler.main
    events:
       - http:
          path: /
          method: GET
          authorizer: auth

and have the http endpoints emit events, if needed. Not only does this save development effort but also makes it easy to setup custom domains for the endpoints.

That does however bring up a big security question. Based on my understanding, it seems that anyone who knows your Event Gateway url, will be able to emit events. For example, by using the event-gateway-sdk, one can simply input the tenant url and call emit(). i.e hackers could start injecting events into your system. The workaround would be to require all events to implement authorizers but seems to a high overhead. Is that the intention/recommendation?

johnlim avatar Sep 21 '18 00:09 johnlim

@alexdebrie @mthenw Just following up to see if you have any advice for me for the aforementioned issue. Thanks in advance.

johnlim avatar Oct 02 '18 09:10 johnlim

Hey @johnlim, great question.

While the Event Gateway does allow for REST-like HTTP endpoints, it's really intended to be an event router more than a traditional API Gateway. As such, it's not as full-featured in the API Gateway department, such as allowing for granular authorization strategies on different endpoints. We may add this functionality in the future but are presently focused on more event-driven flows.

Does that help?

alexdebrie avatar Oct 04 '18 01:10 alexdebrie

Hi @alexdebrie, Thanks for getting back. So if I understand correctly,

  1. In the meantime, for http endpoints that need granular control, it's recommended that we use the traditional serverless/API gateway approach instead of event-gateway REST-like HTTP endpoints?
  2. As event-gateway events can be triggered/emitted by external systems (as long as they know the event-gateway tenant url), all subscribers must/should implement authorizers to validate if they should execute? Thanks in advance.

johnlim avatar Oct 04 '18 01:10 johnlim

Hey guys, we're using the "@serverless/serverless-event-gateway-plugin": "0.7.8" version in one of our projects however the issue mentioned here: https://github.com/serverless/event-gateway/issues/478#issuecomment-410555962 still persists. Has anyone else experiences the same problem since the merge to master for 0.7.8?

WarWolf89 avatar Nov 06 '18 13:11 WarWolf89