Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide method for external authorization policy #768

Closed
michaelansel opened this issue Sep 18, 2014 · 10 comments
Closed

Provide method for external authorization policy #768

michaelansel opened this issue Sep 18, 2014 · 10 comments

Comments

@michaelansel
Copy link
Collaborator

Building off of the filtering mechanism in #724, I'm trying to build a mechanism for setting an authorization policy outside of individual scripts. I want to do this so I can specify my own policy around who can execute commands (and potentially what level of identify verification is required) without forking every script and injecting my own company-specific code.

I think the rough end-result is something like RBAC with a permissions file that maps individual listeners to permitted users/groups. In that model, I need a way to reliably reference individual listeners so that I can apply policy to them.

The most precise, but least stable, method I can think of would be to reference each listener by regex. This feels very dirty though...

The most stable method would be to add an explicit, unique identifier to every command. This could be accomplished by tweaking the respond/hear API to include an identifier field and putting together a PR for every script I intend to use. Not a terrible solution, but it would be a lot of work and requires lots of upstream acceptance.

An in-between option would be to specify an example message and then search for all listeners that match the message. This isn't perfect, but accomplish the goal well enough.

I'm sure other concerns will come up along the way, so opening the issue now to start collecting feedback and posting updates as I work on this.

@michaelansel
Copy link
Collaborator Author

After sitting on this for a while longer, I think option 2 (explicit identifier) is the way to go. However, as a workaround to modifying every script, we can namespace every script by the package/script name. As a result, you can always apply auth to the entire script. If you want finer grained control, you need to fork or submit a PR against the script.

In RBAC-speak, the components break down as follows:

  • Operation: maps 1:1 to a Listener (respond/hear block) in the script file. Operations are identified by a dot-separated name where the first component is always the package name and the second component is always the script name within the package. The rest of the hierarchy is left to the script developer. For backwards compatibility, a Listener without an Operation Identifier defaults to "package.script.unknown". An example Operation Identifier is "hubot-jira.jira.issue.create"
  • Permission: a script developer may implement default permissions (collections of Operations) for easier use by end-users. Permissions are named the same way as Operations. An example Permission Identifier is "hubot-jira.jira.modify". Alternatively, an end-user may choose to assign their own Permissions. The implementation for this last piece needs a little more thought.
  • Role: roles are managed completely independently of normal scripts and are a mapping of users to job functions. An example would be hubot-ldap (non-existent right now) that would manage mapping connected users to a set of LDAP groups (role_dev, role_network, role_dba, etc.). An external config file would then map Roles (LDAP groups) to Permissions. Assuming most people don't need the full power of RBAC, we also need to support a much simpler model (an example would be the current hubot-auth model). I'm still working on exactly how this should look, but I think a 1:1 mapping of Roles to Permissions to Operations might work. Given the large number of Operations though (assuming one per Listener), I imagine most users will end up wanting to create Permissions to collect up all the sensitive Operations.

The end goal is to allow script developers to easily expose a set of Operations that an end user can organize and authorize however they wish.

Things that still need thinking about:

  • Which module is responsible for inserting a filter into the message processing flow to ensure only permitted Listeners are executed?
  • What is the naming scheme for Permissions defined outside of a script? Example: I don't like the hubot-jira Permissions, and I want to define my own. What do I call them?
  • How can we make this a simple as possible for bot owners that have very simple authorization needs?
  • Should Role hierarchies be the responsibility of the role module or be shared somehow?

@michaelansel
Copy link
Collaborator Author

Simple example:

  • hubot-auth links Users to Roles (managed via chat, stored in the brain)
  • bot owner creates config file leveraging the provided Permissions
developer:
- hubot-heroku.heroku.view-logs
- hubot-heroku.heroku.deploy
customer_service:
- hubot-statuspageio.statuspage.modify
  • HUBOT_RBAC_DEFAULT_RULE=allow

This would result in restrictive rules for the 3 listed Permissions, but everything else would be allowed.

