About
This extension implements Open Policy Agent (OPA) policy evaluation as an inline Envoy HTTP filter. It replaces the traditional ext_authz pattern by running the OPA Go library directly within the filter, making authorization decisions on each request without a separate OPA sidecar or server.
Key Features
- Inline Rego evaluation: Load and evaluate Rego policy files directly in the Envoy data path using OPA's Go library. No separate OPA server required.
- ext_authz inspired input: Constructs an OPA input document from request attributes (method, path, headers, source/destination addresses) inspired by the OPA-Envoy plugin. Supports both boolean (allow/deny) and structured object responses with custom HTTP status codes, headers, and response bodies.
- Dry-run mode: Log authorization decisions without enforcing them, useful for testing policies in production.
- Fail-open mode: Allow requests through when policy evaluation encounters an error, instead of returning a 500 response.
- mTLS and SPIFFE identity: Access client certificate attributes including SPIFFE IDs (URI SANs), DNS SANs, and certificate subject for identity-based authorization.
- Configurable decision path: Query any rule path in the policy (default:
envoy.authz.allow). - Request body inspection: Optionally buffer the request body and expose it as parsed JSON
in the OPA input document under
input.body. Only JSON bodies are supported.
Input Document Structure
Requests are parsed and transformed into a structured input document that is passed to the OPA
policy for evaluation. The following input document shows an example of the structure that is
passed to the OPA policy on each request:
{
"attributes": {
"request": {
"http": {
"method": "GET", // HTTP method
"path": "/api/v1/resource", // Full path including query string
"host": "example.com", // Host header (:authority)
"scheme": "https", // URL scheme (default: "http")
"protocol": "HTTP/1.1", // Request protocol (default: "HTTP/1.1")
"headers": { // Request headers (excluding pseudo-headers)
"authorization": "Bearer ...",
"content-type": "application/json"
}
}
},
"source": {
"address": "10.0.0.1:5000", // Client address (ip:port)
"certificate": { // Peer certificate (mTLS connections)
"uri_san": "spiffe://cluster.local/ns/default/sa/client",
"dns_san": "client.default.svc.cluster.local",
"subject": "CN=client,O=example",
"sha256_digest": "abc123..."
}
},
"destination": {
"address": "10.0.0.2:443" // Server address (ip:port)
},
"connection": {
"tls_version": "TLSv1.3" // TLS version
}
},
"parsed_path": ["api", "v1", "resource"], // Path segments
"parsed_query": {"key": ["value"]}, // Parsed query parameters
"body": {"field": "value"} // Parsed JSON body (only when with_body: true)
}The body field is only present when with_body is enabled in the configuration
and the request body is valid JSON. Non-JSON bodies result in body being absent.
Metrics
The extension exposes the opa_requests_total counter metric with a decision tag that
tracks authorization outcomes. The possible tag values are:
| Decision | Description |
|---|---|
allowed |
The policy allowed the request. |
denied |
The policy denied the request (or an error occurred with fail-open disabled). |
failopen |
An error occurred during policy evaluation and the request was allowed because fail-open mode is enabled. |
dryrun_allow |
The policy denied the request but it was allowed because dry-run mode is enabled. |
Usage Examples
Basic Authorization
Run the OPA extension with a simple Rego policy that allows or denies requests based on the request path. Create a policy file and reference it in the configuration.
# Create a simple policy file
cat > /tmp/policy.rego << 'EOF'
package envoy.authz
default allow := false
allow if {
input.parsed_path[0] == "public"
}
EOF
# Run the extension
boe run --extension opa --config '{
"policies": [{"file": "/tmp/policy.rego"}]
}'
# Test allowed request
curl http://localhost:10000/public/resource
# => 200 OK (proxied to upstream)
# Test denied request
curl http://localhost:10000/private/resource
# => 403 Forbidden Custom Deny Response
Use a policy that returns a structured object with custom HTTP status, headers, and body when denying a request.
cat > /tmp/policy.rego << 'EOF'
package envoy.authz
default allow := {"allowed": false, "http_status": 401, "body": "Unauthorized"}
allow := {"allowed": true} if {
token := input.attributes.request.http.headers.authorization
startswith(token, "Bearer ")
}
EOF
boe run --extension opa --config '{
"policies": [{"file": "/tmp/policy.rego"}]
}'
# Test with a valid JWT
curl -H "Authorization: Bearer <token>" http://localhost:10000/api/resource
# => 200 OK
# Test without a token
curl http://localhost:10000/api/resource
# => 401 Unauthorized JWT Token Verification
Use a policy that decodes JWT tokens using OPA's built-in
io.jwt.decode function. This example decodes a JWT token,
checks the subject claim, and forwards the user's role as a
request header to the upstream service.
cat > /tmp/policy.rego << 'EOF'
package envoy.authz
import rego.v1
default allow := {"allowed": false, "http_status": 401, "body": "Unauthorized"}
allow := {"allowed": true, "headers": {"x-jwt-role": payload.role}} if {
auth_header := input.attributes.request.http.headers.authorization
startswith(auth_header, "Bearer ")
token := substring(auth_header, 7, -1)
[_, payload, _] := io.jwt.decode(token)
payload.sub == "my-subject"
}
EOF
boe run --extension opa --config '{
"policies": [{"file": "/tmp/policy.rego"}]
}'
# Test with a valid JWT
curl -H "Authorization: Bearer <token>" http://localhost:10000/api/resource
# => 200 OK (upstream receives x-jwt-role header)
# Test without a token
curl http://localhost:10000/api/resource
# => 401 Unauthorized SPIFFE Identity Authorization
Use a policy that authorizes requests based on the client's SPIFFE identity
from an mTLS connection. The SPIFFE ID is available at
input.attributes.source.certificate.uri_san.
cat > /tmp/policy.rego << 'EOF'
package envoy.authz
default allow := false
# Allow requests from trusted workloads
allow if {
spiffe_id := input.attributes.source.certificate.uri_san
startswith(spiffe_id, "spiffe://cluster.local/ns/production/")
}
EOF
boe run --extension opa --config '{
"policies": [{"file": "/tmp/policy.rego"}]
}' Inline Policy Configuration
Instead of loading policies from files, you can also specify inline Rego policies directly in the configuration. This is useful for simple policies or when you want to manage policies outside of the filesystem.
boe run --extension opa --config '{
"policies": [
{"inline": "package envoy.authz\ndefault allow := false\nallow if {\ninput.parsed_path[0] == \"public\"\n }"},
{"file": "/tmp/other_policy.rego"}
]
}' Dry-Run Mode
Enable dry-run mode to log authorization decisions without enforcing them. Useful for testing new policies in production without blocking traffic.
boe run --extension opa --config '{
"policies": [{"file": "/tmp/policy.rego"}],
"dry_run": true
}' Fail-Open Mode
Enable fail-open mode to allow requests through when policy evaluation encounters an error, instead of returning a 500 response. Useful for non-critical authorization checks where availability is prioritized over enforcement.
boe run --extension opa --config '{
"policies": [{"file": "/tmp/policy.rego"}],
"fail_open": true
}' Request Body Inspection
Enable with_body to buffer the request body and include it as
parsed JSON in the OPA input document under input.body. This allows
writing policies that inspect request body content. Only JSON bodies are
supported; non-JSON bodies result in input.body being absent.
Use Envoy's buffer filter or per-route buffer limits to cap body size.
cat > /tmp/policy.rego << 'EOF'
package envoy.authz
import rego.v1
default allow := false
# Allow only requests where the body contains a valid "role" field.
allow if {
input.body.role == "admin"
}
EOF
boe run --extension opa --config '{
"policies": [{"file": "/tmp/policy.rego"}],
"with_body": true
}'
# Test with allowed body
curl -X POST -H "Content-Type: application/json" \
-d '{"role": "admin"}' http://localhost:10000/api/resource
# => 200 OK
# Test with denied body
curl -X POST -H "Content-Type: application/json" \
-d '{"role": "guest"}' http://localhost:10000/api/resource
# => 403 Forbidden