ory / keto

Open Source (Go) implementation of "Zanzibar: Google's Consistent, Global Authorization System". Ships gRPC, REST APIs, newSQL, and an easy and granular permission language. Supports ACL, RBAC, and other access models.
https://www.ory.sh/?utm_source=github&utm_medium=banner&utm_campaign=keto
Apache License 2.0
4.75k stars 345 forks source link

High level RBAC API/manager #598

Closed zepatrik closed 5 months ago

zepatrik commented 3 years ago

Is your feature request related to a problem? Please describe.

Implementing RBAC currently is possible, but requires a lot of thought and workarounds. A current solution is described in https://gruchalski.com/posts/2021-05-15-rbac-with-ory-keto/

Describe the solution you'd like

From that post:

If there was a higher level system responsible for actual role management, the tool could present a role object called IT Director with these granular tuples hidden from view. The role management operator would be working with that abstraction instead of these granular items.

Describe alternatives you've considered

  1. keep as is: con RBAC is pretty standard, so should be supported somehow
  2. offload to other service: not having RBAC as part of the core functionality but instead a separate "RBAC manager" that wraps around Keto

cc @radekg @christian-roggia

christian-roggia commented 3 years ago

That article is an awesome explanation of what is needed and the current limitation of Keto.

As discussed on Slack, I am still not satiesfied about the following point:

This makes sense because if a certain action should no longer be allowed for a certain role, there should be no need to iterate over every object to remove a permission.

By looking at https://gruchalski.com/posts/2021-05-15-rbac-with-ory-keto/#fry-has-fried-the-production the proposed solution does indeed require manual removal of the bindings from all objects.

This means that if I have 100.000 objects where I assigned the role fast-dev-director and I want to revoke the create permission I need to iterate over all of them with a DELETE call:

curl --silent -X DELETE 'http://localhost:4467/relation-tuples?namespace=default-namespace&object={object}-creator&relation=member&subject=default-namespace:fast-dev-director#member

Or did I actually miss something?

I belive that userset rewrites would probably allow for a more complete (H)RBAC implementation as described in https://www.usenix.org/system/files/atc19-pang.pdf

zepatrik commented 3 years ago

I think you are right, revoking a permission for a role does require iterating. But this could either be done by the high level RBAC solution proposed here, or enabling bulk deletion by providing partial relation tuples. I will open another issue for that idea, as it might be useful for other cases as well (think "purge user/object from everywhere").

christian-roggia commented 3 years ago

Bulk deletion could be a working solution, but then again there would be no solution for the opposite operation: addition.

In RBAC I must have a way to both add and delete permissions from a role. Ideally this operation shouldn't require touching thousands or million of tuples as performance and consistency would probably be hard to guarantee.

It is my understanding that if user rewrites were available no high-level RBAC API would be required, am I correct? Would RBAC work out of the box with user rewrites?

zepatrik commented 3 years ago

Actually, I think we can circumvent that limitation right now, if we fully ditch the object id. So basically everything from the blog post, but remove the first step. In this case it would not be related to specific objects anymore, but the permission itself is the object. You only have the following relation tuples:

create#execute@it-director#member
view#execute@it-director#member
delete#execute@it-director#member

view#execute@dev-director#member

it-director#member@Hermes
dev-director#member@Fry
dev-director#member@Bender

Revoking the create permission for all it-directors would translate to deleting the first tuple. Adding a new one is also just one tuple. But again, this does not work on a object level, but for the whole system. I am not sure how easy this could be implemented by the Keto client :thinking: Would this just boil down to forwarding the operation that is made, e.g. the HTTP method for a REST service? The user is then either allowed to view all or no object.

I now finally fully understand the problem you have @christian-roggia :sweat_smile: What I don't understand is do you want to add a new permissions to all objects in the system, or to a subset (might be large)? How would you solve the later case with other authorization systems? Because if it is not a global view permission, then you would always have to somehow specify the list of all objects, right? If it is global, does my above idea work, or why not?

radekg commented 3 years ago

What I've found to be the easiest to reason about the individual person membership was not to explicitly make the individual persons a member. If I know that Bender is a dev-director, I can fetch every role of Bender and check the resource against Bender's roles. I think this is in the principle how the Zanzibar usersets would work.

I mean, this mapping has to exist somewhere. Otherwise how do you know the user has certain mapping assigned. There's no magical method to have a mapping without the info in the fact storage.

