Fine-grained Service Authorizations with Istio and OPA

Ensuring secure communication within networks is one of the pillars of the zero-trust approach. The traditional model of securing the boundary and allowing open communication within has evolved into more granular, identity-based security paradigms.

Istio, being part of the infrastructure, provides a way to apply security policy to peer service identities and tokens attached to the requests simultaneously.

As an infrastructure component, Istio doesn’t and shouldn’t know about the business logic of the application. However, its extensibility feature can easily integrate into existing policy systems, allowing you to decouple policy decisions from your infrastructure and your services.

When looking at the north-south traffic, API gateways act as a control point for the outside world to access the various application services that run in your environment. Similarly, once the requests are inside the environment we still want to enforce some form of authentication authorization for east-west traffic.

north-south east-west traffic

In the past, we secured the boundary to the virtual private cloud (VPC), and then services within the VPC were allowed to communicate. If there was a need for fine-grained control, we’d leverage layer 3 firewall rules and start segmenting the VPC.

This technique, called “micro-segmentation”, allows us to logically divide the network into smaller segments and apply security policies to each segment.

policy management

As the core principle of zero trust, one of the goals of micro-segmentation is to reduce the number of attack surfaces and, consequently, limit lateral movement attacks.

To implement micro-segmentation, one of the requirements is to have a way to create, assign, and manage identities for each micro-segment, and to do that at scale. Additionally, we want a uniform way to manage policies, get metrics, and observe the traffic between the applications.

It is challenging to implement this, as we need to keep up with the evolution of applications in the network – for example, living in the Kubernetes world, we know how fast IP addresses change and what that would mean for any identities that are based on that.

Identity Model in Istio

Istio offers an identity-based authorization model that relies on the workload identity instead of relying on IP addresses. Istio’s identity model is flexible and will depend on the platform you’re using. For example, in Kubernetes, the identity is based on the Kubernetes service accounts.

Each time we deploy a workload to the Istio service mesh running in Kubernetes, Istio creates a strong identity based on a short-lived X.509 certificate and manages the certificate’s lifecycle. The identity creation is transparent to the applications running in the mesh, and it doesn’t require any changes to the application itself or the infrastructure.

With that, Istio provides a way to secure service-to-service communication using mutual TLS and to authenticate peers. On top of that, it also allows us to do request-level authentication and verify the credentials that are attached to the requests. Lastly, it allows us to do both at the same time!

request authentication

So with the combination of an authorization policy and request authentication, we can apply security policy to peer identities (“Service A can call service B on /path…”) and tokens attached to the requests (“… as long as it has a JWT and with audience XYZ”).

For certain scenarios, a coarse-grained policy like that is enough, however, in some cases, we need more control and fine-grained policies when answering questions like “Can User X access resource Y, can User Z access …”.

Since Istio is generic infrastructure, it doesn’t know about User X or User Z. To know whether User X or User Z have read or write access typically requires reading the resource out of the storage, which is business logic specific to the business use case and the permission model behind it. This is part of the individual service’s capabilities, not the infrastructure.

One of the features of Istio is its extensibility which allows integration with external services that can make the business use-case-specific policy decisions. Istio is built on top of Envoy, and one of the features of Envoy is an external authorization filter, which allows delegating authorization decisions to an external service.

external services

While Istio can parse and validate the JWT, an external service can check individual token claims against policies and make decisions based on that. This enables us to decouple policy and policy decisions from the service code and integrate any external policy systems. One example of such an external service is Open Policy Agent (OPA).

What is Open Policy Agent?

Open Policy Agent is a general-purpose policy engine that allows you to uniformly enforce policies across your organization. When running in the context of Istio service mesh, OPA can be configured as an external authorization provider and used for fine-grained policy decisions.

As the requests reach the Istio sidecar or waypoint proxies, in the case of Istio ambient mesh, the request attributes are passed to the authorization service, where they get evaluated against the combination of policies and data. Based on the decision from the external service, the request is either allowed or denied.

For a more detailed overview of the Open Policy Agent, check out watch this “Introduction to Open Policy Agent” video:

If we continue with the previous example, once OPA is configured we can write policies using a language called Rego. As part of the policy, we could implement things such as JWT token verification, extracting the token’s claims, and using them to make policy decisions.

Note that things such as JWT token verification and claim- and scope-based authorization can be accomplished without any extra components with Gloo Gateway. The same goes for making policy decisions on any L7 attributes, such as headers, paths, and methods.

Let’s say we need to check the request attributes against some data in the database or other storage and make a decision specific to the service’s use case. Implementing and hardcoding business case decisions in each individual service is not the right way to set up the policies, nor is making these decisions at the proxy level. Your infrastructure shouldn’t know about your business logic. Instead, we can use the Istio proxy feature of calling out to the external authorization service that makes those decisions, without requiring us to modify the services code.

With a decision like that, we can define use-case-specific policies and do things like schema validation.

For example, the policy listing below sets up the policy that allows the requests when the following is true:

  • JWT token from the Authorization header is valid
  • The role claim from the JWT is set to “admin
  • The request is a POST request
  • The path in the request is /post
  • The schema in the request body conforms to the defined schema
package istio.authz

import future.keywords
import input.attributes.request.http as http_request

