Giter VIP home page Giter VIP logo

Comments (13)

josephschorr avatar josephschorr commented on August 17, 2024 4

Implemented by #336

from spicedb.

bryanaknight avatar bryanaknight commented on August 17, 2024 3

I ran into a use-case (for GitHub) that is related to this.

Scenario: We have specially privileged users (e.g. staff) that have permissions for lots of different objects in our schema. These users and permissions are not tied to any sort of high-level container like an organization.

I asked about this not wanting to have to embed some sort of a "staff" relation into every single object where these users have permission, and the recommendation was

generally, the recommendation in that case is to have some sort of root type, like system or platform
connect your items to that, and then place the staff user permission on the root object
so say you had resource -> organization
you could add resource -> organization -> platform
it means attaching every org to the platform, but that should be fairly minimal vs attaching to every resource
it also means you can tenant out these kinds of "global" permissions later, if you decide you want a "partially 'global'" staff member

This is working for us but as it states it still means we have a lot of relationships that we need to generate between the organization and any other resource owner(e.g. uesr, business) and the platform. The schema I came up with looks something like:

definition platform {
  relation administrator: user
  permission admin = administrator
}

definition organization {
  relation platform: platform
  permission platform_admin = platform->admin
}

definition repository {
  relation owner: user | organization

  permission delete = owner + owner->platform_admin
}

Not that these platform admins don't have every single permission available on the platform, but they do have a large set of permissions spanning lots of different resource types.

This sparked the idea of utilizing the public type to solve this more efficiently. The current proposal for public is to allow something like resource:someresource#viewer@user:*, which states that any user can view a specific resource. We could theoretically do the inverse of that for this use case resource:*#viewer@user:staffgal, which states that any resource can be viewed (or some other permission) by a specific user.

from spicedb.

josephschorr avatar josephschorr commented on August 17, 2024 1

Yeah, the original idea was a lint check, but then how do you know that the developer "approved" the inclusion... hence the idea of introducing a keyword and making it an explicit type check. I think public has the facet that it is demonstrably "worse": it would be easy to accidentally include, and if indeed included accidentally... game over.

Totally, I think it's a step in the right direction and is going to help! Definitely better than not having such mechanism in place ๐Ÿ’ฏ ๐Ÿ‘๐Ÿป From your comment, I get the feeling that a gap in the tooling leads to introduce language-level constructs to address the root problem. Perhaps a concern here is the tension between the playground being that, just a playground, versus a fully fledged control plane for the lifecycle of the schema. I could imagine a fully managed experience, similar to what we see for example in planetscale.com with their schema migration automation. There is also an obvious tension between what goes into open-source codebase, and what goes into the SaaS product ๐Ÿ˜‰

Yeah, although we don't want to limit people to using just a UI, as many prefer CI or other automated workflows.

All of these changes should be tracked via the validation and verification tooling (which we have built into the Playground), but developers often times will simply hit the "update the validation code until green" button, so I wanted to be extra careful.

@vroldanbet Have you taken a look at the Playground validation tooling and, if so, thoughts on how we might extend those to make it more useful to developers in general?

I've been tinkering with it quite a bit in fact, but perhaps there is a different issue to gather that feedback instead of bloating this issue with potentially orthogonal concerns? ๐Ÿค” Happy to report here otherwise!

Sure. I was more interested in raising the idea :)

from spicedb.

josephschorr avatar josephschorr commented on August 17, 2024

Possible Solutions

Provide guidance on how customers can implement themselves

Each schemas defines its own definition of a specialized subject type to represent this case:

Schema

definition user {}
definition allusers {}


definition video {
  relation viewer: user | allusers
}

Relationships

video:X#viewer@user:userA
video:X#viewer@allusers:all

Code

if authzed.check(video(โ€˜xโ€™).viewer, user) or authzed.check(video(โ€˜xโ€™).viewer, all_users):
  ...

Pros:

  • No code changes
  • Each schema could define different โ€œclassesโ€ of โ€œallโ€ users or โ€œpublicโ€
  • Fairly simple to understand
  • Somewhat type safe since it must be checked manually for the public case

Cons:

  • Provides no additional value over existing support
  • Requires two check/expand/lookup calls
  • Not intuitive to have a โ€œsingletonโ€ definition class