This boils down to a cross reference: for any role Bender holds, is any of these roles allowed to execute this action on an object. The higher level tool is definitely able to update this consistently across large number of objects. I also think it's not uncommon to expect this kind of update to happen over millions of objects. You'll have to have the trade off somewhere: either when inserting / updating the permissions or during the query. I'd expect the trade off to be on the create / update step because the query must be as fast as possible.

zepatrik commented 3 years ago

Just a terminology thing, a userset in the paper is the part where you have

obj#rel@other-obj#rel
        ^-----------^ userset

So instead of a single user, you refer to a set of users defined by "everybody who has this permission". This part is already implemented in Keto, but we are missing the "rewrites", which allow you to basically define relation tuples globally with kind of variables. One good example is indirect permission on a file through permission on the parent directory. Currently you have to explicitly say that file1#view@parent_dir1#view but this should be defined globally by saying that you can view every file whose parent you can view. That part is not implemented yet.

Therefore, you don't have to get all the roles of a specific user to check if any of the roles is allowed to do something, but you can instead if you have these tuples:

create#execute@it-director#member
view#execute@it-director#member
delete#execute@it-director#member

view#execute@dev-director#member

it-director#member@Hermes
dev-director#member@Fry
dev-director#member@Bender

check if create#execute@Bender

radekg commented 3 years ago

Thanks, that does explain things better. The higher level tool would definitely make things easier as thinking in tuples over multiple relationships is a bit of a mind bender. But then, this isn’t a Keto (Zanzibar) problem, rather a business domain complexity.

zepatrik commented 3 years ago

The higher level tool would definitely make things easier as thinking in tuples over multiple relationships is a bit of a mind bender.

Haha my brain thinks only in tuples since I worked so much on Keto :joy:

But then, this isn’t a Keto (Zanzibar) problem, rather a business domain complexity.

This is a good point, we have to decide where the line between Keto and your business logic is. It seems that there are some steps that are always required for RBAC, and in that case it does make sense to add a high level abstraction for that. It would just be a shortcut and all the decisions will still be made through the ACLs.

radekg commented 3 years ago

Hey @zepatrik, there is another problem with the queries:

curl --silent 'http://localhost:4466/expand?namespace=default-namespace&subject=Fry&relation=member&max-depth=2' | jq '.'

returns

{
  "type": "union",
  "subject": "default-namespace:#member",
  "children": [
    {
      "type": "leaf",
      "subject": "Bender"
    },
    {
      "type": "leaf",
      "subject": "Fry"
    },
    {
      "type": "leaf",
      "subject": "Hermes"
    },
    {
      "type": "leaf",
      "subject": "default-namespace:it-director#member"
    },
    {
      "type": "leaf",
      "subject": "default-namespace:it-director#member"
    },
    {
      "type": "leaf",
      "subject": "default-namespace:dev-director#member"
    },
    {
      "type": "leaf",
      "subject": "default-namespace:it-director#member"
    }
  ]
}

this is completely not what the logic suggests the result should be.

christian-roggia commented 3 years ago

Abstract implementation

The following is a complete model that we previously implemented through a graph database (dgraph) that fully supported RBAC:

AuthZ - Policies - GraphQL

  1. Every object might have a policy attached to it that defines who is authorized to do what.
  2. A policy is composed of multiple bindings that connect a single role with a group of members, which could be direct users or groups of users.
  3. Every role is globally defined and contains a set of permissions.
  4. Every object might have a parent (and only one) from which it inherits policies.

AuthZ - Hierarchy - GraphQL

This is an example of the inheritance of policies from the most generic to the most granular.

AuthZ - Graph - GraphQL

NOTE: Policies can be global or attached to a specific object.

Hard requirements

What I would expect from an RBAC implementation is the following:

Practical examples

I have 3 users in a Google Cloud project called [PROJECT-X]:

Christian is the project administrator and has the role admin assigned to the project itself, this role is a global role and is therefore inherited by all resources managed by the project.

The role admin grants the following permissions:

The project owns a Pub/Sub Topic resource called [TOPIC-A].

Being [TOPIC-A] a child of [PROJECT-X], it inherits all policies from it, and therefore the administrator Christian is authorized to get, create, and delete the topic itself.

Additionally, the user Matteo has been assigned the role pubsub-editor to the [TOPIC-A].

The role pubsub-editor grants the following permissions:

Matteo and Christian have therefore administrative authority on [TOPIC-A], Matteo has the role assigned directly, while Christian has the role inherited.

Finally, the project owns a Storage Bucket resource called [BUCKET-W].

