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.
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.
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:
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.
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:
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:
/jwt/create
– Create a JWT token without roles;/jwt/create/viewer
– Create a JWT token with the viewer role: “viewer”;/jwt/create/editor
– Create a JWT token with the editor role: “editor”;/jwt/create/admin
– Create a JWT token with the administrator role: “admin”;/jwt/roles
– View the roles in the issued token;Nginx configuration that calls jwt.js methods
The
/security/
route is used as an API applicationOPA
Description of roles and methods:
Nginx + OPA
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.