opa
Delegates the authorization decision for each request to an external Open Policy Agent server. The plugin builds an OPA input document describing the request (and, optionally, the matched consumer), POSTs it to <host>/v1/data/<policy>, and enforces the decision under result. Place it early in the request pipeline, before the upstream node.
Configuration
| Key | Type | Default | Description |
|---|---|---|---|
host | string | — | OPA base URL (e.g. http://opa:8181). Required; a trailing / is trimmed. |
policy | string | — | Decision path appended as /v1/data/<policy>. Required; a leading / is trimmed. |
ssl_verify | boolean | true | Verify TLS certificates for https callouts. |
timeout | integer (ms) | 3000 | Whole-call callout deadline (connect + request + response). |
with_consumer | boolean | false | Include a consumer object (built from context.message's consumer.* keys) in the input document. |
with_route | boolean | false | Accepted for config compatibility but a no-op — featherbit has no route object. |
with_service | boolean | false | Accepted for config compatibility but a no-op — featherbit has no service object. |
send_headers_upstream | array of strings | — | OPA-response header names copied onto the request forwarded upstream on allow. A configured name absent from the OPA response removes any client-supplied value. |
- id: authz
type: opa
config:
host: http://opa:8181
policy: example/allow
with_consumer: true
send_headers_upstream: [x-user-id]
ssl_verify: true
timeout: 3000
Behavior
The plugin POSTs the following input document to <host>/v1/data/<policy>:
{
"input": {
"type": "http",
"request": {
"scheme": "http", "method": "GET", "host": "example.com", "port": 8080,
"path": "/api/users", "headers": { "...": "..." }, "query": { "...": "..." }
},
"var": { "remote_addr": "10.0.0.7", "remote_port": 5555, "timestamp": 1710000000 },
"consumer": { "name": "alice", "auth_type": "key-auth" }
}
}
Headers and query parameters collapse to a string when single-valued and to an array when repeated. The consumer object is present only when with_consumer: true and a consumer is attached. The var block omits server_addr / server_port, which featherbit does not track.
The OPA reply's result object determines routing:
allow: true→ the request passes through the success port. Ifsend_headers_upstreamis set, each named header fromresult.headersis copied onto the request forwarded upstream; a named header absent from the OPA response is removed.allow: falseor missing → the request is rejected through the error port with errorOPA_DENIED. OPA-suppliedresult.status_code(orresult.status) sets the response status (default403),result.headersare copied onto the response, andresult.reasonbecomes the response body (objects are JSON-encoded).- Callout failure (timeout / transport error) → rejected with status
403and errorOPA_ERROR(block-by-default). - Unparseable response (not JSON, or missing
result) → rejected with status503and errorOPA_ERROR.