When it comes to application permissions, two results emerge from this situation:
If you are interested in how you can build RBAC from available tools for any service that respects REST, you’re welcome.

What is it for?

In short, this entire article can be fit into the following phrase:
When processing a request in Nginx, we send an access request to the OPA before sending it to the service and receive the authorization result. If access is allowed, the request is sent to the service.

Application

So, let’s consider an example with an application and its placement.
Suppose we have a cluster containing two applications:
The application has a REST API with CRUD operations:
Now let’s form a minimal access matrix:

Authorization

Now let’s figure out how to define the possibility of accessing data.
To make a decision, you will need the following data:
In our case, this data can be interpreted:
The result will be a positive or negative decision.
Based on this decision, you can conclude what the user can perform within the business application.
Now let’s go back to our example with the application and figure out where to get the data for making such a decision.
User. As a user, it is very convenient to use a JWT token as a validated identity snapshot.
Recently, Keycloak and its SSO Redhat implementation have been gaining in popularity, so I’m going to proceed from the Keycloak token structure.
Action. It is very convenient to use the action marker to operate with the classic REST notation and assume that the methods.
GET is to read, POST/PUT is to create and change, and DELETE is to delete.
Data. In the case of a proxy, it is convenient to interpret data as a route. That is, the route that a call takes is our data.

Gateway, Authorization, Application

Now let’s put together all the above puzzles to form a picture.
If we want to perform authorization at the proxy/gateway level according to requests from users, we have all the initial data for checking access rights.
That is, if we assume that Gateway can perform the authorization request, all that remains is to add a new puzzle to our picture – the authorization module.
Thus, our chain turns into the following sequence:
  1. The user has received his identity token, and we assume that it contains all the necessary information about the user. With this token, the user makes a request to the business application and gets to Gateway.
  2. Gateway needs to form a request for access rights. To do this, it parses the request into parts:
    - It takes the token from the header and deserializes it, thus forming data about the user;
    - It separates the HTTP method from the request and says that this is the action performed by the user;
    - It forms data from the request path;
  3. There are three rules in authorization, which say that the reader can read data, the editor can read and change data, and the administrator can do everything;
  4. If access is allowed, the request is sent to the business application.

Implementation

That’s it. The theory is done. To be honest, there is much more theory than implementation. This is what this decision really impresses me with.
I will use OPA as an authorization module – https://www.openpolicyagent.org
And I will take Nginx for Gateway – http://nginx.org
As a side note, OPA is gaining popularity in filtering requests, and there are modules for Envoy – https://github.com/open-policy-agent/opa-envoy-plugin, Traefic – https://doc.traefik.io/traefik-enterprise/v2.4/middlewares/opa/
Nginx
In my case, the main Nginx configuration does not contain any additional manipulations.
JWT
I use Keycloak as my token publisher. However, for clarity of interaction, I’ve added the following methods to Nginx:
Nginx configuration that calls jwt.js methods

API

The
/security/
route is used as an API application
OPA
Description of roles and methods:
Nginx + OPA

Now it’s Time for Awkward Questions

Is it possible to restrict access to specific resources and not just specific methods?
Partially it is. We have two types of resources: requested and returned. The object that is requested at the time of request can be selected and sent to the authorization request. According to the rules, you need to take into account the parameters of the object. For the returned resource, the response is symmetric. However, the access request will have to be generated after the application has processed the request.
However, that’s not necessary. This implementation is based on the data available in the request with little or no processing. Practically, that’s because token deserialization is quite a significant cost. But it can be reduced by caching tokens at the proxy level. Considering that a token usually lives for more than 15 minutes, this will significantly reduce the processing time.
And if you bring request parsing and extracting key data from the body to the request logic, this can significantly slow down the processing of requests. In the future, however, you will face the fact that the “Give everything available to this user” method will require some post-processing.
Can this approach be used with Graphql or services that ignore REST?
Partially it can. By extracting the Graphql function from the request body, you will manage to more accurately determine the access rights.
However, that’s not necessary. Since this will eventually lead to a loss of performance for the reasons from the first point.