Skip to content(if available)orjump to list(if available)

Gatehouse – a composable, async-friendly authorization policy framework in Rust

jzelinskie

This project looks like a very nice lightweight way to implement policy in a Rust application; I really like the ergonomics of the builder. Despite being very different systems, the core permissions check being the same signature as a call to SpiceDB[0] (e.g. the subject, action, resource, and context) shows the beauty of the authorization problem-domain regardless of the implementation.

I would like to add some color that a policy engine is not all you need to implement authorization for your applications. Without data, there's nothing for a policy engine to execute a policy against and not all data is going to be conveniently in the request context to pass along. I'd like to see more policy engines take stances on how their users should get that data to their applications to improve the DX. Without doing so, you get the OPA[1] ecosystem where there are bunch of implementations filling the gap as an afterthought, which is great, but doesn't give a first-class experience.

[0] https://spicedb.io

[1] https://openpolicyagent.org

gneray

Agreed! But you're glossing over the Zanzibar point of view on this topic, which falls back to dual-writes. That approach has a lot of downsides: "Unfortunately, when making writes to multiple systems, there are no easy answers."[0]

Having spoken with the actual creators of Zanzibar, they lament the massive challenge this design presents and the heroics they undertook over 7+ years at Google to overcome them.

By contrast, we're seeing lots of the best tech companies opt for approaches that let them leave the data in their source database wherever and as much as possible [1]

[0] https://authzed.com/blog/the-dual-write-problem

[1] https://www.osohq.com/post/local-authorization

I'm founder of Oso btw.

jzelinskie

Thanks for the response. In my opinion, oso does the best of job of any policy engine at prescribing how input data to their system is fed (and your linked blog demonstrates it well!).

I do think you might have pivoted the conversation, though. My post was purely about federation strategies and policy engines, but you appear to discussing consistency and Zanzibar, which is only tangentially related. Federation and consistency aren't necessarily coupled. Oso also would require a complex scheme for achieving strict serializability, but it instead chooses to trade-off consistency of the centralized data in favor for the local data.

hardbyte

Yeah a big motivation for us was avoiding the need to keep another system up to date. Gatehouse basically sits at the execute policy layer, and we let the application code decide how to unify the data (or not).

Oso local authorization looks like a fantastic solution.

Thaxll

Is it standard to have 2k+ loc in a lib.rs file? I'm looking at Rust code and it seems that everything goes in there. People usually don't break that down?

klysm

I used to think big files were a problem but I've come to like them more as the years go by. I don't have to wrap my head around some organization scheme which might be half baked - it's all right there.

sunshowers

In general, a problem with big files is encapsulation -- ideally you want private fields to have as little visibility to other code as possible.

klysm

Agreed but this really depends on what tools the language gives you for modules. Some languages have the module structure directly coupled to the file layout like JavaScript, where others are a lot more flexible.

hardbyte

Nah, I think your intuition is correct it should be broken up.

mdaniel

