I tend to build the "ownership" model whenever I can. It works extremely well and has a few simple rules:
1. a user can own an entity/row/unit/whatever. They have full control over this unit.
2. a user can share ownership with another user/role.
3. a user can share various "rights" over any units they own -- CRUD, for example -- for any user/role.
4. a user can only interact with any unit they have a right to.
This can be implemented through a simple db table (or inline in the data itself) and doesn't depend on much. Once you build the middleware, you don't even need to think about the authorization layer.
Sandstorm.org and WASI are both doing interesting things in this space to bring that model to running programs, so eg, the right to access the internet is a capability the OS has which can be given to programs who may then give it to subprocesses they run (or any lesser permission, maybe just the ability to access a single URL)
Its really clean and works great in practice from what I can tell
Cloudflare workers use this in an interesting way as well. The capability is basically a function or an object with data and functions is returned from a call to a remote service. The functions are intercepted with proxy to call other services instead of local code.
The callee basically decides what kind of capabilities it provides to the caller with these functions, anything you're given, you have the right to call without any further auth preconditions. Those capabilities can then be delegated by returning them, wrapped or not, to other callers.
Cloudflare workers are lead by the creator of sandstorm, and use CapnProto internally which has a really neat capabilities based RPC mechanism as well.
Great to know! I'm not deep in the space but was reading about their recent impl update and it seems really well done and much of the ideas are quite thought provoking.
I'm not sure I'm sold on nano services, but if their scheduling system ends up being good enough, a lot of the problems behind treating local and remote calls homogeneously could go away.
I'm not either tbh, haven't used cloudflare workers myself, though I like their style (ultra low weight, run at the edge) a lot more than AWS's where you have to worry about cold starts. But for anything at the scale I'm doing it, one box running everything itself is enough.
Mostly just excited about capability security :) & what it can hopefully do to make doing things the right way (least privilege) as painless as possible - especially across program or network boundaries.
Yeah, as I was reading the OSO piece, it was obvious that a great many of the problems they are solving exist because of RBAC (Role-Based Access Control).
With RBAC, changes to access trigger changes to the principal. For example, when a new employee is hired, there is often a complex and time-consuming administrative process of getting their roles and permissions set up for their position, and when they change positions, teams, or leave the company, access control changes must be propagated based on these events.
With something like Attribute-Based Access Control (ABAC), the authorization system controls access to objects by evaluating rules against the attributes of both subject (the entity requesting access) and object (the entity to be accessed).
This can adapt dynamically determine access based on situational aspects: i.e. in an emergency situation, a subject may be granted access when it would be denied under normal conditions. If you've ever been in a situation when there's a problem in production and the only person who can fix it is unavailable, or can't get access, ABAC can be programmed to allow, temporarily, a backup access path. See e.g. https://csrc.nist.gov/Projects/Attribute-Based-Access-Contro...
I'm probably too close to it, so I'm not following: "a great many of the problems they are solving exist because of RBAC"
Oso supports authorization using any combination of RBAC/ReBAC/ABAC you want.
If anything, I would say that sticking with RBAC is the "easy way" to do it, but you push the complexity of managing it onto your end users (the ones who need to administer it). Whereas building authorization that uses attributes like you describe requires more implementation work, but can make the experience easier for users.
This sounds really elegant, I love it. Have you seen this deployed in a service-oriented architecture or primarily integrated as part of a single app/db?
Both. Usually the service has a table of "shares" and the owner(s) attached to the actual row. Thus determining if a user has a right to do something looks like this:
select 1 from kites k
join shares s on (s.model = 'kites' and :operation in s.rights)
where :user in k.owner or s.user = :user
That's a really clean implementation. And the shares are used to resolve authorization here [1], right?
Two things that we're solving for at Oso is: making it easier for multiple teams to collaborate on permissions (which we solve by having a declarative language), and solving the list filtering problem (as talked about in the post).
If you don't need either of those two things and are happy with a DIY approach, what you've shared would work great IMO. If you packaged that up as a standalone solution, I could see a lot of people getting value from it!
There are not enough people sharing authz implementations out there, a blog post on this shares approach would be super cool.
> And the shares are used to resolve authorization here [1], right?
That's correct!
> making it easier for multiple teams to collaborate on permissions (which we solve by having a declarative language), and solving the list filtering problem (as talked about in the post).
Those are pretty hard problems, so it's really cool to see someone solving it in a reusable way! For me, authz is always a chore ... making it something easy to specify in a way that "just works" is worth quite a bit in my mind!
> If you packaged that up as a standalone solution, I could see a lot of people getting value from it!
I don't really have much desire to get into maintaining an auth library; there's just not enough time in the day!
> a blog post on this shares approach would be super cool.
It's pending publish, actually! I've got a devlog (more like a book at this point) for something I've been working on for years now, but no posts are going to be published until I hit a milestone. I'm almost there... not much further.
> I don't really have much desire to get into maintaining an auth library; there's just not enough time in the day!
Haha, well in some ways I'm glad to hear that. That's why we exist :)
> It's pending publish, actually! I've got a devlog (more like a book at this point) for something I've been working on for years now, but no posts are going to be published until I hit a milestone. I'm almost there... not much further.
Send it over if you want another pair of eyes, and lmk when publishing so we can share with our community too. I'm sam [at] osohq.com
Judging by the name of supported operations it looks like this is built around a type of CQRS pattern. Am I reading this right? Is it possible grant granular operations like a specific type of read or write for an entity?
Indeed. It is CQRS with some ES in there (for sagas/workflows).
Right now, granularity is pretty granular but it could be more granular. For example, there is a concept of serialization scopes that I’m barely using. But it would be possible to (and fairly trivial) to use this to prevent serialization of the entity snapshot for certain properties. This would give the illusion (especially in the graphql projection) that an entity was “missing” fields. From a consumer aspect, it might mean that you can’t call certain methods. Already, the granularity may allow you to call a method and not be able to see its result, for example. It’s pretty neat … but this is also a reminder of sooo much left to document.
1. a user can own an entity/row/unit/whatever. They have full control over this unit.
2. a user can share ownership with another user/role.
3. a user can share various "rights" over any units they own -- CRUD, for example -- for any user/role.
4. a user can only interact with any unit they have a right to.
This can be implemented through a simple db table (or inline in the data itself) and doesn't depend on much. Once you build the middleware, you don't even need to think about the authorization layer.