New specialized value for subject

Allow for a system-wide defined specialized value for subjects called PUBLIC. A relationship specifies PUBLIC as a subject, which means any and all Checks will succeed for that relation.

Schema

definition user {}


definition video {
  relation viewer: user | PUBLIC
}

Relationships

video:X#viewer@user:userA
video:X#viewer@PUBLIC

Code

if authzed.check(video(โ€˜xโ€™).viewer, user):
  ...

Pros:

  • Easy to use and understand
  • Somewhat type safe

Cons:

  • Would require changing the SubjectReference to allow for blank object_type and object_id fields, and PUBLIC as the optional_relation
  • Type safety is only on the relation itself; any permission referencing the relation would not be marked as publicable, meaning significant risk of accidentally allowing public access to a sensitive non-readonly permission
  • Does not allow for different classes of โ€œpublicโ€: check would match everything

Specialized relation within a subject class

Allow for a system-wide defined specialized value for the relations of subjects called PUBLIC. A relationship specifies PUBLIC as a subjectโ€™s relation, which means any and all Checks will succeed for that relation.

Schema

definition user {}


definition video {
  relation viewer: user | user#PUBLIC
}

Relationships

video:X#viewer@user:userA
video:X#viewer@user:anything#PUBLIC

Code

if authzed.check(video(โ€˜xโ€™).viewer, user):
  ...

Pros:

  • Only need to allow blank for the object_id field on SubjectReference

Cons:

  • All the cons of the solution above

Allow star subject IDs

Allow for a specialized star Object ID in subjects, which indicates that the relationship applies to any objects of that type.

Schema

definition user {}


definition video {
  relation viewer: user
}

Relationships

video:X#viewer@user:userA
video:X#viewer@user:* // indicates any user as subject is valid

Code

if authzed.check(video(โ€˜xโ€™).viewer, user):
  ...

Pros:

  • Every relationship still has all fields
  • Wildcards are understood by most developers
  • Semantically reads nicely
  • No changes to RelationTuple except allowing for * in the object_id field of SubjectReference
  • Only applies to object classes of the specified type; if user:* is given, would not apply to group:*, or vice versa

Cons:

  • Not as readable/discoverable initially
  • No type safety on whether the star is allowed
  • It might be expected that other kinds of prefixing works (such as someSubjectIdPrefix*)
    • NOTE: This may be a useful feature down the road for granting access to classes of subjects without putting them into a group or team or whatnot, but there will likely be performance issues
  • If you wanted to be able to support truly-public, e.g. it matches ANY subject type that is sent in, you would have to duplicate/enumerate the types for all subject types that you might call check with.

Type safe allow star subject IDs

Same as the above proposal for allowed star subjects, but with added enforced type safety via additional keywords:

Schema

definition user {}


definition video {
  relation viewer: user | user:* // Allows the * user


  publicable permission view = viewer // Must be specified โ€œpublicableโ€ since viewer allows the user:*
}

Relationships

video:X#viewer@user:userA
video:X#viewer@user:* // indicates any user as subject is valid

Code

if authzed.check(video(โ€˜xโ€™).view, user):
  ...

Pros:

  • All the pros of the previous proposal, but with a slight loss in readability
  • Type safety and no risk of having a * on a permission that is unexpected
  • Can skip the wildcard check on a relation unless it specifies this type - more efficient

Cons:

  • All the cons of the previous proposal, but with type safety added in
  • In order to calculate the safety of the permission, weโ€™d need to do a full type graph union of all reachable types, which will be a bit slower on SchemaWrite

RECOMMENDATION: Type safe allow star subject IDs

from spicedb.

vroldanbet avatar vroldanbet commented on August 17, 2024

Howdy! ๐Ÿ‘‹๐Ÿป Adding feedback here after some conversation in the official Discord channel. Copying my comment here for discoverability:

alternative implementation without language support

I the "Provide guidance on how customers can implement it themselves" section a way to implement this without language support was described. I found that forcing callsites to perform 2 checks is something we've always tried to steer our engineers away from: it not only leaks implementation details (engineers need to understand public visibility has its own treatment) but could be easily overlooked by devs and cause logic drift across callsites..

