Authorize Better: Istio Traffic Policies with OPA, Styra DAS
Cloud native tooling for authorization is an emerging trend poised to revolutionize the way we approach this oft-neglected part of our applications. Open Policy Agent (OPA) is the leading contender to become a de-facto standard for applying policies to many different systems, from workloads running on Kubernetes to requests passing through Istio. In this article I will look more closely at the latter use case and try to answer the question of what OPA and its commercial management tool Styra DAS offer.
When services are connected using the Istio service mesh, all those sidecar proxies running Envoy are great places to make authorization decisions. All HTTP requests flow through them with metadata included about the source and destination services. The capabilities of Envoy are exposed by Istio in the form of the AuthorizationPolicy object. Whatever you can express in the AuthorizationPolicy, Istio will do a great job of enforcing.
Some examples of policies that can be implemented using AuthorizationPolicy:
- Service A can only talk to Service B on port 80 with HTTP path /getobjects.
- Service B can only receive requests from namespace X.
- All requests to Service B must contain a valid JSON Web Token (JWT).
But, how far can AuthorizationPolicy get us? It can perform checks on HTTP headers and the path, as well as validating JWTs. It can also use metadata about which Kubernetes service the request originates from and where it’s heading.
While Istio offers us quite a few options, it is also limited in several ways. It cannot use all fields from a JWT, which is often the place where authentication systems like Auth0 place metadata about the user. It also cannot use the request payload for decision-making. Finally, it doesn’t have any contextual information beyond the few pieces mentioned above.
More Authorization with OPA
The Envoy proxy that Istio uses has the ability to delegate authorization decisions to an external system. It will send all the information about the incoming request in JSON format and expect to basically get a “yes” or “no” decision in response. This is exactly the operating model of OPA — receive JSON data, evaluate policy, return yes or no. We can add OPA as the external authorizer to Istio’s Envoy sidecars over the whole service mesh:
The OPA agent will run as a separate process, preferably as another sidecar container next to the microservice and will make all the authorization decisions for Envoy. You can learn more about the technical details of the OPA-Envoy integration in the OPA docs.
OPA’s policies are written in the declarative policy language Rego. Not only is the language more flexible than AuthorizationPolicy, but it can work with the parts of the request that Istio doesn’t give us access to. It can also make use of additional data about the request’s context; we can load any data into OPA and use it during policy evaluation.
To be able to show the possibilities OPA gives us, we need a more concrete example. Let’s imagine that a user of the Payroll Service is setting up a new employee, which will result in a call from the Payroll Service to the Employee Manager Service. Both services live in their own namespaces and are assigned their own ServiceAccounts. The Payroll Service will include the logged-in user’s JWT in the request, which will include the groups and roles the user belongs to. Finally, the request body will contain the newly created employee’s data.
Let’s come up with a few policies we couldn’t express with pure Istio:
- Only users identified as “manager” in the JWT token can create new employees.
- Managers can only create employees in their own group.
- A user must be part of the employee-managers list to be allowed to create employees.
Now let’s take a look at some code and see how we would implement these policies. Note that this will depend on the structure of the JSON that Envoy passes to OPA. Here is that JSON, with all non-essential data removed for clarity:
Let’s implement the first rule: Only users identified as “manager” in the JWT token can create new employees.
This assumes that we already parsed the bearer token as a JWT.
Let’s see the second one: Managers can only create employees in their own group.
This is an extension of the previous rule, with an additional condition.
Now let’s add one more to verify whether the requesting user is in a special list of users who are allowed to create employees. To simplify things the list is part of the OPA rule, but we could be loading data from external sources as well such as Git or an HTTP API.
These aren’t complex rules, but they couldn’t be implemented using Istio’s AuthorizationPolicy. The price we pay for the additional flexibility is performance. While Istio’s policies are evaluated directly by Envoy, in the OPA case, Envoy has to make an HTTP request to the OPA sidecar. OPA stores its rules in memory already precompiled, so the evaluation time is measured in milliseconds, but of course even an HTTP call to localhost will always be slower than an operation inside the process itself. Our experience is that performance only becomes a problem for services dealing with hundreds of thousands of requests per second.
This table summarizes the different types of policies and where do we need OPA to implement:
|JWT||is the token valid||✅||✅|
|JWT||sub, iss, aud, azp||✅||✅|
|JWT||all other fields||❌||✅|
|HTTP Request Body||❌||✅|
|Context||Data not present in the request||❌||✅|
Managing a fleet of OPAs
By moving our policy rules into OPA, we have introduced a management challenge. All those Envoy proxies are managed by the Istio control plane, but no one is managing all the OPAs. First, we have to make sure an OPA sidecar container gets injected into every pod. We can do this pretty easily using a mutating webhook.
The more difficult challenge is to manage the rules that each OPA container executes. This is the reason Styra, the creators of OPA, created the Styra Declarative Authorization Service (DAS). Styra DAS is a SaaS service that acts as the control plane for OPA the same way as Istio acts as the control plane for Envoy. Styra DAS will store all the rules and related data (e.g. a Datasource containing the employee_managers list) and each OPA agent will periodically poll Styra DAS for the latest rule bundle.
Additionally, OPA agents will push decision logs back to Styra DAS so they can be recorded and analyzed for auditing purposes.
Centralized policy management and collection of policy decision logs are a powerful combination that helps companies meet audit and compliance requirements. Additionally, OPA is not limited to authorizing HTTP requests passing through Envoy, but can be integrated with Kubernetes, Terraform and basically any other system that can interact with its simple, JSON-based API. With this, Styra DAS can become a single dashboard for all policy decisions in the organization.
While OPA is an open source project, Styra DAS is a commercial product. That raises the question of how to distribute your policies if you don’t want to use Styra DAS. The protocol connecting OPA to Styra DAS is part of the open source project, which means you can use a cloud bucket to store your policy bundles and to upload decision logs. You can also roll your own home-grown HTTP service to serve policy bundles and receive decision logs. You can find the documentation for these APIs in the OPA docs.
Istio’s built-in AuthorizationPolicy mechanism is a great tool, but once you hit its limitations, OPA is the way to take the next step. What’s more, OPA takes you much further than just better Istio authorization; it helps you build centralized authorization across different domains and system types.