With microservices development, I often came across a problem with implementing Authentication and Authorization (A&A). We want a robust and centrally managed authentication and authorization strategy. But, the distributed nature of the application makes it difficult to implement. In this post, I will explore how Open Policy Agent can help simplify the authorization problem.

Let’s take a quick look at the definition for Authentication and Authorization. Authentication refers to identifying the user (“who”). Whereas Authorization refers to determining the level of access an authenticated user has (“what”).

My focus for this post is the Authorization part. For simplicity sake, I have created a sample application with a set of microservices. There is a basic user interface where we can carry out various operations and see the results. The only purpose of this application is to show how various authorization scenarios are handled by Open Policy Agent. In the subsequent posts, we will extend this application to cover increasingly complex use cases, and policy administration.

So, let’s get started!

Sample Application

First, some context about the application. I am taking an example of a CPQ application commonly used by sales teams to configure quotes for customers.

There are following roles :

Sales – Users that have Sales role can create new offers for their customers and update the offers. The users that have Sales role cannot, however, delete an offer. Sales Support – Support staff who can see all the offers but cannot edit any offer. Sales Admin – Administration staff, can see all the offers but cannot edit/create any offer. However, they can delete an offer if required to ensure cleanup.

Since we are focusing on the authorization part, I have assumed that the user is already authenticated and has a valid JSON Web Token (JWT). Every API request contains this JWT in the request header.

Download the sample application from github here. Follow the install instructions in README and you should be able to access the UI with url http://<MINIKUBE URL>/

Authorization in action…

Based on the roles we saw above, the expectation is that the Sales team is able to

Create a new offer

List the offers

Update an existing offer

Whereas Sales Support team is only able to list the offers but not able to edit/create them. Sales Admin team is only able to list the offers and delete them.

The UI shows multiple buttons each representing an action from the user. Select a role that you want to use, and then try creating, editing or deleting an offer. The UI will give a feedback on whether the action is successful or not.

So, what is happening here?

Let’s take a quick look at how the setup is currently.

The application has two microservices Offer and Customer. A NGINX Reverse proxy exposes the APIs to outside world. NGINX intercepts each API request and requests authorization service to validate if user is allowed to execute the requested action or not. We use auth_request directive of NGINX to intercept the incoming API calls. Each API call has an Authorization header which contains a JWT. Entire user information including the Role is contained in the JWT.

The Authorization service has two containers –

Authorization – A custom built service (Authorization) to receive the request and create formatted input request for Open Policy Agent. Open Policy Agent (OPA) – Runs as a sidecar and exposes http endpoints for communication with Authorization container.

Basically, NGINX sends the /authorize request to the Authorization container to authorize an API call. Authorization service then consults Open Policy Agent whether to authorize the request or not (true/false). It then returns either a success (200 OK) or an error (403 Forbidden) response to NGINX. Accordingly, NGINX either allows the API call or returns a 403 Forbidden response to the client.

What is Open Policy Agent?

What better than to quote the Open Policy Agent website itself.

The Open Policy Agent (OPA, pronounced “oh-pa”) is an open source, general-purpose policy engine that unifies policy enforcement across the stack. OPA provides a high-level declarative language that let’s you specify policy as code and simple APIs to offload policy decision-making from your software. You can use OPA to enforce policies in microservices, Kubernetes, CI/CD pipelines, API gateways, and more.

OPA, basically, decouples the decision making with enforcement. It accepts structured data as input (JSON) and can return either a decision (true/false) or arbitrary structured data as output.

OPA uses rego as the policy language. You can read more about rego and open policy agent at https://www.openpolicyagent.org/docs/latest/

Authorization service in details..

Let’s look at the Authorization service in details to understand how it works.

We run Open Policy Agent in a server mode and make use of its REST APIs to update policies and get policy decisions.

Open Policy Agent exposes a REST API to create or update a policy.

PUT /v1/policies/<id> Content-Type: text/plain

For our example, update the policy in OPA using this endpoint with following request (this is mentioned in the GitHub README as well).