command-f "license" :-(

I do see <https://github.com/thepartly/gatehouse/blob/v0.1.2/Cargo.tom...> but my experience with package manager license declaration is that it is almost always "yeah, yeah, default, whatever" versus an explicit choice. They are also invisible to the GitHub license widget, which places the burden upon the user to go spelunking around in the code to know for sure

hardbyte

I did put licence = "Apache-2.0" in the Cargo.toml but Partly is okay with it being MIT. Update the repo to explicitly add the license text now. Thanks for the call out

rendaw

Does cargo have a default license?

mdaniel

I guess not? I'm just saying a lot of them do, and inertial is a powerful force that causes many developers to not read ques^h^h^h^hanything

  $ cargo new --verbose abcabc
    Creating binary (application) `abcabc` package
  $ cat abcabc/Cargo.toml
  [package]
  name = "abcabc"
  version = "0.1.0"
  edition = "2024"

  [dependencies]

codetrotter

What is the advantage of having the policy checking be an async function? From a brief look (framework itself and one of the examples provided in the examples directory) it seems that you’d typically be doing all I/O operations up front before invoking the policy checker, and that doing the policy check does not itself involve any I/O.

I only looked over the code quickly so I’m probably overlooking something.

It would be neat to get the motivation for each of the functions that are async being async instead of just “normal” functions.

vlovich123

The examples just don't show any I/O but you could easily imagine that the actual policy check is done by a different service which lives across an I/O boundary or involves DB lookups to validate if the action on the given resource by the subject within the given context is allowed.

hinkley

A lot of policies are also about accessing IO in the first place. So I feel like this tracks.

codetrotter

Yeah it makes more sense now.

First I was looking at the Axum integration example https://github.com/thepartly/gatehouse/blob/a3b46bcb37353cb5... where they do the simulated I/O first and then do a policy check that consists only of business logic without any I/O as far as I could tell

https://github.com/thepartly/gatehouse/blob/a3b46bcb37353cb5...

Because there from what I could see, all decisions made by the policies are based on properties read from the user, resource and action objects that were already retrieved in full by the application before it did the call to evaluate access.

So from that and looking over the framework itself I was wondering why evaluating access was an async function, when all of the data I saw it use was retrieved before doing the actual access evaluation itself.

But after reading your comment I then had a look at some of the other examples, and found for example the ReBAC policy example https://github.com/thepartly/gatehouse/blob/a3b46bcb37353cb5... which does seem to be written like it demonstrates I/O such as DB lookups during the access evaluation itself.

It kind of seems to me now then, that the sort of usage of the framework they demonstrate in the Axum integration would typically not do I/O during access evaluation, even for a production grade app. Whereas the kind of usage they demonstrate in the ReBAC example would. And that the reason for involving async is to facilitate the sort of usage they do in the ReBAC example and that it is sort of incidental then that even when you use it in a way that would work for production without I/O during access evaluation, like in the Axum integration example, you still end up with the access evaluation being an async function. Because they are doing a framework that also has to support the ReBAC example kind of usage they have the trait for access evaluation async.

And in turn this probably makes it so that you could start using the framework with the sort of access evaluation that is not doing I/O itself (like in the Axum integration), and then you still have flexibility for later to drop in one that does (like in the ReBAC example) without having to change too much in the parts of your code where you were calling the access evaluation from the beginning.

I could see some value in that. At the same time, assuming my above understanding is correct, it also does seem like in some cases one might end up pulling in a framework that is more flexible than one really needs, if one starts out with using it in the way like in the Axum integration example and then later finds out that one ended up not needing much more beyond what one could do with that.

And because I myself don't yet have advanced needs like in the ReBAC example, I'm not sure if a framework like this would really benefit me. Or if I'd be better off finding a more basic framework for doing authorization in my own server programs.

Of course, I could be misunderstanding the whole thing still :P

hardbyte

You're spot on - we wanted to support some async graph traversal calls similar to the ReBAC example and therefore made the evaluate_access call async. I also wanted to support short circuiting such that somewhat expensive IO calls might be skipped by returning early from a policy that didn't need to make that call.

mparis

Very cool project. I’ve used the cedar crate for similar use cases in the past but it’s always bugged me that it requires writing the policies in yet another language.

Will definitely check this out.

jzelinskie

A separate policy language is explicitly useful for those that want to be able to reuse policies in programs written in different languages. It's a part of the best practice (for larger orgs/products) for decoupling authorization logic and data from your application codebases.

When you're just hacking something together, you're totally right, it might as well be Rust!

mparis

That’s fair. Another pro is the flexibility that comes from being able to store policies in a database and manage them as data instead of code. E.G. roll your own IAM.

A good problem to solve when you need to, but for many of my projects, which admittedly don’t grow into big organizations, I find myself valuing the simplicity of the reduced toolkit.

hardbyte

Thanks! I'm a big fan of Cedar and DSLs such as CEL and gorules. Hopefully there is a place for a Rust solution as well.

esafak

What's the persistence layer?

klysm

I love that this _doesn't_ have a persistence layer. Authorization _should_ be just a library

esafak

You need to store the policy somewhere.

klysm

Okay but that’s a separate concern

hardbyte

BYO persistence

hardbyte

async-friendly Rust native library with decision traceability.