Hacker News new | ask | show | jobs
by samjs 1536 days ago
Love this article. My favourite part is:

> "So the logical thing to do is to implement an authorization service and everybody would be able to use that and keep your precious service boundaries, right? WRONG. Your hell, has just begun!

Drawing the right boundary for authorization is near impossible. If I want to check whether the user is allowed to see who left an emoji reaction on a comment response to an issue inside a repository belonging to an organization -- do I store that entire hierarchy in my authorization service? Or do I leave some of that in the application? I've yet to see a good heuristic for the latter.

Also, thank you to the author for referencing us :) I'm Sam, Cofounder/CTO at Oso where we've been working on authorization through our open source library for the past couple of years. Authorization for microservices has been a recurrent theme over that time, and we've been furiously writing on the topic (e.g. [1], [2], [3]).

We'd be interested in talking to anyone who is currently facing the challenges of authorization for microservices (or really just multiple services). We're building authorization as a service, which you can learn more about here: https://www.osohq.com/oso-cloud. It's currently in private beta, but if you would rather _not_ speak with us first, there's also a free sandbox to try out the product linked from that page.

[1]: https://www.osohq.com/post/why-authorization-is-hard

[2]: https://www.osohq.com/post/microservices-authorization-patte...

[3]: https://www.osohq.com/academy/microservices-authorization

5 comments