I experimented with an alternative that, while it also comes with its own set of tradeoffs, addresses the "multiple check" problem. Kudos to @ecordell for kindly demonstrating this in a playground: https://play.authzed.com/s/DybMLyaLcX4t/schema

Schema

definition user {}
definition allusers {
  relation member: user
}

definition video {
  relation viewer: user
  relation public: allusers

  permission view: user | public -> member
}

Relationships

allusers:0#member@user:userA
video:X#public@allusers:0

Code

if authzed.check(video(โ€˜xโ€™).view, userA):
  // business logic

Note

I think the same Pros / Cons are somewhat applicable here, sans the "duplicated request" call.
I definitely feel a bit of a "con" having to write relation for each newly registered user. For a user base as large as ours, that's a lot of relationships to store.

from spicedb.

vroldanbet avatar vroldanbet commented on August 17, 2024

From our systems requirement perspective, Type safe allow star subject IDs would seem to meet our needs. Some feedback:

  • It isn't immediately obvious why publicable is needed, as it would appear as redundant. Joey clarified in Discord it was added as a safeguard to prevent human mistakes as the customers schema evolves.
  • It's not clear if it solve "anonymous" access, unless it supports user:* as subject of a Check operation? ๐Ÿค”. E.g. you don't need to be authenticated to see a public repository in github. It can be solved modelling the anonymous user as a special singleton user
  • When it comes to ACL Filtering, it may lead to the "list all public resources in the system" problem. Think running lookup RPC on a system with billions of public resources. I can't think of a scenario where a customer wants to list all public resources in an application. This means that it will need special treatment for ACL filtering. The equivalent of ACL filtering in our authorization system solves it by defining a boundary where to execute the ACL filtering. For example we do ACL filtering of public resources in the realm of an organization (e.g. tell me all the repositories this user has read access to in the context of this organization), but we simply ignore public repositories for tell me all the repositories this user has read access to in the global context). The same problem would affect Expand operation.

from spicedb.

josephschorr avatar josephschorr commented on August 17, 2024
  • It isn't immediately obvious why publicable is needed, as it would appear as redundant. Joey clarified in Discord it was added as a safeguard to prevent human mistakes as the customers schema evolves.

Yeah, this would just be a means to ensure any permission that can be public is explicitly marked as such. It would allow schema developers to have confidence that, for example, someone didn't accidentally add user:* into a relation that feeds into a permission like write or admin or do_all_the_things. I figured that since that was a non-zero risk, might as well make it explicit that its allowable at the permission level.

  • It's not clear if it solve "anonymous" access, unless it supports user:* as subject of a Check operation? ๐Ÿค”. E.g. you don't need to be authenticated to see a public repository in github. It can be solved modelling the anonymous user as a special singleton user

Since it would apply to any user, the recommendation would be to either check directly for public check(resource:whatever, view, user:*) or having a singleton name for a user (like ANONYMOUS); that object would not need to actually be in SpiceDB, as user:* would literally match any user object ID given.

  • When it comes to ACL Filtering, it may lead to the "list all public resources in the system" problem. Think running lookup RPC on a system with billions of public resources. I can't think of a scenario where a customer wants to list all public resources in an application. This means that it will need special treatment for ACL filtering. The equivalent of ACL filtering in our authorization system solves it by defining a boundary where to execute the ACL filtering. For example we do ACL filtering of public resources in the realm of an organization (e.g. tell me all the repositories this user has read access to in the context of this organization), but we simply ignore public repositories for tell me all the repositories this user has read access to in the global context). The same problem would affect Expand operation.

Yes, this would be an interesting problem. We'd likely have LookupResources ignore the public entirely, unless explicitly requested. Then, the expectation would be that the caller can call it for the user or the context on which they are interesting (in your case, the parent organization). We likely need to develop out this question further.

from spicedb.

vroldanbet avatar vroldanbet commented on August 17, 2024

Yeah, this would just be a means to ensure any permission that can be public is explicitly marked as such. It would allow schema developers to have confidence that, for example, someone didn't accidentally add user:* into a relation that feeds into a permission like write or admin or do_all_the_things. I figured that since that was a non-zero risk, might as well make it explicit that its allowable at the permission level.

