Using Open Policy Agent with Envoy

Mike Dunn
CloudX at Fidelity
Published in
9 min readDec 7, 2021

--

Photo by Kutan Ural on Unsplash

This is the third Envoy & Open Policy Agent Getting Started Guide. Each guide is intended to explore a single feature and walk through a simple implementation. Each guide builds on the concepts explored in the previous guide with the end goal of building a very powerful authorization service by the end of the series.

All of the source code for this getting started example is located on GitHub. → Envoy & OPA GS # 3

Envoy’s External Authorization API

Envoy has the capability to call out to an external authorization service. There are two protocols supported. The authorization service can be either a RESTful API endpoint or using Envoy’s new gRPC protocol. Envoy sends all of the request details to the authorization service including the method, URI, parameters, headers, and request body. The authorization service simply needs to return 200 OK to permit the request to go through.

In this example, we will be using a pre-built Open Policy Agent container that already understands Envoy’s authorization protocol.

Open Policy Agent Overview

The Open Policy Agent site describes it very succinctly.

The Open Policy Agent (OPA) is an open source, general-purpose policy engine. OPA provides a declarative language that lets you specify policy as code and APIs to offload policy decision-making from your software. OPA to enforce policies in micro-services, Kubernetes, CI/CD pipelines, API gateways, or nearly any other software.

OPA focuses exclusively on making policy decisions and not on policy enforcement. OPA pairs with Envoy for policy enforcement. OPA can run as:

  • A stand-alone service accessible via an API
  • A library that can be compiled into your code

It has an extremely flexible programming model.

Picture that shows the REGO Playground web site, the VS Code Rego Policy Authoring tool and the command line interface that supports REPL style interaction.

Additionally, the company behind OPA, Styra, offers control plane products to author policies and manage a fleet of OPA instances.

OPA decouples policy decision-making from policy enforcement. When your software needs to make policy decisions it queries OPA and supplies structured data (e.g., JSON) as input. OPA accepts arbitrary structured data as input.

OPA generates policy decisions by evaluating the query input against policies and data. OPA expresses policies in a language called Rego. OPA has exploded in popularity and has become a Cloud Native Computing Foundation incubating project. Typical use cases are deciding:

  • Which users can access which resources
  • Which subnets egress traffic is allowed
  • Which clusters a workload can be deployed to
  • Which registries binaries can be downloaded from
  • Which OS capabilities a container can execute with
  • The time of day the system can be accessed

Policy decisions are not limited to simple yes/no or allow/deny answers. Policies can generate any arbitrary structured data as output.

This getting started example is based on this OPA tutorial using docker-compose instead of Kubernetes.

SOLUTION OVERVIEW

In this getting started example we take the super simple envoy environment we created in getting started episode 1 and add the simplest possible authorization rule using Open Policy Agent.

Picture that shows HTTPBIN, Envoy and Open Policy Agent running inside docker on the local machine.
Diagram of example solution

Docker Compose

The starting point is the docker compose file used in the “Using Envoy as a Front Proxy” article. There are a few modifications.

Line 15 in the docker-compose file adds a reference to the Open Policy Agent container. This is a special version of the Open Policy Agent containers published on DockerHub. This version is designed to integrate with Envoy and has an API exposed that complies with Envoy’s gRPC specification.

There are also some differences in the configuration file to make note of in the highlighted section of the image below:

  • Debug level logging is set on line 21
  • The gRPC service for Envoy is configured on line 23
  • The logs are sent to the console for a log aggregation solution to pickup on line 24
Picture that shows a new Open Policy Agent section added to the docker compose file from the previous blog post on using OPA with Envoy. This configuration will be discussed in the remainder of the blog.
A new section for OPA was added to the docker compose file from the previous blog post

ENVOY CONFIG CHANGES

The envoy configuration file is shown below. There are a few things that were added to the configuration to enable external authorization:

  • The external authorization filter is added at line 23.
  • The failure_mode_allow property on line 26 determines whether envoy fails open or closed when the authorization service fails.
  • The with_request_body property determines if the request body is sent to the authorization service.
  • The max_request_bytes property determines how large of a request body Envoy will permit.
  • The allow_partial_message property determines if a partial body is sent or not when a buffer maximum is reached.
  • The grpc_service object on line 30 specifies how to reach the Open Policy Agent endpoint to make an authorization decision.
A picture that shows a new section for the external authorization endpoint configuration that was added to the envoy configuration from the previous blog. This configuration will be discussed in the remainder of the blog.
A new section was added for the external authorization endpoint to the envoy configuration from the previous blog

Rego: OPA’s Policy Language

There are a lot of examples of how to write Rego policies on other sites. Rego is covered in this walkthrough to extent required to show how to use it in making Envoy authorization decisions:

Line 1: The package statement on the first line determines where the policy is loaded into OPA. When an application requests a policy decision from OPA, the package name is used as the prefix to locate the named decision.

Line 3: The import statement on line 3 assigns a much shorter alias to the heavily nested data structure that Envoy sends OPA. The alias is used in the rest of the Rego policy.

Line 5: The default keyword, sets the value returned for a rule in case no other rule explicitly defines the result. This line declares that there is a rule and gives it the name allow. It also assigns a default value for the rule. The data structure shown is defined in the API contract for Envoy’s Authorization API. The