Being [BUCKET-W] a child of [PROJECT-X], it inherits all policies from it, and therefore the administrator Christian is authorized to get, create, and delete the bucket itself.

Additionally, all users have been granted read-only access to the [BUCKET-W] through the role storage-viewer.

The role storage-viewer grants the following permissions:

Matteo, Christian, and Thomas have all view access on the bucket (and its content), while only Christian has administration access on it since the role admin granted the additional permissions storage.object.create and storage.object.delete.

After an accident, it is decided that members of the pubsub-editor role should no longer have pubsub.topic.delete permission. It is also observed that Matteo is able to manage the Pub/Sub resource but it isn't able to publish any message, it is decided then that the pubsub-editor should be granted the additional pubsub.topic.publish permission.

The role is therefore changed to reflect the new decision:

This change must be reflected globally and retroactively.

github-actions[bot] commented 2 years ago

Hello contributors!

I am marking this issue as stale as it has not received any engagement from the community or maintainers a year. That does not imply that the issue has no merit! If you feel strongly about this issue

Throughout its lifetime, Ory has received over 10.000 issues and PRs. To sustain that growth, we need to prioritize and focus on issues that are important to the community. A good indication of importance, and thus priority, is activity on a topic.

Unfortunately, burnout has become a topic of concern amongst open-source projects.

It can lead to severe personal and health issues as well as opening catastrophic attack vectors.

The motivation for this automation is to help prioritize issues in the backlog and not ignore, reject, or belittle anyone.

If this issue was marked as stale erroneous you can exempt it by adding the backlog label, assigning someone, or setting a milestone for it.

Thank you for your understanding and to anyone who participated in the conversation! And as written above, please do participate in the conversation if this topic is important to you!

Thank you 🙏✌️

timonbimon commented 2 years ago

Is there an anticipated timeline for when native RBAC support will go live? :)

aeneasr commented 2 years ago

https://github.com/ory/keto/pull/877 ;)

zepatrik commented 2 years ago

Truly native support as tracked in this issue is currently not being worked on actively. In general, we can't give out timelines for open source issues and features, all I can say is that we currently don't work on it and won't start tomorrow, especially because #877 is a per-requesit.

cbrendanprice commented 1 year ago

@zepatrik any update on this now that #877 has been merged in?

zepatrik commented 1 year ago

As I said, truly native support as tracked in this issue is currently not being worked on actively. Maybe you want to add your use-case and solution proposal?

nbrugger-tgm commented 1 year ago

Hi, maybe I am wrong but isn't the ory permission language (official doc) the solution to this, since the TS file allows full customization of what is accessible by what?

More specifically what is missing for (H) RBAC to be usable?

zepatrik commented 1 year ago

Yes, you can build RBAC using OPL, and configure it as needed. This issue is rather about having an "official" way to do RBAC. With the OPL, the new option of using dependencies and building authz libraries opened up. Instead of everyone defining their own (probably very similar) flavor of RBAC, we could provide something similar to

import { Namespace } from "@ory/keto-namespace-types"
import { Role } from "@ory/rbac"

class User implements Namespace {...}

class File implements Namespace {...}

class FileRole extends Role {
    // possibility to customize the role behavior
}

But maybe this also is too much complexity and it makes more sense to just have everyone implement their own RBAC? Maybe we could collect some definitions here to see if it makes sense?

Davincible commented 1 year ago

@zepatrik

Maybe we could collect some definitions here to see if it makes sense?

It would be very nice to have a complete (and more complex, real life use case) RBAC-like example in the examples folder of this repo with best practices / common models.

I think it would greatly benefit users coming into Keto like me

github-actions[bot] commented 6 months ago

Hello contributors!

I am marking this issue as stale as it has not received any engagement from the community or maintainers for a year. That does not imply that the issue has no merit! If you feel strongly about this issue

Throughout its lifetime, Ory has received over 10.000 issues and PRs. To sustain that growth, we need to prioritize and focus on issues that are important to the community. A good indication of importance, and thus priority, is activity on a topic.

Unfortunately, burnout has become a topic of concern amongst open-source projects.

It can lead to severe personal and health issues as well as opening catastrophic attack vectors.

The motivation for this automation is to help prioritize issues in the backlog and not ignore, reject, or belittle anyone.

If this issue was marked as stale erroneously you can exempt it by adding the backlog label, assigning someone, or setting a milestone for it.

Thank you for your understanding and to anyone who participated in the conversation! And as written above, please do participate in the conversation if this topic is important to you!

Thank you 🙏✌️