Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
246 changes: 246 additions & 0 deletions docs/integrations/gateways/envoy.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
---
sidebar_position: 4
title: Envoy Proxy x Permit
---

## Overview

This guide explains how to integrate Envoy's External Authorization (ext_authz) filter with a Permit.io PDP using **GitOps-managed custom Rego policies**.
With this setup, Envoy acts as a gateway that filters application requests by calling the PDP, which evaluates your Permit policies and returns an allow/deny decision.

URL Mapping (`POST /allowed_url` on the PDP HTTP API) is a separate path. It is not used by the Envoy ext_authz gRPC integration described here — the entrypoint policy must map the request to a Permit resource and action itself.

---

## Prerequisites

- **Envoy** deployed with the [External Authorization filter](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/ext_authz_filter) available
- **Permit.io account** and project with a running PDP
- **PDP version** **0.9.10 or higher** (required for this integration)
- **GitOps flow** configured for your Permit environment (see below)
- Basic familiarity with Envoy configuration and OPA/Rego

---

## 1. Configure GitOps for Custom Policies

Use Permit GitOps to manage your PDP policies (including the Envoy entrypoint) in a Git repository.

1. **Configure GitOps** for your environment (choose one of the following):
- **Using the CLI**: follow the CLI GitOps guide: [Custom Rego (OPA) and GitOps CLI](/how-to/permit-cli/permit-cli-gitops).
- **Using the manual flow**: follow the GitOps integration guide: [Git and Permit](/integrations/gitops/github).
2. **Clone the GitOps repository** locally and check out the branch for the environment you want Envoy to protect.

Once GitOps is configured, any changes you push to the repository will be synced to the PDP.

---

## 2. Add an Envoy entrypoint policy under `custom`

In your GitOps repository:

1. **Create a new Rego file** under the `custom` directory at the root of the GitOps repo, for example:

```text
custom/envoy_entrypoint.rego
```

2. Define an entrypoint rule that Envoy's ext_authz integration will call.
The PDP OPA plugin will evaluate the data document at the path you configure (see the `PDP_OPA_PLUGINS` section below).

Put the policy in the `permit.custom` namespace used by other custom GitOps files, and derive a Permit resource **key** from the path (Permit resource keys cannot contain `/`):

```rego
package permit.custom.envoy

import data.permit.root as check
import input.attributes.request.http as http_request

# Boolean entrypoint evaluated by the Envoy ext_authz gRPC plugin
default envoy = false

envoy {
method := http_request.method

# First path segment is the resource key: /documents/123 -> "documents"
# This key must match a resource configured in Permit.
segments := split(trim_prefix(http_request.path, "/"), "/")
resource_key := segments[0]

# Example user & tenant extraction (adapt to your authentication setup)
user_key := http_request.headers["x-user-id"]
tenant_key := http_request.headers["x-tenant-id"]

check_input := {
"user": {
"key": user_key,
},
"action": method,
"resource": {
"type": resource_key,
"tenant": tenant_key,
},
}

# Call "permit.check"
check.allow with input as check_input
}
```

This boolean entrypoint is enough for Envoy to allow or deny the request. The Audit Log **Decision** column is filled from the boolean result. To also fill **user**, **action**, **resource**, and **tenant**, return an object that includes `permitio_metadata` — see [Ingest Envoy decisions into Permit Audit Logs](#5-ingest-envoy-decisions-into-permit-audit-logs).

3. Commit and **push** the new file to the GitOps repository.
4. Wait for the PDP to sync the updated policies (or trigger a sync if you are using a CI flow).

For the full structure of the Envoy authorization input (`input`) used in your policy, see the official
[Envoy ext_authz filter documentation](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/ext_authz_filter).

---

## 3. Configure the PDP OPA plugins

The PDP **must** be started with an `envoy_ext_authz_grpc` plugin that exposes the Envoy-compatible gRPC service and points it at your custom policy entrypoint.

Set the **`PDP_OPA_PLUGINS`** environment variable when running the PDP:

```bash
export PDP_OPA_PLUGINS='{"permit_graph":{},"envoy_ext_authz_grpc":{"addr":":9191","path":"permit/custom/envoy"}}'
```

- **`permit_graph`** is required for Permit to function correctly.
- **`envoy_ext_authz_grpc`** enables the Envoy gRPC external authorization endpoint.
- **`addr`** is the address/port on which the gRPC server will listen (e.g. `:9191`).
- **`path`** must match the OPA data path of your Envoy entrypoint policy (in the example above, `data.permit.custom.envoy`).

The gRPC listener also has to be reachable from Envoy. The PDP image does not expose port `9191` by default:

- **Docker:** add `-p 9191:9191` to the `docker run` command (or a `ports` entry in Compose).
- **Helm:** add the port to the PDP Service via `pdp.additionalPorts`:

```yaml
pdp:
additionalPorts:
- name: grpc
port: 9191
targetPort: 9191
```

For more advanced plugin and Envoy integration options (timeouts, metadata, headers, etc.), see the OPA Envoy plugin configuration docs: [OPA Envoy Integration Configuration](https://www.openpolicyagent.org/docs/latest/envoy/#configuration).

Ensure this variable is set alongside your usual PDP configuration (such as `PDP_API_KEY`, `PDP_DEBUG`, etc.).

---

## 4. Configure Envoy to call the PDP

In your Envoy configuration, add the **HTTP ext_authz filter** and point it to the PDP gRPC endpoint configured above.

A minimal example (simplified) might look like this:

```yaml
http_filters:
- name: envoy.filters.http.ext_authz
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz
transport_api_version: V3
grpc_service:
envoy_grpc:
cluster_name: permit_pdp_ext_authz
timeout: 0.5s

clusters:
- name: permit_pdp_ext_authz
type: logical_dns
connect_timeout: 0.25s
lb_policy: round_robin
Comment thread
omer9564 marked this conversation as resolved.
http2_protocol_options: {}
load_assignment:
cluster_name: permit_pdp_ext_authz
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: pdp
port_value: 9191
Comment thread
omer9564 marked this conversation as resolved.
```

`http2_protocol_options` is required so the cluster speaks HTTP/2, which gRPC needs. Without it, the upstream defaults to HTTP/1.1 and ext_authz calls to the PDP can fail.

Adjust the cluster name, address, and port to match your environment.
Consult the [Envoy ext_authz docs](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/ext_authz_filter)
for the full configuration options.

---

## 5. Ingest Envoy decisions into Permit Audit Logs

Envoy's ext_authz input is **not** a `permit.check` payload. The PDP records a decision log for the Envoy query (`permit/custom/envoy`) and fills the **Decision** column from the boolean result, but the Audit Log page cannot fill **user**, **action**, **resource**, or **tenant** from that Envoy-shaped input.

To map those columns, include a `permitio_metadata` object on the **policy result**, using the same structure as [`permit.check()`](/how-to/enforce-permissions/check). Permit reads this object as a fallback when the query input is not a standard check.

Return an object from the Envoy entrypoint (the OPA Envoy plugin uses the `allowed` field for allow/deny). Put `permitio_metadata` on the **default** as well — otherwise default-denied requests (missing headers, unparseable path) produce an empty audit row and are dropped:

```rego
package permit.custom.envoy

import data.permit.root as check
import input.attributes.request.http as http_request

default envoy := {
"allowed": false,
"permitio_metadata": {"allow": false},
}

envoy := {
"allowed": allowed,
"permitio_metadata": {
"allow": allowed,
"user": {"key": user_key},
"action": method,
"resource": {
"type": resource_key,
"tenant": tenant_key,
},
},
} {
method := http_request.method
segments := split(trim_prefix(http_request.path, "/"), "/")
resource_key := segments[0]
user_key := http_request.headers["x-user-id"]
tenant_key := http_request.headers["x-tenant-id"]

check_input := {
"user": {"key": user_key},
"action": method,
"resource": {
"type": resource_key,
"tenant": tenant_key,
},
}

allowed := check.allow with input as check_input
}
```

`permitio_metadata` supports the following fields (all optional; omit any you do not have):

| Field | Audit Log column |
| --- | --- |
| `allow` | Decision (allow / deny) |
| `user.key` | User |
| `user.email` | User email |
| `user.first_name` / `user.last_name` | User display name |
| `action` | Action |
| `resource.type` | Resource |
| `resource.tenant` | Tenant |

If the query input already has a standard `permit.check` field (for example `input.user.key`), that value wins. `permitio_metadata` is only used when the corresponding input field is missing.

:::note
Keep the OPA path under the `permit/` prefix (for example `permit/custom/envoy`). Decision logs whose query path does not start with `permit/` are not ingested into the Audit Log.
:::

After traffic hits Envoy, open the [Audit Log](https://app.permit.io/audit-log) and use the **All** query type. Envoy evaluations are not `permit.check` queries, so they will not appear if you filter to Check only. Missing columns in the table mean that field was not present on `permitio_metadata`.

For how to read and filter logs, see [Audit Types & Filtering](/how-to/use-audit-logs/types-and-filtering).
1 change: 1 addition & 0 deletions sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,7 @@ const sidebars = {
"integrations/gateways/aws-api-gateway",
"integrations/gateways/kong",
"integrations/gateways/nginx",
"integrations/gateways/envoy",
],
},
{
Expand Down