253 karma · joined July 6, 2017
Thanks for the correction. I’ll get on updating that.
The "public" example was meant to be a simple example of attribute-based access control but you could replace that with other similar ABAC examples for why you might need to bring in an additional policy engine.
It builds up a bit more gently and introduces a lot more concepts than I could fit into one (already long) blog post.
I've got to say, the folks at Intercom made it particularly fun. They were sending us traces and graphs from their internal systems when we trying to figure out some issues with them (e.g. we ran into this datadog context problem: https://github.com/DataDog/dd-trace-rb/issues/1389)
We're heavily focusing on adoption of the open source product right now for helping developers with application authorization. We do currently charge for things like support + consulting, but in the future we're planning on providing additional functionality through a service that would be more on the operational/security side.
To give a simple example of an attribute-based control that is tough with the service model: if you want to express "anybody can read a document if it's public", then you need to push that "public" field into the service. Every attribute that you want to use for authorization becomes something that you need to either move or synchronise into the service. Or you leave that logic in the application.
When we were building Oso [1], we were optimising for the best thing for developers, and reached the same conclusion as you... (a) It doesn't make sense to rearchitect your app to move all the data to a separate service, and (b) it's way less complex to build it in the application. That's why we're building Oso as an open source library instead. You get to leave your application data in the application, and don't need to worry about adding an external service to the critical path.
Super interesting how many companies are building authorization systems based on Zanzibar suddenly! This is a bit of a shameless plug, but I just wrote a blog post earlier today talking through how to build Zanzibar from scratch in ~150 lines of code: https://news.ycombinator.com/item?id=28076549
Not many people talk about how Zanzibar requires you re-architect your application around authorization when you really don't need to do that at all. Any sufficiently powerful authorization framework can handle the same flexibility. If not more, since most Zanzibar implementations can't handle simple attribute-based controls (e.g. anyone can read a document if its public). Which means you'll end up implementing a bunch of authorization logic in your app anyway.
---
Edit to add: I realise in hindsight I got a bit too absorbed in thinking about the Zanzibar part to say congrats on the launch! It's awesome to see the space heating up, and love to see more focus on the developer experience :)
Seems like a few others have come to the same conclusion :)
We're working on this at Oso (https://osohq.com) - I'm the CTO. Oso is an open source framework for authorization. Policies can reference application directly, so any authorization decisions can be made dynamically based on the data. And your data doesn't need to leave your application.
> Often the issue is the inability to easily push down filters into the database or search index.
We actually support pushing down filters into the database directly through a feature we call... data filtering. We support Django and SQLAlchemy today (https://docs.osohq.com/python/guides/data_access.html) but will be making this available for non-ORM/non-Python users in the next few months.
The idea is to put all data in one place, and then aggressively optimise for answering queries about that data. For authorization you're pretty much always asking specific reachability questions. That's where the design of the index comes into it.
> Also, I was wondering: Are ALL permissions stored as an Object::relation pair? i.e. do you need to register permissions for all new entities relationships, or do you have some way of storing more dynamic permissions?
In Zanzibar, there is a configuration format for computing relationships dynamically. E.g. "anyone who is an editor of a document is a viewer of a document". I'm not sure if they implemented something like that.
If you're interested, I wrote a guide on relationship-based access control that includes a section on how Zanzibar(-like) systems fit in: https://www.osohq.com/academy/authorization-academy-chapter-...
Depending on your requirements, yes that's kind of what happens if you want to centralise. It can make sense for Google-scale problems where you really do need to handle the complex graph of relationships between all users and resources, and doing that in any one service is non-trivial.
In practice though, a lot of service-oriented architectures can get the same benefits by having a central user management service, and keeping most of the authorization in each service. That central service can provide information like what organizations/teams/roles etc. the user belongs to, and then the individual services can make decisions based on that data.
This is the approach I covered with the hybrid approach. With this you can still implement most complex authorization models.
I'm pretty sure I know who you are in the slack, so I'll follow up with you there about the blog post :)
Over time, we've gotten a lot of questions from developers on authorization basics – like how to model roles, where to add authorization checks, or how to integrate permissions in the UI.
It turns out that there's not much information available on these topics that's technical, concrete and user-friendly. So, we started writing a series of guides on these topics to capture everything we've learnt, heard, and experienced ourselves.
The series is called Authorization Academy (https://www.osohq.com/developers/authorization-academy). The first few chapters cover architecture and modeling roles. Next we'll cover relationship-based access control, enforcement, and integrating permissions data into UIs. We have a lot of topics we'd like to write about, and they take a fair bit of time, but let us know if there's anything in particular you'd like to read about!
The guides cover authorization generically and are not specific to Oso, so they should be useful for anyone who is building authorization into their app or otherwise wants to learn about the area. Of course, if you'd like to learn more about Oso you can visit our site (https://www.osohq.com) or come talk with us in Slack (https://join-slack.osohq.com).
Strong agree. We're trying to strike the right balance with this problem with oso [1] (I'm a cofounder). By letting you separate the authorization logic from your code, and doing a lot of the thinking for you (how to design + implement roles etc), but ultimately the data *stays in the application*. There's such a blurry line between authZ and business logic that it doesn't make sense to fully outsource it.
However, without the header file right now.
We need to think through whether the C ABI is stable. It hasn't really changed in a while, but we haven't thought through the implications yet.
This would be a great option. That's effectively how our build pipeline works anyway - we compile the polar-c-api [1] crate to a dynamic lib and embed in each language. If we made this available through package managers, we could also provide an alternative installation for, e.g. Python that didn't even need to use the precompiled wheel.
> It would be great if `serde` was opt-in through cargo features,
We rely on this for the FFI, since we pass events/messages back and forth as JSON. We _could_ make it opt-in for people using it in Rust projects though. Would that work for your use case?
Running our test suite [2] takes about 9 minutes. 4 of which is running (a) the pure Rust tests, (b) compiling the C library and running Python, Ruby, and Java tests, (c) building the WASM library and running Nodejs tests.
That's why I'm a little surprised by the time. You're right though, I didn't factor in the slowdown for the macos builds.
> Not trying to be overly critical, just trying to give a view! Compiling on other people's machines is expensive, a good way for me to buy into something like this instead of doing it myself (which is how I'm doing it right now!) is to reduce the dependency tree and make building stupid fast/easy.
Thank you! I appreciate the feedback. We did go out of our way to make it as easy as possible to install from whatever language package manager (e.g. pip install oso will take seconds). We weren't anticipating people would want to compile it themselves from scratch, so it's a helpful perspective to get.
[1]: https://github.com/osohq/oso/tree/main/polar-c-api
[2]: https://github.com/osohq/oso/runs/1540314680?check_suite_foc...
Agreed! And I strongly recommend to anyone thinking about doing so to join our slack [1] and come chat with us :) We'll be happy to share our thoughts on this.
All these questions are also tempting me to go and put together a demo for this too...
Thanks for giving it a try! Were you using this in a Rust project, or building it from scratch for some other language?
For the latter, we do provide libraries for Python, Ruby, Java, Nodejs with the Rust core precompiled. It's a tricky CI challenge for us, but shouldn't affect users.
If you're using in a Rust project, then you will indeed need to pull in our dependencies. Though we are pretty lean on dependencies, and I would expect most projects using oso would be using serde anyway.
That being said those times you're seeing seem very long, even for Rust! Is that building with `--release`? Also, the time to compile it the first time will be slow, esp. including downloading the source. But incremental builds are a lot faster. We do a little caching in CI to make this a bit less painful.
What this means practically is either the data is processed through whatever data access layer you have (i.e. SQL, or an ORM). And there's more work we're doing here to make that experience seamless [1].
Or if you do have some large input data and you iterate over it in the policy, then the oso host library (the part in your app) will just iterate through it without sending the entire object back and forth.
> I presume that this is how you’d rather have users build more advanced policy than extending what seems to be a pretty lightweight language
Yep, that's the idea. I answered a similar question here [2]. You can call class + instance methods from Polar, so if there's anything you can't do you can add it that way. We have considered/are considering adding a standard library to provide common pieces out of the box, but it's not a limiting factor for using oso currently.
There are some side benefits though that a standard library would provide - like having a robust implementation of common operations in the Rust core.
[1]: https://docs.osohq.com/getting-started/list-filtering/index....
In GitHub you might be able to merge a PR in a repository because you are the repo owner, or you were invited to it. Or because you're an organization admin, and the repo is in the same organization. (These are all common scenarios in b2b saas apps).
When a user attempts to merge a PR (or hits the API) you need to make that authorization decision based on what the application knows about the user.
So how do you allow the policy to access this information? Your options are basically: (a) you make it possible for the policy engine to independently lookup the data. You are now building a distributed monolith, since any change to the data requires updating both the policy engine and the application. (b) You send the relevant data into the policy decisions. Knowing what data you need to send to the policy is another form of coupling.
OPA is sort of a combination of (a) and (b). There is an API for sending authorization data to it, and you can also send data along with a request.
The problem is that the line between authorization and business logic is _so blurry_. Me being a member of a github org is fundamental application logic. But also crucial to making authorization decisions.
Because of this, traditionally people write this logic as a part of the application code. Normally a bunch of `if` statements. This is hard logic to abstract well in an app, because it's normally a bunch of conditionals and a decision flow. Which it turns out prologs/logic-based languages are great at solving (same conclusion OPA folks reached).
So given all this, the balance we wanted to strike was: decoupling the logic, but not the data. If your app already defines what a User is, what a Repository is, how a User becomes a Member of a Repository, then write your policy over those things, instead of re-implementing all of that logic elsewhere.
That being said, there are cases where the OPA-style decoupling works fine since there isn't the same kind of business logic/authz logic distinction. Or where the decision is being made over the entire input by default (things like checking terraform files, or other infra use cases).
[1] https://docs.osohq.com/more/design-principles.html#separatio...
[2] https://www.osohq.com/post/building-the-github-authorization...
We've done some internal proof of concepts of this, and have discussed it with various folks, and happy to share more of this with anyone interested.
> I’d also be interested to know if you’d compared the performance of oso policy vs Rego on comparable input.
Not yet, but this a great suggestion. I would be a little worried that it would be hard to do it in an unbiased way. The two have different design choices, etc.
And on the policy language. Polar is indeed a prolog variant. You can see pretty early we took the decision to diverge from a more familiar prolog syntax [1] because we wanted to make it a little more accessible.
> The programming language specific code just is some plumbing for accessing the policy agent.
If you want to make policy decisions over your application data you still need to work out a way to move it into the OPA service, or send it as part of the policy request.
You can make oso policies language agnostic if you take a similar approach and have a small number of shim rules that handle the application-specific parts.
oso was designed to be embedded directly in applications as a library. So there's no additional service to run to use it to make authorization decisions in an application.
The other part is that oso policies are written directly over your application data - e.g. the classes/types, and can access attributes and call methods. There's no integration work needed to get application data available to the policy to start making decisions, nothing to keep in sync, and no additional network overhead.
Thanks to OP for posting this. A very pleasant surprise to wake up and find this here :)
I'll be around to answer any questions people have, but you can also find myself and the team in our community slack: https://join-slack.osohq.com/
All the docs are hosted here: https://docs.osohq.com/
And we have written up various articles on using oso and some of the internals on our blog: https://www.osohq.com/company/blog
You're right, and this is something we've discussed a bunch. Our current view is that:
1. Allowing teams to standarise on an _approach_ and a library in the first place is a huge step up from individual ad hoc implementations.
2. If you have shared logic between applications, you can still write language-agnostic policies and share those between them. If there are parts that are specific to an application/language, its easy enough to pull these into separate rules and add shims.
3. We intentionally wanted to keep the language lean to lower the learning curve. But we're considering adding a standard library in the future, powered by the Rust core, to provide common functionality. The nice thing is that you're not limited by how fast we add those.
We're interested in hearing from folks who are working in those environments to hear what would make the most sense for them.