default allow := false

allow if {
	action_allowed
}

action_allowed if {
	is_admin
	http_request.method == "POST"
	http_request.path == "/post"
	is_schema_valid
}

is_admin if {
	token_payload.role == "admin"
}

is_schema_valid if {
	[match, _] := json.match_schema(http_request.body, schema)
	match == true
}

# Decode the token and return the payload
token_payload := payload if {
	[_, payload, _] := io.jwt.decode(token)
}

# Get the token from the Authorization header
token := t if {
	# "Authorization": "Bearer <token>"
	t := split(http_request.headers.authorization, " ")[1]
}

# Schema to validate the request body against
schema := {
	"properties": {"id": {"type": "integer"}, "owner": {"type": "string"}, "price": {"type": "integer", "minimum": 0}}
	"required": ["id"],
}

There are multiple ways to deploy OPA. When running inside the cluster, we can deploy it as a separate component (i.e., one-per host, cluster, or per namespace) or run it as another sidecar container within each pod. The OPA deployment model will depend on your specific requirements.

The next step is configuring the OPA instance and deciding where the policies will be read from. The simplest, yet impractical, option is storing the policies in ConfigMaps; a better solution is creating policy bundles and deploying them to a separate location (HTTP server) and configuring OPA to download and refresh the policies automatically. Note that using the policy bundles also requires setting up and managing your policy deployment workflow and maintaining an additional HTTP server to store the policies.

Now that we have OPA configured and running, we must also tell Istio to use it. We must configure an extension provider in the mesh config and point to the OPA service. Once we have this in place then we can use AuthorizationPolicy resource to configure for which workloads we want to delegate the auth decisions to the external provider.

External Authorization with Gloo Gateway and Gloo Mesh

Since API gateways are the connection with the outside world, they typically receive a large number of requests. This is the first point at which we can use external authentication to establish and validate the client, which services its calling, and the attributes of the incoming request.

Gloo Gateway comes with a built-in external auth server that implements a variety of authentication and authorization models, such as authenticating requests with usernames and passwords, API keys, using LDAP, using OpenID Connect with OAuth 2.0, Open Policy Agent, and it also allows you to use your own external gRPC service.

Gloo Gateway

The authorization models are implemented using a set of custom resources that make it straightforward to use your existing external authorization system or the one Gloo Gateway provides.

As opposed to manually installing and configuring OPA, configuring custom extension providers with Istio, and AuthorizationPolicies, with Gloo, an external authorization service gets deployed for us, and we’re given a dedicated Kubernetes resource called ExtAuthPolicy to apply the policies.

We don’t have to worry about yet another deployment or configuration and instead just focus on our policy model (or any other supported auth model) and directly apply it to any or all routes in a workspace. However, if you already have a policy solution like OPA, Gloo can connect to it instead.

apiVersion: security.policy.gloo.solo.io/v2
kind: ExtAuthPolicy
metadata:
  name: ratings-opa
  namespace: bookinfo
spec:
  applyToRoutes:
  - route:
      labels:
        route: ratings
  config:
    server:
      # Default ext auth server in the cluster
      name: ext-auth-server
      namespace: bookinfo
      cluster: mycluster
    glooAuth:
      configs:
      - opaAuth:
          modules:
            # Reference to the OPA policy (ConfigMap)
          - name: allow-get-users
            namespace: bookinfo
          # Which path to test for allow/deny decision
          query: "data.test.allow == true"

The ExtAuthPolicy above applies to the ratings route and references a Rego policy specified in the allow-get-users ConfigMap.

In addition to OPA policy support, we can configure multiple authorization configurations simultaneously – for example, we could configure ApiKeyAuth, and then the OPA configuration, LDAP, or any other supported authentication and authorization model.

apiVersion: security.policy.gloo.solo.io/v2
kind: ExtAuthPolicy
metadata:
  name: ratings-opa
  namespace: bookinfo
spec:
  applyToRoutes:
  - route:
      labels:
        route: ratings
  config:
    server:
      # Default ext auth server in the cluster
      name: ext-auth-server
      namespace: bookinfo
      cluster: mycluster
    glooAuth:
      configs:
      - apiKeyAuth:
          headerName: api-key
          headersFromMetadataEntry:
            x-user-email: 
              name: user-email
          k8sSecretApikeyStorage:
            labelSelector:
              extauth: apikey
      - opaAuth:
          modules:
            # Reference to the OPA policy (ConfigMap)
          - name: allow-get-users
            namespace: bookinfo
          # Which path to test for allow/deny decision
          query: "data.test.allow == true"

The configuration above shows the API key authentication configured in addition to the OPA. The API key auth extracts the values from the api-key and x-user-email headers and then compares the values to the ones stored in the apikey Kubernetes secret.

Conclusion

Istio as an infrastructure component with identity-based authorization, OPA’s policy engine, and Gloo Gateway’s ability to implement various authorization models work together to secure both north-south and east-west traffic. As security threats grow more sophisticated, the need for granular, identity-based security measures increases. By employing these tools, we are better equipped to build a secure network environment that limits attack surfaces, mitigates lateral movement attacks, and ensures robust authentication and authorization.

Do you want to explore how Solo and Gloo Gateway can help you implement zero-trust networking?