This seems simple to me, but I've been thinking a lot about RBAC. Is this simple enough for less-paranoid organizations?

@michaelansel
Copy link
Collaborator Author

Example script with Operation names and basic Permission assignment:

# somehow need to auto-extract the module and file names for the base namespace
module.exports = (robot) ->
  robot.rbac?.addPermissions?({
    'modify': ['create-issue', 'comment-on-issue', 'close-issue'],
    'read': ['issue-summary'],
  })

  robot.respond /open an issue ticket for (.*)$/, {id:'create-issue'}, (msg) ->
    # create a new ticket

  robot.hear /https:\/\/jira.example.com/browse/[A-Z]+-[0-9]+$/, {id:'issue-summary'}, (msg) ->
    # display info about the ticket

@michaelansel
Copy link
Collaborator Author

@technicalpickles (or whoever you want to redirect to) Can you take a look at the last 2 comments and let me know if that seems like a reasonable path to start implementing?

@michaelansel
Copy link
Collaborator Author

@tombell I just discovered #399 and it looks like it has roughly the same goal. Based on your previous work, do you have any thoughts on building this out as described above?

@tombell
Copy link
Contributor

tombell commented Oct 23, 2014

I don't personally use hubot much anymore, I have my own private robot implementation.

@michaelansel
Copy link
Collaborator Author

bummer... well, thanks for the fast response!

@technicalpickles
Copy link
Member

@michaelansel thanks for putting this all together. It's quite a bit to parse, which is part of why I've been '...', but I've definitely been thinking on making hubot more flexible to allow for these kinds of behaviors.

Overall, I'm liking the approach. The things I'm particularly interested/concerned about are backwards-compatability with existing hubot installs/scripts, and the complexity of the code going into hubot itself.

Which module is responsible for inserting a filter into the message processing flow to ensure only permitted Listeners are executed?

One idea I've been mulling about is the applying the idea of having middleware in place for when listeners are invoked. That's a concept I could see living in hubot itself.

That plays nicely into listeners having ids and other metadta. I could easily imagine there being things like automatically collecting metrics & logs.

That still has the problem of what adds the middleware? It could be a new robot.listenerMiddleware call, but you have some possible ordering problems. They could be included as external-scripts, which are loaded in order, but it's not particularly obvious that the order is signiciant if you are looking at the file.

For that, I've been considering something similar to rack's config.ru file. That is, an initial file that gets loaded, before any scripts are loaded, that can be used to programatically make changes to the boat. This would be the place to add in middleware. I think it'd have the benefit of eventually being able to get rid of hubot-scripts.json and external-scripts.json in favor of programatically loading things.

What is the naming scheme for Permissions defined outside of a script? Example: I don't like the hubot-jira Permissions, and I want to define my own. What do I call them?

Config file, I suppose. Or maybe make a script loaded after the core one, that redefines the permissions?

How can we make this a simple as possible for bot owners that have very simple authorization needs?

I think it'd make sense to lay out some use cases to help flesh this out. Here's what I would imagine are the simplest common ones:

  • Anyone can do anything
    • this is the current default, so I would keep this for now
  • Some employee or staff role can do anything, everyone else can't do anything

Anything after that is going to be more complex, like allowing some roles some things, other roles other things, and blacklisting anything else.

Example script with Operation names and basic Permission assignment:

The example looks on par to me. Only comments are:

  • I would make sure the data to robot.rbac is namespaced by the script. I think you'd just want to pass it in a name, and then the permissions for it.
  • I'm not positive, but I think you'd be able to call the scripts like respond /open an issue ticket for (.*)$/, id: 'create-issue', (msg) ->

@technicalpickles
Copy link
Member

Work is progressing over in #803 to add listener middleware. Once that's done, work can proceed on an external hubot script for authorization ie https://github.com/michaelansel/hubot-rbac

Going to leave this open until the listener middleware merges.

@technicalpickles
Copy link
Member

Going to leave this open until the listener middleware merges.

It's been merged for awhile 😂

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants