Comments (13)
Implemented by #336
from spicedb.
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.
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.
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 blankobject_type
andobject_id
fields, andPUBLIC
as theoptional_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 onSubjectReference
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 theobject_id
field ofSubjectReference
- Only applies to object classes of the specified type; if
user:*
is given, would not apply togroup:*
, 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.
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.
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 aCheck
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 fortell me all the repositories this user has read access to in the global context
). The same problem would affectExpand
operation.
from spicedb.
- 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 aCheck
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 fortell me all the repositories this user has read access to in the global context
). The same problem would affectExpand
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.
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.
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 workspicedb
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.
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.
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.
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
- 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 - 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"). - 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'" ExpandPermission
will return the wildcard relationship, and will not expand beyond itLookupResources
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"- 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.
@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)
- Proposal: Health check endpoint for HTTP API HOT 5
- Return Evaluated Caveat Context
- Add ability to toggle certain schema warnings off
- Evaluate replacing ristretto with theine-go
- support normalization of caveat context data
- --datastore-bootstrap-overwrite is additive, not overwriting previous namespaces HOT 5
- v1.34.0 Segfault on ReadRelationships before WriteSchema when using memstore HOT 9
- Unnecessary COALESCE Used
- Maybe a bug or not! Spicedb connect to Pgbouncer report error "prepared statement name is already in use (SQLSTATE 08P01)" HOT 2
- Schema Change Events via Watch API
- wildcard support HOT 4
- Would it be possible to use SpiceDB with sqlite/libsql? HOT 1
- failed to create datastore: failed to load bootstrap files: could not lookup caveat
- SpiceDB version middleware not enabled in `serve-testing` mode
- GOMAXPROCS, GOMEMLIMIT logs emitted on all CLI commands HOT 2
- It would be nice if by specifying some relation we could use it as indirect inverse relation in schema as well HOT 1
- Getting a duplicated transaction exists when trying to do a write request HOT 2
- Add external support for streaming relationships into and out of SpiceDB
- Max connections open flag does not work HOT 2
- Fix up flag descriptions and organize into flagsets HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google โค๏ธ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from spicedb.