I think that makes a ton of sense, specially since it wouldn't be immediately obvious all downstream permissions that are making use of it, directly or indirectly. It can be inferred from the AST and be flagged at development time. And as one permission gets marked as publicable, if may be used by another permission, consequently propagating another "compile" time error.

With that said, I would argue the problem is not exclusive to "public visibility". A heavily DRY'ed schema can also lead to similar escalation of privileges as permissions and relations are reused, and special safeguard keyword wouldn't be a sustainable solution here. Public visibility is perhaps just the worst of all possible outcomes if a mistake is made.

I don't necessarily have an alternative proposal here, just wanted to flag what appeared to me a solution to a subset of the problem. I could imagine a world where the developer tools would automatically identify all relations and permissions transitively affected by a change and require the developer to explicitly approve them before roll out, and even so that does not impede human mistakes.

Yes, this would be an interesting problem. We'd likely have LookupResources ignore the public entirely, unless explicitly requested. Then, the expectation would be that the caller can call it for the user or the context on which they are interesting (in your case, the parent organization). We likely need to develop out this question further.

Yeah, agreed. Listing public resources being "opted-out" seems like a sensible default. Introducing a means to "define a boundary/fencing to lookup results not only could help solving this problem, but is also a neat feature on its own, as it relieves the client from having to "filter out" elements they weren't interested in, reduces the amount of data that needs to be sent over, and potentially the amount of work spicedb has to do to compute the result.

from spicedb.

josephschorr avatar josephschorr commented on August 17, 2024

Yeah, this would just be a means to ensure any permission that can be public is explicitly marked as such. It would allow schema developers to have confidence that, for example, someone didn't accidentally add user:* into a relation that feeds into a permission like write or admin or do_all_the_things. I figured that since that was a non-zero risk, might as well make it explicit that its allowable at the permission level.

I think that makes a ton of sense, specially since it wouldn't be immediately obvious all downstream permissions that are making use of it, directly or indirectly. It can be inferred from the AST and be flagged at development time. And as one permission gets marked as publicable, if may be used by another permission, consequently propagating another "compile" time error.

With that said, I would argue the problem is not exclusive to "public visibility". A heavily DRY'ed schema can also lead to similar escalation of privileges as permissions and relations are reused, and special safeguard keyword wouldn't be a sustainable solution here. Public visibility is perhaps just the worst of all possible outcomes if a mistake is made.

I don't necessarily have an alternative proposal here, just wanted to flag what appeared to me a solution to a subset of the problem. I could imagine a world where the developer tools would automatically identify all relations and permissions transitively affected by a change and require the developer to explicitly approve them before roll out, and even so that does not impede human mistakes.

Yeah, the original idea was a lint check, but then how do you know that the developer "approved" the inclusion... hence the idea of introducing a keyword and making it an explicit type check. I think public has the facet that it is demonstrably "worse": it would be easy to accidentally include, and if indeed included accidentally... game over.

All of these changes should be tracked via the validation and verification tooling (which we have built into the Playground), but developers often times will simply hit the "update the validation code until green" button, so I wanted to be extra careful.

@vroldanbet Have you taken a look at the Playground validation tooling and, if so, thoughts on how we might extend those to make it more useful to developers in general?

Yes, this would be an interesting problem. We'd likely have LookupResources ignore the public entirely, unless explicitly requested. Then, the expectation would be that the caller can call it for the user or the context on which they are interesting (in your case, the parent organization). We likely need to develop out this question further.

Yeah, agreed. Listing public resources being "opted-out" seems like a sensible default. Introducing a means to "define a boundary/fencing to lookup results not only could help solving this problem, but is also a neat feature on its own, as it relieves the client from having to "filter out" elements they weren't interested in, reduces the amount of data that needs to be sent over, and potentially the amount of work spicedb has to do to compute the result.

Agreed. I really like the filtered boundary for LookupResources, with the caveat that it would make caching harder (but then, so does public, which would likely not be included).

from spicedb.

vroldanbet avatar vroldanbet commented on August 17, 2024

Yeah, the original idea was a lint check, but then how do you know that the developer "approved" the inclusion... hence the idea of introducing a keyword and making it an explicit type check. I think public has the facet that it is demonstrably "worse": it would be easy to accidentally include, and if indeed included accidentally... game over.