Envoy contract specifies the following properties:

  • The allowed property determines if the request is permitted to go through or not.
  • The headers property allows us to set headers on either the forwarded request (if approved) or on the rejected request (if denied). OPA does not marshal any Rego data types to a string on your behalf. Therefore all header values must be specified as a string.
  • The body property can be used to communicate authorization error details to the requestor. It does NOT override the request body sent upstream for approved requests. OPA does not marshal any Rego data types to a string on your behalf. So, the body value must be specified as a string.
  • The http_status property can be set on any rejected requests to any value as desired.
Picture that shows the Rego policy that will be discussed
A Simple Rego Policy to demonstrate Envoy Integration

The last section starts at line 12. It defines the logic for approving the request.

  • Line 12: Defines an authorization rule. It is given the name allow. Its result is defined as equal to the variable named response. The variable response is defined in the body of the rule on line 14.
  • Line 13 is a logical expression. If the variable http_request.method equals POST then the result of the expression is true.
  • Line 14 assigns the object defined on lines 15 and 16 to the variable named response.

If the execution of every expression in the body of the rule is true, then the value of the response variable is assigned as result of the allow rule. Since this is there are no other rules to execute in our policy, then this result is sent back to Envoy as the authorization decision.

Envoy asks OPA for an Authorization Decision

Below is an example of an authorization request sent from Envoy.

The request sent from Envoy is a JSON object that is heavily nested and very deep. Let’s dive into the details of the data structure.

Line 3 is an object called destination. This is where Envoy will send the request if it is authorized. The destination object contains both the IP address and port where the request is going.

If approved, line 10 has a complete copy of the request object that will be sent to the destination. This is included so that it can be taken into account for the authorization decision. The body is present and encoded as a string. Other information about the request is included as well:

  • request headers
  • hostname where the original request was sent
  • a unique request ID assigned by Envoy
  • request method
  • unparsed path
  • protocol
  • request size
A picture that shows the JSON input received by Open Policy Agent from Envoy.
A picture that shows the JSON input received by Open Policy Agent from Envoy

Line 34 is an object that describes where the request came from. It contains the IP address and port where the request originated.

Line 45 contains the results of Envoy’s processing of the request. If Envoy was able to parse the body, it is included here as JSON. The developer has the option to configure whether the body is forwarded or not.

Line 46 and 47 contains the results of Envoy’s analysis of the request path and query parameters respectively.

Finally, line 48 communicates whether the complete request body is present or not. There are a couple of reasons that the entire request body might not be present. One reason is if Envoy’s request buffer was exceeded. The size of the request buffer is defined in the Envoy configuration file. Another reason that the body may be truncated is if chunked encoding is used and all chunks have not been received yet.

Reviewing the Request Flow in Envoy’s Logs

In debug mode, there is a lot of information that Envoy logs to help determine what may be going wrong as we develop our system. Below is an example screen shot of envoy logs.

  • Line 1 shows the connection request first coming in
  • Lines 4 through 12 shows the request headers that Envoy received
  • Line 13 through 14 indicates that Envoy is buffering the request until it reaches the buffer limit that is set in the configuration file
  • Line 16–20 shows that Envoy forwarded the request to the authorization service and got an approval to forward the request
  • The remainder of the logs show envoy forwarding the request to its destination and then forwarding the response back to the originating client
A picture that shows the details that envoy logs when it processes a request.
A picture that shows the details that envoy logs when it processes a request.

Reviewing OPA’s Decision Log

The open policy agent decision logs include the input that we just reviewed and some other information that we can use for debugging, troubleshooting or audit logging. The interesting parts that we haven’t reviewed yet include:

  • The decision ID on line 2 can be matched against other OPA log entries to find other log lines that are related to this decision
  • Line 4, the attributes property contains the full authorization request that OPA received from Envoy
  • Line 37 has the labels that describe the specific envoy instance the authorization request came from
  • Line 42 is an object that describe how long OPA took to make the decision
  • Line 50 shows the response that OPA sent back to Envoy
A picture that shows OPA’s decision log.
OPA’s decision log

Taking the Example Solution for a Spin

You can see all of the above working on your own local machine. With docker installed and configured properly, clone the example repository and run the example by executing the script ./demonstrate_opa_integration.sh

  1. The script starts several containers. It starts the envoy front proxy, HTTPBIN and Open Policy Agent.
  2. Next the script prints out the docker container status to allow the user to make sure everything is up and running.
  3. Curl is used to issue a request. The request sent should be valid. It should be approved, forwarded to HTTPBin instance and the script should display the results.
  4. The OPA decision logs are sent to the terminal screen to let user explore the information available for development debugging and troubleshooting.
  5. The script also has a test case that shows an un-authorized request. The next request should fail the simple authorization rule check.
  6. The user can inspect the decision logs to see how they differ from the authorized transaction use case.
  7. Finally, the example is brought down and cleaned up.

In the next getting started guide we will explore Open Policy Agent’s command line interface and unit testing support.

Originally published at https://helpfulbadger.github.io on September 1, 2020.

--

--

Mike Dunn
CloudX at Fidelity

Lead Developer Experience and Engineering Standards for Fidelity’s Personal Investing Business.