What an evocative example! Before reading your links (sorry! They're long. I added them to Pocket), I just don't see how "allowed to leave an emoji reaction to an issue inside a repository belonging to an organization" could be anything but application logic. That is, all of the logic should be in the application (except authentication). Every noun in the sentence is application-specific! Perhaps the organization has repository-specific blocked-users lists, and disallowed-emoji lists, and (in the application) repo owners can modify the disallowed-emoji list (subject to approval from the organization owners, of course!).

It seems like building a service that tries to abstract all that (the emoji is an "attribute" of the "action" to a "object" that has an "owner" that has "rules") is doomed to succumb to the inner platform effect. The only solution I can see is to build a general rules engine, but, in my experience, those tend to just be complicated, hard-to-read ways to implement logic that should just be code.

> I just don't see how "allowed to leave an emoji reaction to an issue inside a repository belonging to an organization" could be anything but application logic.

I think it's absolutely fair to say that authorization logic is a subset of application logic! The question is whether it's possible to separate any of the authorization logic from the application.

There are two reasons you might want to do that:

1. Separation of concerns (true whether monolith or microservices) 2. You have authorization logic shared across multiple services.

(1) is still a hard problem, but you can be a little less rigorous about it. (2) is where it gets really fun.

Continuing the example, that repository-specific blocked-users lists is _probably_ going to be needed across every other service.

I don't think any of that contradicts what you're saying, just clarifying that "in the application" might still mean you want to extract the shared logic into a service.

But to get to your last paragraph: yes it's definitely hard to build a service that's both sufficiently generic to handle all the kinds of authorization use cases people typically need to do.

You'd be surprised though at how many use cases fall into very similar patterns. We have some internal (soon to be external) documentation where we managed to get to about 20 distinct patterns -- from the common one like roles, and groups, to less common ones like impersonation and approval flows. And most of these share the same 2 or 3 distinct primitives (user is in a list of blocked users for a repository, emoji is in the disallowed-emoji list, etc.).

So you can build something less abstract than a general rules engine, but that still saves you the work of building from scratch for the nth time a roles system or a deny list.

> that repository-specific blocked-users lists is _probably_ going to be needed across every other service.

Rather than a service acting as a shared upstream for multiple microservices, you might want to put this kind of generalized authorization into an API Gateway service that sits in front of multiple (internal or external) microservices.

Compare/contrast: blocking malicious origin IP addresses in a firewall appliance. But substitute "IP address" with "API key", and "firewall appliance" with "load balancer."

Then anyone inside your network has unrestricted access. That's why almost all security innovation and adoption is moving away from trusted boundaries. It can still play a role, in conjunction with other layers.
To be clear, I'm not talking about an VPC-edge WAF; I'm talking about a service that sits in front of — and encapsulates — only the specific microservices that require it. An internal ingress controller, in k8s terms.

And also, to be clear, the services would still do domain-object policy-based authorization themselves. The point of such a multi-microservice API gateway is to optimize universal, pre-authentication, static-credential-based denials (e.g. blocking specific API keys, rather than blocking specific users) out of the critical path, such that users can't DoS your backend with 403-generating requests.

> You'd be surprised though at how many use cases fall into very similar patterns.

I look forward to seeing that pattern list. I'm in an adjacent space (an authentication server offering RBAC) and am constantly amazed at the intricacies of many orgs authentication needs.

I suspect that common patterns can take care of 95% of authorization needs, but I imagine there'll need to be an escape hatch for the 5% that are really super business case specific.

Super insightful, thank you!

One concrete example of completely separated auth is AWS. It would be quite the nightmare if each AWS service had its own authorization system. Centralizing that management in IAM makes it... manageable (and only barely at that).

Even then, that only covers authorization for things in AWS that fit the general shape of control-plane API calls. There are endpoints in AWS that don't fit that shape, which do their own thing for auth (see e.g. S3 uploads with signed URLs.)
But that's the difference between authentication and authorization - sure, you've logged in, and we can verify that, but now we need to know if you're permitted to do what you're trying to do. And yes, authorization will then have awareness of some amount of application/business logic.
I really need to write more extensively about this, but I've dealt extensively with authorization as a topic.

The concepts are simple but the implementation can be very difficult.

Authorization and Business Logic are two entirely separate domains. You have to start there. They are orthogonal.

From that follows that requirements involving who you are get implemented in the authorization logic/service/etc, and other requirements get divided up into the appropriate domain logic/services/etc.

If there are requirements around access to emojis then that involves the authorization service.

Sometimes data needs to get duplicated across service boundaries and that's when you need application concepts like sagas to manage this. That's where the implementation starts to get difficult.

I’ve always wondered how authorization should be handled “properly”, as in, what is the end game that is capable of handling the problem at a scale seen at places like AWS? Are the validations and checks still integrated into a (middleware layer of the) service implementing the business logic? If so, how is all this governed, such that correct implementation of all authorization logic can easily be audited?

I would absolutely love to learn more about this, I feel like I’m unable to conceive an appropriate solution to these requirements.

Google has a paper on their system, Zanzibar: https://research.google/pubs/pub48190/

Doubt it will answer all your questions but I found it interesting.

Please let me know if you do write something. I would love to read it!
The best I've ever had it is using an API gateway that destructured a token into headers. Back end services used MTLS. This meant testing Auth was as simple as adding headers. No server needed to be up, no jwt nonsense needed to be mocked. I can't recommend enough keeping this nonsense at the boundaries.
So if I understand it correctly, a service would respond with http headers that describe the claim necessary for the action? Which begs the question, how would that work with side effects.

Or would the acquired claim be communicated towards the service in the request? Which begs the question, how does the service communicate which claim is required.

Not trying to be critical by the way, genuinely curious.

That sounds like authentication not authorization.
The way it's done at my current company is that services do their own minimal authorisation based on their own business logic, if any at all, and the rest is done by a dedicated "policy service" that is called by the request composer (which is the only service the FE calls) to do very advanced checks on what individual users are and aren't allowed to do, with the policy service being configurable through YAML "policy documents". It seems to work very well for the colleagues of mine that work on it.
A former employer (top 5 investment bank) did this as well. There was a central store of identity (well, two: One Windows and one non-Windows). Your application then included it’s own policies (written in Prolog) that could reference identity details and/or deep intrinsic request details. As soon as Prolog hit a condition where it couldn’t unify your request and the policy, you got a no.

There’s a similar OSS implementation (OPA) targeting mainly k8s but allegedly useful generically that uses Datalog.

Nice!

Is the request composer responsible for checking the authorization data? Like what roles/permissions the user has?

Not the OP but we did similar and this front end gateway/"backend for front end" would do the roles checks, yes. Back end services could do course grained checks if needed.
In the enterprise system I design/implement/maintain, I currently have a (!)limited set of roles for my main business logic service.

Each task in that system has an accompanying set of roles that are authorized to execute it, and essentially that authorization happens at the controller level and is "at the edge" of the application instead of in the business logic itself.

Is there a benefit I could gain from Oso? I will read what you've provided.

Honestly, if your authorization needs are coarse enough that you can (a) handle it as middleware at the controller level, and (b) mostly just rely on roles, then you're probably in a good place to keep going with that! It avoids a lot of the complexity of centralization.

Where we normally see people considering Oso is when the model gets more complex, or the data requirements get larger. E.g. when you introduce new features like user groups, projects, or sharing, then the amount of logic + data you need to share between the services grows beyond what's sustainable.

If you're current system is working for you, I'm not going to try and tell you otherwise! If that changes though, let me know ;)