Totally, I think it's a step in the right direction and is going to help! Definitely better than not having such mechanism in place ๐Ÿ’ฏ ๐Ÿ‘๐Ÿป From your comment, I get the feeling that a gap in the tooling leads to introduce language-level constructs to address the root problem. Perhaps a concern here is the tension between the playground being that, just a playground, versus a fully fledged control plane for the lifecycle of the schema. I could imagine a fully managed experience, similar to what we see for example in planetscale.com with their schema migration automation. There is also an obvious tension between what goes into open-source codebase, and what goes into the SaaS product ๐Ÿ˜‰

All of these changes should be tracked via the validation and verification tooling (which we have built into the Playground), but developers often times will simply hit the "update the validation code until green" button, so I wanted to be extra careful.

@vroldanbet Have you taken a look at the Playground validation tooling and, if so, thoughts on how we might extend those to make it more useful to developers in general?

I've been tinkering with it quite a bit in fact, but perhaps there is a different issue to gather that feedback instead of bloating this issue with potentially orthogonal concerns? ๐Ÿค” Happy to report here otherwise!

from spicedb.

jakedt avatar jakedt commented on August 17, 2024

Just had another thought: Subject ids are fairly wide open in terms of identifier constraints due to the need for the user to be able to use their own source of truth identifiers as subject ids. I'm not sure we can rule out someone someday needing to use * in an object id. Another option to using * would be to allow the schema author to specify in the schema a special value that should be interpreted as public. For some schemas that could be PUBLIC, for some it could be *, etc.

If we do allow for writing relationships with * as the subject id, we should definitely allow for check queries with that value as well for the fully public case.

from spicedb.

josephschorr avatar josephschorr commented on August 17, 2024

After additional discussion, here is the updated proposal:

Schema

definition user {}

definition someothersubjecttype {}

definition video {
  relation viewer: user | user:* | someothersubjecttype:* // Allows public for all users or all someothersubjecttype's
  permission view = viewer
}

Relationships

video:X#viewer@user:userA
video:X#viewer@user:* // indicates that any user can view
video:X#viewer@someothersubjecttype:* // indicates that any someothersubjecttype can view

Code

// Check if a particular user can view the video. Will return PERMISSION_ALLOWED for all users IF there exists the
// relationship `video:x#viewer@user:*`
if authzed.check(video(โ€˜xโ€™).view, user('someuser')):
  ...

// Check if the video is public (e.g. accessible to any user)
if authzed.check(video(โ€˜xโ€™).view, user('*')):
  ...

Details

  1. Wildcard support is added into the schema on relations as an additional type (e.g. user:*), indicating that a relation can be marked as public for that particular kind of subject
  2. If a relation has a wildcard subject for a particular type of subject, and a CheckPermission is issued for a subject of that type, it will succeed, regardless of the subject's object ID (e.g. the permission is "public").
  3. A CheckPermission for a subject with the wildcard itself will return true iff the relation contains the public relationship, e.g. "the permission itself is 'public'"
  4. ExpandPermission will return the wildcard relationship, and will not expand beyond it
  5. LookupResources will not return resources accessible to the specified subject if they are only accessible via a wildcard for that subject, e.g. "public-only resources will not be returned for a subject"
  6. Instead of a publicable keyword, a relation which contains a wildcard subject will not be allowed on the right hand side of another relation, to prevent a wildcard/public from "leaking" across relations. This restriction may be removed or relaxed in the future.

@vroldanbet Let us know if you find that the above will meet your needs

from spicedb.

vroldanbet avatar vroldanbet commented on August 17, 2024

@josephschorr love it! โœจ It addresses most of our concerns! Love the new way to prevent accidentally turning something into public, by making it explicit in the relation. I also like the behaviour for expand and lookup, and particularly for the latter I think it's reasonable compromise. Client applications wanting to return public resources could, for example, perform additional RPC to determine if a set of resources are public, or perhaps even perform a Read call like "all resources of a given type with viewer: user:*.

One potential consequence of the semantics of public resources in lookup I could think of is that, for example, showing an ordered list of resources in an index-like web page would be more difficult, given that resources need to be streamed from 2 different RPC operations. In order to order the results, the client would need to load the data set in memory, whereas with 1 single RPC, the backend can stream based on a given ordering criteria.

from spicedb.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.