curl -X PUT --data-binary @policies/httpapi.authz.rego http://<MINIKUBE URL>/authorize/v1/policies/httpapi/authz

To get a decision based on this policy, there is another API

POST /v1/data/<path> Content-Type: application/json

here <path> is the path of the policy identified by the package e.g. package httpapi.authz . For our example, To delete an offer id “1000”, the Authorization service will invoke the endpoint http://localhost:8181/data/httpapi/authz with the following request body :

{ "input" : { "method": "DELETE", "api": "/offer/1000", "jwt": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE1NzQ2NjM3MDAsImV4cCI6NDA5OTE4NTMwMCwiYXVkIjoib3BhLWV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIkdpdmVuTmFtZSI6IkpvaG5ueSIsIlN1cm5hbWUiOiJSb2NrZXQiLCJFbWFpbCI6Impyb2NrZXRAZXhhbXBsZS5jb20iLCJSb2xlIjoiU2FsZXMgQWRtaW4ifQ._UtjZtowF3NNN3IF1t0LBHuzQhdfIfsO8jC-46GvbRM" } }

The Authorization application will receive the request from NGINX and generate an input request to OPA as above. For the sake of this example, I have hardcoded the JWTs in the frontend code. Each API request contains the JWT in the Authorization header. The Authorization application will extract the JWT and add it to the input request to OPA.

Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJMb2NhbEpXVElzc3VlciIsImlhdCI6MTU3MzcyNzM5MSwiZXhwIjo0MDk4MjQ1Mzc4LCJhdWQiOiJvcGEtZXhhbXBsZS5jb20iLCJzdWIiOiJzYWxlc0BleGFtcGxlLmNvbSIsIkdpdmVuTmFtdyI6IkpvaG5ueSIsIlN1cm5hbWUiOiJTYWxlcyIsIkVtYWlsIjoianNhbGVzQGV4YW1wbGUuY29tIiwiUm9sZSI6IlNhbGVzIn0.UbHWQpCMwupzsFp8f0CQ4o_bJSVaBugKijhcURZ_Mko

Note that, the authorization service only retrieves the JWT from incoming request, doesn’t decode it. OPA supports the parsing of JWTs via the builtin function io.jwt.decode .

. You can try the rego policy we used for this example in rego playground here. Play around with different input requests and you will see the output that OPA generates for each of these.

Conclusion

So, in a nutshell, Open Policy Agent provide a way to decouple authorization decisions from business logic in microservices. System administrators can setup Open Policy Agent and delegate the responsibility to generate authorization policies (rego policies) to individual microservice owners. Neither the microservice owners nor the system administrators deal with an area out of their boundaries.

Way forward..

I used a simplistic example here. But, there are many more considerations in a production system that this post doesn’t cover.

Current setup only authorizes based on API Endpoint URL. The policy doesn’t take into account the request body or other contextual information. For e.g. Sales teams may have more granular restrictions based on customer segments, geographical locations etc. OPA supports loading data from external data sources ( data document ) as well as partial evaluation of policies to address this.

) as well as partial evaluation of policies to address this. We updated the rego policies using a REST API endpoint of OPA. I have to repeat this every time the Authorization service restarts.

With more than one OPA instances running, updating policies through REST APIs is not scalable. OPA circumvents this through Bundle Service APIs to manage and distribute the policies to multiple instances.

Performance concern – sending each API call to authorization service will result into performance degradation.

The policy used here only provides a true/false type answer. OPA can return arbitrary structured data (JSON) as output. You can use this to enrich the request with additional attributes. This comes handy when implementing transparent data filtering.

OPA has solutions for all of these. I plan to publish a series of blog posts to cover these points one by one. I will enhance the example CPQ application we started in this post and explore the OPA capabilities to build a production grade authorization approach.

Till then, I hope, this gives you an overview of how OPA can be used during microservices authorization. If you like this post or identify a mistake or simply like to discuss the use case, please do comment and let me know.