This is an automated email from the ASF dual-hosted git repository.
oscerd pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new ea12ca33bfe7 CAMEL-24740: camel-opa - support batch policy evaluation
(#26679)
ea12ca33bfe7 is described below
commit ea12ca33bfe7383860105ddc453f785dc779ae1c
Author: Andrea Cosentino <[email protected]>
AuthorDate: Fri Sep 25 11:06:45 2026 +0200
CAMEL-24740: camel-opa - support batch policy evaluation (#26679)
* CAMEL-24740: camel-opa - support batch policy evaluation
A route that splits a payload and authorizes each element paid one OPA
round-trip
per element. The producer now takes a batch option: with batch=true and a
List
body it builds one input document per element - each element as the body,
sharing
the exchange's headers and properties - and evaluates them in a single call
through OPA's batch API (OPAClient.evaluateBatch, which the SDK falls back
to
sequential requests for when the server does not implement the endpoint).
The per-element verdicts are reported in CamelOpaBatchDecision, a
List<Boolean>
parallel to the input; CamelOpaDecisionAllow is not set in batch mode. The
header
is cleared on entry and withheld from the OPA input like the other decision
headers.
Fail-closed is per element: an element whose evaluation could not be
reached is
denied, or allowed under failOpen, while the others decide normally - the
batch
is never denied as a whole because one element failed, nor allowed because
most
succeeded. Only a call that fails entirely denies (or, under failOpen,
allows)
every element. That is the part the issue calls out as not mechanical.
Batch is rest-only and rejected with evaluationMode=wasm at startup: it
saves the
per-element HTTP round-trip, which has no meaning for in-process
evaluation. The
wasm evaluator inherits the base evaluator's refusal.
OpaBatchEvaluationTest (mocked OPAClient) covers the parallel verdicts, a
failed
element denied while its neighbours decide, the same element allowed under
failOpen, the List-body requirement, and the wasm rejection.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Signed-off-by: Andrea Cosentino <[email protected]>
* CAMEL-24740: camel-opa - short-circuit an empty batch, set the
policy-path header, and cover whole-batch failure
Addresses review feedback on batch evaluation:
- an empty List body is answered without calling the OPA SDK, whose
behaviour on an empty batch is undefined, so the verdict list is a
deterministic empty list rather than a possible fail-closed error.
- CamelOpaPolicyPath is now set in batch mode too, so observability
tooling reads the same header it does after a single evaluation.
- added tests for the whole-batch failure path, both fail-closed and
under failOpen, and for the empty-batch short-circuit.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Signed-off-by: Andrea Cosentino <[email protected]>
* CAMEL-24740: correct what the batch docs claim about headers and total
failure
Addresses review feedback on #26679.
Two statements did not match the code.
The header paragraph named only CamelOpaDecisionAllow as unset in batch
mode, so a reader took it that everything else survives.
setDecisionHeaders writes three headers and setBatchDecisionHeaders
writes two, so CamelOpaDecision is unset as well. Both are now named,
and CamelOpaPolicyPath is stated positively as set, since tooling may
want to rely on it.
"Only a call that fails entirely ... denies every element" described the
fail-closed whole-batch case as a denial, but that path throws and
leaves no verdict, as failsClosedWhenTheWholeBatchCannotBeEvaluated
asserts. Someone reading it would write code expecting a List of false.
It now says the exchange fails and carries no verdict. The failOpen half
was accurate and keeps its meaning.
Documentation only. The catalog mirror is updated with it.
Co-Authored-By: Claude Opus 5 <[email protected]>
Signed-off-by: Andrea Cosentino <[email protected]>
* CAMEL-24740: regenerate the opa catalog for both new headers
Rebasing onto main brought CAMEL-24738's CamelOpaDecisionFailedOpen
alongside this branch's CamelOpaBatchDecision, and the generated
metadata conflicted because each side had regenerated it with only its
own header.
Taking either side would have dropped a header from the catalog: the
conflicted file listed four, and the component ships five. Regenerated
from the merged OpaConstants instead, and the catalog copy taken from
that output rather than hand-merged.
The two conflicts in OpaPolicyEvaluator were resolved the same way, and
they are the ones that mattered: clearDecisionHeaders now removes both
new headers and isDecisionHeader lists both. Keeping only one would have
left the other attacker-settable on entry and sent it to OPA as input,
which is the bug CAMEL-24754 and CAMEL-24738 exist to prevent.
99 tests green.
Co-Authored-By: Claude Opus 5 <[email protected]>
Signed-off-by: Andrea Cosentino <[email protected]>
* Regen
---------
Signed-off-by: Andrea Cosentino <[email protected]>
Co-authored-by: Claude Opus 4.8 <[email protected]>
Co-authored-by: Guillaume Nodet <[email protected]>
---
.../org/apache/camel/catalog/components/opa.json | 79 ++++-----
.../apache/camel/catalog/docs/opa-component.adoc | 31 ++++
.../component/opa/OpaComponentConfigurer.java | 3 +
.../camel/component/opa/OpaEndpointConfigurer.java | 3 +
.../camel/component/opa/OpaEndpointUriFactory.java | 3 +-
.../org/apache/camel/component/opa/opa.json | 79 ++++-----
.../camel-opa/src/main/docs/opa-component.adoc | 31 ++++
.../camel/component/opa/OpaConfiguration.java | 20 +++
.../apache/camel/component/opa/OpaConstants.java | 7 +
.../apache/camel/component/opa/OpaEndpoint.java | 6 +
.../camel/component/opa/OpaPolicyEvaluator.java | 128 ++++++++++++++-
.../apache/camel/component/opa/OpaProducer.java | 15 +-
.../camel/component/opa/OpaRestEvaluator.java | 26 +++
.../component/opa/OpaBatchEvaluationTest.java | 176 +++++++++++++++++++++
.../component/dsl/OpaComponentBuilderFactory.java | 27 ++++
.../endpoint/dsl/OpaEndpointBuilderFactory.java | 63 ++++++++
16 files changed, 616 insertions(+), 81 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/opa.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/opa.json
index 099541bf5a47..a98f123dc071 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/opa.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/opa.json
@@ -25,52 +25,55 @@
},
"componentProperties": {
"allowKey": { "index": 0, "kind": "property", "displayName": "Allow Key",
"group": "producer", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "defaultValue": "allow", "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "The key to read the allow\/deny verdict from
when the policy returns an object rather than a plain b [...]
- "configuration": { "index": 1, "kind": "property", "displayName":
"Configuration", "group": "producer", "label": "", "required": false, "type":
"object", "javaType": "org.apache.camel.component.opa.OpaConfiguration",
"deprecated": false, "autowired": false, "secret": false, "description": "The
component configuration." },
- "entrypoint": { "index": 2, "kind": "property", "displayName":
"Entrypoint", "group": "producer", "label": "", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "The compiled entrypoint to evaluate in wasm
mode. This is not the same thing as the policy path: an entrypoint is fixed wh
[...]
- "evaluationMode": { "index": 3, "kind": "property", "displayName":
"Evaluation Mode", "group": "producer", "label": "", "required": false, "type":
"enum", "javaType": "java.lang.String", "enum": [ "rest", "wasm" ],
"deprecated": false, "autowired": false, "secret": false, "defaultValue":
"rest", "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "How the policy is evaluated. rest (the
default) calls a running [...]
- "includeBody": { "index": 4, "kind": "property", "displayName": "Include
Body", "group": "producer", "label": "", "required": false, "type": "boolean",
"javaType": "boolean", "deprecated": false, "autowired": false, "secret":
false, "defaultValue": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "Whether to send the message body to OPA as
part of the input document. Disabled by default: bodies can b [...]
- "includeHeaders": { "index": 5, "kind": "property", "displayName":
"Include Headers", "group": "producer", "label": "", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": false, "defaultValue": "*", "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "Comma-separated list of message header names
to send to OPA in the input document. The defau [...]
- "includeProperties": { "index": 6, "kind": "property", "displayName":
"Include Properties", "group": "producer", "label": "", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "Comma-separated list of exchange property
names to send to OPA in the input document, or {code } for all of [...]
- "lazyStartProducer": { "index": 7, "kind": "property", "displayName":
"Lazy Start Producer", "group": "producer", "label": "producer", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": false, "description":
"Whether the producer should be started lazy (on the first message). By
starting lazy you can use this to allow CamelContext and routes to startup in
situations where a producer may otherwise fail [...]
- "policyBundle": { "index": 8, "kind": "property", "displayName": "Policy
Bundle", "group": "producer", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "The WebAssembly policy to evaluate in wasm
mode, as produced by {code opa build -t wasm}. Accepts a {code file:}, {co [...]
- "serverUrl": { "index": 9, "kind": "property", "displayName": "Server
Url", "group": "producer", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "defaultValue": "http:\/\/localhost:8181",
"configurationClass": "org.apache.camel.component.opa.OpaConfiguration",
"configurationField": "configuration", "description": "The base URL of the OPA
server, without the {code \/v1\/data} suffix. The defaul [...]
- "autowiredEnabled": { "index": 10, "kind": "property", "displayName":
"Autowired Enabled", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": true, "description":
"Whether autowiring is enabled. This is used for automatic autowiring options
(the option must be marked as autowired) by looking up in the registry to find
if there is a single instance of matching [...]
- "borrowTimeout": { "index": 11, "kind": "property", "displayName": "Borrow
Timeout", "group": "advanced", "label": "advanced", "required": false, "type":
"duration", "javaType": "long", "deprecated": false, "autowired": false,
"secret": false, "defaultValue": "30000", "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "How long an exchange waits for a free
WebAssembly policy instance in wasm mode before the e [...]
- "connectionTimeout": { "index": 12, "kind": "property", "displayName":
"Connection Timeout", "group": "advanced", "label": "advanced", "required":
false, "type": "duration", "javaType": "long", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": "10000",
"configurationClass": "org.apache.camel.component.opa.OpaConfiguration",
"configurationField": "configuration", "description": "How long to wait for the
connection to the OPA server to be established, in rest mo [...]
- "opaClient": { "index": 13, "kind": "property", "displayName": "Opa
Client", "group": "advanced", "label": "advanced", "required": false, "type":
"object", "javaType": "com.styra.opa.OPAClient", "deprecated": false,
"deprecationNote": "", "autowired": true, "secret": false,
"configurationClass": "org.apache.camel.component.opa.OpaConfiguration",
"configurationField": "configuration", "description": "An existing OPAClient to
use. When set, serverUrl and bearerToken are ignored." },
- "poolSize": { "index": 14, "kind": "property", "displayName": "Pool Size",
"group": "advanced", "label": "advanced", "required": false, "type": "integer",
"javaType": "int", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": 8, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "How many WebAssembly policy instances to pool
in wasm mode. An instance carries mutable state and is not thre [...]
- "requestTimeout": { "index": 15, "kind": "property", "displayName":
"Request Timeout", "group": "advanced", "label": "advanced", "required": false,
"type": "duration", "javaType": "long", "deprecated": false, "autowired":
false, "secret": false, "defaultValue": "30000", "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "How long to wait for the decision once
connected, in rest mode. A request that times out [...]
- "healthCheckConsumerEnabled": { "index": 16, "kind": "property",
"displayName": "Health Check Consumer Enabled", "group": "health", "label":
"health", "required": false, "type": "boolean", "javaType": "boolean",
"deprecated": false, "autowired": false, "secret": false, "defaultValue": true,
"description": "Used for enabling or disabling all consumer based health checks
from this component" },
- "healthCheckProducerEnabled": { "index": 17, "kind": "property",
"displayName": "Health Check Producer Enabled", "group": "health", "label":
"health", "required": false, "type": "boolean", "javaType": "boolean",
"deprecated": false, "autowired": false, "secret": false, "defaultValue": true,
"description": "Used for enabling or disabling all producer based health checks
from this component. Notice: Camel has by default disabled all producer based
health-checks. You can turn on produce [...]
- "bearerToken": { "index": 18, "kind": "property", "displayName": "Bearer
Token", "group": "security", "label": "security", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": true, "security": "secret", "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "Bearer token sent to the OPA server in the
Authorization header, for an OPA instance that [...]
- "failOpen": { "index": 19, "kind": "property", "displayName": "Fail Open",
"group": "security", "label": "security", "required": false, "type": "boolean",
"javaType": "boolean", "deprecated": false, "autowired": false, "secret":
false, "security": "insecure:dev", "defaultValue": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "Whether to allow the exchange to proceed when
the policy cannot be evalua [...]
- "sslContextParameters": { "index": 20, "kind": "property", "displayName":
"Ssl Context Parameters", "group": "security", "label": "security", "required":
false, "type": "object", "javaType":
"org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false,
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "TLS configuration for the connection to the
OPA server in [...]
- "useGlobalSslContextParameters": { "index": 21, "kind": "property",
"displayName": "Use Global Ssl Context Parameters", "group": "security",
"label": "security", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Enable usage of global SSL context
parameters." }
+ "batch": { "index": 1, "kind": "property", "displayName": "Batch",
"group": "producer", "label": "producer", "required": false, "type": "boolean",
"javaType": "boolean", "deprecated": false, "autowired": false, "secret":
false, "defaultValue": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "Authorize a whole collection in one call. When
enabled the producer expects a List body, evaluates one input [...]
+ "configuration": { "index": 2, "kind": "property", "displayName":
"Configuration", "group": "producer", "label": "", "required": false, "type":
"object", "javaType": "org.apache.camel.component.opa.OpaConfiguration",
"deprecated": false, "autowired": false, "secret": false, "description": "The
component configuration." },
+ "entrypoint": { "index": 3, "kind": "property", "displayName":
"Entrypoint", "group": "producer", "label": "", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "The compiled entrypoint to evaluate in wasm
mode. This is not the same thing as the policy path: an entrypoint is fixed wh
[...]
+ "evaluationMode": { "index": 4, "kind": "property", "displayName":
"Evaluation Mode", "group": "producer", "label": "", "required": false, "type":
"enum", "javaType": "java.lang.String", "enum": [ "rest", "wasm" ],
"deprecated": false, "autowired": false, "secret": false, "defaultValue":
"rest", "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "How the policy is evaluated. rest (the
default) calls a running [...]
+ "includeBody": { "index": 5, "kind": "property", "displayName": "Include
Body", "group": "producer", "label": "", "required": false, "type": "boolean",
"javaType": "boolean", "deprecated": false, "autowired": false, "secret":
false, "defaultValue": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "Whether to send the message body to OPA as
part of the input document. Disabled by default: bodies can b [...]
+ "includeHeaders": { "index": 6, "kind": "property", "displayName":
"Include Headers", "group": "producer", "label": "", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": false, "defaultValue": "*", "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "Comma-separated list of message header names
to send to OPA in the input document. The defau [...]
+ "includeProperties": { "index": 7, "kind": "property", "displayName":
"Include Properties", "group": "producer", "label": "", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "Comma-separated list of exchange property
names to send to OPA in the input document, or {code } for all of [...]
+ "lazyStartProducer": { "index": 8, "kind": "property", "displayName":
"Lazy Start Producer", "group": "producer", "label": "producer", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": false, "description":
"Whether the producer should be started lazy (on the first message). By
starting lazy you can use this to allow CamelContext and routes to startup in
situations where a producer may otherwise fail [...]
+ "policyBundle": { "index": 9, "kind": "property", "displayName": "Policy
Bundle", "group": "producer", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "The WebAssembly policy to evaluate in wasm
mode, as produced by {code opa build -t wasm}. Accepts a {code file:}, {co [...]
+ "serverUrl": { "index": 10, "kind": "property", "displayName": "Server
Url", "group": "producer", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "defaultValue": "http:\/\/localhost:8181",
"configurationClass": "org.apache.camel.component.opa.OpaConfiguration",
"configurationField": "configuration", "description": "The base URL of the OPA
server, without the {code \/v1\/data} suffix. The defau [...]
+ "autowiredEnabled": { "index": 11, "kind": "property", "displayName":
"Autowired Enabled", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": true, "description":
"Whether autowiring is enabled. This is used for automatic autowiring options
(the option must be marked as autowired) by looking up in the registry to find
if there is a single instance of matching [...]
+ "borrowTimeout": { "index": 12, "kind": "property", "displayName": "Borrow
Timeout", "group": "advanced", "label": "advanced", "required": false, "type":
"duration", "javaType": "long", "deprecated": false, "autowired": false,
"secret": false, "defaultValue": "30000", "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "How long an exchange waits for a free
WebAssembly policy instance in wasm mode before the e [...]
+ "connectionTimeout": { "index": 13, "kind": "property", "displayName":
"Connection Timeout", "group": "advanced", "label": "advanced", "required":
false, "type": "duration", "javaType": "long", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": "10000",
"configurationClass": "org.apache.camel.component.opa.OpaConfiguration",
"configurationField": "configuration", "description": "How long to wait for the
connection to the OPA server to be established, in rest mo [...]
+ "opaClient": { "index": 14, "kind": "property", "displayName": "Opa
Client", "group": "advanced", "label": "advanced", "required": false, "type":
"object", "javaType": "com.styra.opa.OPAClient", "deprecated": false,
"deprecationNote": "", "autowired": true, "secret": false,
"configurationClass": "org.apache.camel.component.opa.OpaConfiguration",
"configurationField": "configuration", "description": "An existing OPAClient to
use. When set, serverUrl and bearerToken are ignored." },
+ "poolSize": { "index": 15, "kind": "property", "displayName": "Pool Size",
"group": "advanced", "label": "advanced", "required": false, "type": "integer",
"javaType": "int", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": 8, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "How many WebAssembly policy instances to pool
in wasm mode. An instance carries mutable state and is not thre [...]
+ "requestTimeout": { "index": 16, "kind": "property", "displayName":
"Request Timeout", "group": "advanced", "label": "advanced", "required": false,
"type": "duration", "javaType": "long", "deprecated": false, "autowired":
false, "secret": false, "defaultValue": "30000", "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "How long to wait for the decision once
connected, in rest mode. A request that times out [...]
+ "healthCheckConsumerEnabled": { "index": 17, "kind": "property",
"displayName": "Health Check Consumer Enabled", "group": "health", "label":
"health", "required": false, "type": "boolean", "javaType": "boolean",
"deprecated": false, "autowired": false, "secret": false, "defaultValue": true,
"description": "Used for enabling or disabling all consumer based health checks
from this component" },
+ "healthCheckProducerEnabled": { "index": 18, "kind": "property",
"displayName": "Health Check Producer Enabled", "group": "health", "label":
"health", "required": false, "type": "boolean", "javaType": "boolean",
"deprecated": false, "autowired": false, "secret": false, "defaultValue": true,
"description": "Used for enabling or disabling all producer based health checks
from this component. Notice: Camel has by default disabled all producer based
health-checks. You can turn on produce [...]
+ "bearerToken": { "index": 19, "kind": "property", "displayName": "Bearer
Token", "group": "security", "label": "security", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": true, "security": "secret", "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "Bearer token sent to the OPA server in the
Authorization header, for an OPA instance that [...]
+ "failOpen": { "index": 20, "kind": "property", "displayName": "Fail Open",
"group": "security", "label": "security", "required": false, "type": "boolean",
"javaType": "boolean", "deprecated": false, "autowired": false, "secret":
false, "security": "insecure:dev", "defaultValue": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "Whether to allow the exchange to proceed when
the policy cannot be evalua [...]
+ "sslContextParameters": { "index": 21, "kind": "property", "displayName":
"Ssl Context Parameters", "group": "security", "label": "security", "required":
false, "type": "object", "javaType":
"org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false,
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "TLS configuration for the connection to the
OPA server in [...]
+ "useGlobalSslContextParameters": { "index": 22, "kind": "property",
"displayName": "Use Global Ssl Context Parameters", "group": "security",
"label": "security", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Enable usage of global SSL context
parameters." }
},
"headers": {
"CamelOpaDecisionAllow": { "index": 0, "kind": "header", "displayName":
"", "group": "producer", "label": "producer", "required": false, "javaType":
"Boolean", "deprecated": false, "deprecationNote": "", "autowired": false,
"secret": false, "description": "The allow\/deny verdict of the policy
evaluation. Always overwritten by the component, so a value set by an inbound
message never survives into the route.", "constantName":
"org.apache.camel.component.opa.OpaConstants#DECISION_ALLOW" },
"CamelOpaDecision": { "index": 1, "kind": "header", "displayName": "",
"group": "producer", "label": "producer", "required": false, "javaType":
"Object", "deprecated": false, "deprecationNote": "", "autowired": false,
"secret": false, "description": "The raw decision document returned by OPA.
Useful for policies that return more than a boolean, such as obligations, row
filters or deny reasons.", "constantName":
"org.apache.camel.component.opa.OpaConstants#DECISION" },
"CamelOpaPolicyPath": { "index": 2, "kind": "header", "displayName": "",
"group": "producer", "label": "producer", "required": false, "javaType":
"String", "deprecated": false, "deprecationNote": "", "autowired": false,
"secret": false, "description": "The policy path that was evaluated. Set by the
component for observability; it is not read as an input and cannot be used to
select a different policy.", "constantName":
"org.apache.camel.component.opa.OpaConstants#POLICY_PATH" },
- "CamelOpaDecisionFailedOpen": { "index": 3, "kind": "header",
"displayName": "", "group": "producer", "label": "producer", "required": false,
"javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Set to true only when the exchange
proceeded because failOpen is enabled and the policy could not be evaluated -
nothing authorized it. Absent on every decision an actual policy made, so a
route or an audit trail can tell the [...]
+ "CamelOpaDecisionFailedOpen": { "index": 3, "kind": "header",
"displayName": "", "group": "producer", "label": "producer", "required": false,
"javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Set to true only when the exchange
proceeded because failOpen is enabled and the policy could not be evaluated -
nothing authorized it. Absent on every decision an actual policy made, so a
route or an audit trail can tell the [...]
+ "CamelOpaBatchDecision": { "index": 4, "kind": "header", "displayName":
"", "group": "producer", "label": "producer", "required": false, "javaType":
"java.util.List<Boolean>", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "The per-element
allow\/deny verdicts of a batch evaluation (batch=true), as a List of Boolean
parallel to the List body. Always overwritten by the component. An element
whose evaluation could not be reached is denie [...]
},
"properties": {
"policyPath": { "index": 0, "kind": "path", "displayName": "Policy Path",
"group": "producer", "label": "", "required": true, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Path of the Rego rule head
to evaluate, relative to the OPA data document. For a rule named allow in a
policy declaring package authz.orders, this is authz\/orders\/allow. The path
is taken from the endpoint only: i [...]
"allowKey": { "index": 1, "kind": "parameter", "displayName": "Allow Key",
"group": "producer", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "defaultValue": "allow", "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "The key to read the allow\/deny verdict from
when the policy returns an object rather than a plain [...]
- "entrypoint": { "index": 2, "kind": "parameter", "displayName":
"Entrypoint", "group": "producer", "label": "", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "The compiled entrypoint to evaluate in wasm
mode. This is not the same thing as the policy path: an entrypoint is fixed w
[...]
- "evaluationMode": { "index": 3, "kind": "parameter", "displayName":
"Evaluation Mode", "group": "producer", "label": "", "required": false, "type":
"enum", "javaType": "java.lang.String", "enum": [ "rest", "wasm" ],
"deprecated": false, "autowired": false, "secret": false, "defaultValue":
"rest", "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "How the policy is evaluated. rest (the
default) calls a runnin [...]
- "includeBody": { "index": 4, "kind": "parameter", "displayName": "Include
Body", "group": "producer", "label": "", "required": false, "type": "boolean",
"javaType": "boolean", "deprecated": false, "autowired": false, "secret":
false, "defaultValue": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "Whether to send the message body to OPA as
part of the input document. Disabled by default: bodies can [...]
- "includeHeaders": { "index": 5, "kind": "parameter", "displayName":
"Include Headers", "group": "producer", "label": "", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": false, "defaultValue": "*", "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "Comma-separated list of message header names
to send to OPA in the input document. The defa [...]
- "includeProperties": { "index": 6, "kind": "parameter", "displayName":
"Include Properties", "group": "producer", "label": "", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "Comma-separated list of exchange property
names to send to OPA in the input document, or {code } for all o [...]
- "policyBundle": { "index": 7, "kind": "parameter", "displayName": "Policy
Bundle", "group": "producer", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "The WebAssembly policy to evaluate in wasm
mode, as produced by {code opa build -t wasm}. Accepts a {code file:}, {c [...]
- "serverUrl": { "index": 8, "kind": "parameter", "displayName": "Server
Url", "group": "producer", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "defaultValue": "http:\/\/localhost:8181",
"configurationClass": "org.apache.camel.component.opa.OpaConfiguration",
"configurationField": "configuration", "description": "The base URL of the OPA
server, without the {code \/v1\/data} suffix. The defau [...]
- "lazyStartProducer": { "index": 9, "kind": "parameter", "displayName":
"Lazy Start Producer", "group": "producer (advanced)", "label":
"producer,advanced", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Whether the producer should be started
lazy (on the first message). By starting lazy you can use this to allow
CamelContext and routes to startup in situations where a produc [...]
- "borrowTimeout": { "index": 10, "kind": "parameter", "displayName":
"Borrow Timeout", "group": "advanced", "label": "advanced", "required": false,
"type": "duration", "javaType": "long", "deprecated": false, "autowired":
false, "secret": false, "defaultValue": "30000", "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "How long an exchange waits for a free
WebAssembly policy instance in wasm mode before the [...]
- "connectionTimeout": { "index": 11, "kind": "parameter", "displayName":
"Connection Timeout", "group": "advanced", "label": "advanced", "required":
false, "type": "duration", "javaType": "long", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": "10000",
"configurationClass": "org.apache.camel.component.opa.OpaConfiguration",
"configurationField": "configuration", "description": "How long to wait for the
connection to the OPA server to be established, in rest m [...]
- "opaClient": { "index": 12, "kind": "parameter", "displayName": "Opa
Client", "group": "advanced", "label": "advanced", "required": false, "type":
"object", "javaType": "com.styra.opa.OPAClient", "deprecated": false,
"deprecationNote": "", "autowired": true, "secret": false,
"configurationClass": "org.apache.camel.component.opa.OpaConfiguration",
"configurationField": "configuration", "description": "An existing OPAClient to
use. When set, serverUrl and bearerToken are ignored." },
- "poolSize": { "index": 13, "kind": "parameter", "displayName": "Pool
Size", "group": "advanced", "label": "advanced", "required": false, "type":
"integer", "javaType": "int", "deprecated": false, "autowired": false,
"secret": false, "defaultValue": 8, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "How many WebAssembly policy instances to pool
in wasm mode. An instance carries mutable state and is not thr [...]
- "requestTimeout": { "index": 14, "kind": "parameter", "displayName":
"Request Timeout", "group": "advanced", "label": "advanced", "required": false,
"type": "duration", "javaType": "long", "deprecated": false, "autowired":
false, "secret": false, "defaultValue": "30000", "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "How long to wait for the decision once
connected, in rest mode. A request that times out [...]
- "bearerToken": { "index": 15, "kind": "parameter", "displayName": "Bearer
Token", "group": "security", "label": "security", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": true, "security": "secret", "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "Bearer token sent to the OPA server in the
Authorization header, for an OPA instance tha [...]
- "failOpen": { "index": 16, "kind": "parameter", "displayName": "Fail
Open", "group": "security", "label": "security", "required": false, "type":
"boolean", "javaType": "boolean", "deprecated": false, "autowired": false,
"secret": false, "security": "insecure:dev", "defaultValue": false,
"configurationClass": "org.apache.camel.component.opa.OpaConfiguration",
"configurationField": "configuration", "description": "Whether to allow the
exchange to proceed when the policy cannot be evalu [...]
- "sslContextParameters": { "index": 17, "kind": "parameter", "displayName":
"Ssl Context Parameters", "group": "security", "label": "security", "required":
false, "type": "object", "javaType":
"org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false,
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "TLS configuration for the connection to the
OPA server i [...]
+ "batch": { "index": 2, "kind": "parameter", "displayName": "Batch",
"group": "producer", "label": "producer", "required": false, "type": "boolean",
"javaType": "boolean", "deprecated": false, "autowired": false, "secret":
false, "defaultValue": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "Authorize a whole collection in one call. When
enabled the producer expects a List body, evaluates one input [...]
+ "entrypoint": { "index": 3, "kind": "parameter", "displayName":
"Entrypoint", "group": "producer", "label": "", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "The compiled entrypoint to evaluate in wasm
mode. This is not the same thing as the policy path: an entrypoint is fixed w
[...]
+ "evaluationMode": { "index": 4, "kind": "parameter", "displayName":
"Evaluation Mode", "group": "producer", "label": "", "required": false, "type":
"enum", "javaType": "java.lang.String", "enum": [ "rest", "wasm" ],
"deprecated": false, "autowired": false, "secret": false, "defaultValue":
"rest", "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "How the policy is evaluated. rest (the
default) calls a runnin [...]
+ "includeBody": { "index": 5, "kind": "parameter", "displayName": "Include
Body", "group": "producer", "label": "", "required": false, "type": "boolean",
"javaType": "boolean", "deprecated": false, "autowired": false, "secret":
false, "defaultValue": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "Whether to send the message body to OPA as
part of the input document. Disabled by default: bodies can [...]
+ "includeHeaders": { "index": 6, "kind": "parameter", "displayName":
"Include Headers", "group": "producer", "label": "", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": false, "defaultValue": "*", "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "Comma-separated list of message header names
to send to OPA in the input document. The defa [...]
+ "includeProperties": { "index": 7, "kind": "parameter", "displayName":
"Include Properties", "group": "producer", "label": "", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "Comma-separated list of exchange property
names to send to OPA in the input document, or {code } for all o [...]
+ "policyBundle": { "index": 8, "kind": "parameter", "displayName": "Policy
Bundle", "group": "producer", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "The WebAssembly policy to evaluate in wasm
mode, as produced by {code opa build -t wasm}. Accepts a {code file:}, {c [...]
+ "serverUrl": { "index": 9, "kind": "parameter", "displayName": "Server
Url", "group": "producer", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "defaultValue": "http:\/\/localhost:8181",
"configurationClass": "org.apache.camel.component.opa.OpaConfiguration",
"configurationField": "configuration", "description": "The base URL of the OPA
server, without the {code \/v1\/data} suffix. The defau [...]
+ "lazyStartProducer": { "index": 10, "kind": "parameter", "displayName":
"Lazy Start Producer", "group": "producer (advanced)", "label":
"producer,advanced", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Whether the producer should be started
lazy (on the first message). By starting lazy you can use this to allow
CamelContext and routes to startup in situations where a produ [...]
+ "borrowTimeout": { "index": 11, "kind": "parameter", "displayName":
"Borrow Timeout", "group": "advanced", "label": "advanced", "required": false,
"type": "duration", "javaType": "long", "deprecated": false, "autowired":
false, "secret": false, "defaultValue": "30000", "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "How long an exchange waits for a free
WebAssembly policy instance in wasm mode before the [...]
+ "connectionTimeout": { "index": 12, "kind": "parameter", "displayName":
"Connection Timeout", "group": "advanced", "label": "advanced", "required":
false, "type": "duration", "javaType": "long", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": "10000",
"configurationClass": "org.apache.camel.component.opa.OpaConfiguration",
"configurationField": "configuration", "description": "How long to wait for the
connection to the OPA server to be established, in rest m [...]
+ "opaClient": { "index": 13, "kind": "parameter", "displayName": "Opa
Client", "group": "advanced", "label": "advanced", "required": false, "type":
"object", "javaType": "com.styra.opa.OPAClient", "deprecated": false,
"deprecationNote": "", "autowired": true, "secret": false,
"configurationClass": "org.apache.camel.component.opa.OpaConfiguration",
"configurationField": "configuration", "description": "An existing OPAClient to
use. When set, serverUrl and bearerToken are ignored." },
+ "poolSize": { "index": 14, "kind": "parameter", "displayName": "Pool
Size", "group": "advanced", "label": "advanced", "required": false, "type":
"integer", "javaType": "int", "deprecated": false, "autowired": false,
"secret": false, "defaultValue": 8, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "How many WebAssembly policy instances to pool
in wasm mode. An instance carries mutable state and is not thr [...]
+ "requestTimeout": { "index": 15, "kind": "parameter", "displayName":
"Request Timeout", "group": "advanced", "label": "advanced", "required": false,
"type": "duration", "javaType": "long", "deprecated": false, "autowired":
false, "secret": false, "defaultValue": "30000", "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "How long to wait for the decision once
connected, in rest mode. A request that times out [...]
+ "bearerToken": { "index": 16, "kind": "parameter", "displayName": "Bearer
Token", "group": "security", "label": "security", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": true, "security": "secret", "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "Bearer token sent to the OPA server in the
Authorization header, for an OPA instance tha [...]
+ "failOpen": { "index": 17, "kind": "parameter", "displayName": "Fail
Open", "group": "security", "label": "security", "required": false, "type":
"boolean", "javaType": "boolean", "deprecated": false, "autowired": false,
"secret": false, "security": "insecure:dev", "defaultValue": false,
"configurationClass": "org.apache.camel.component.opa.OpaConfiguration",
"configurationField": "configuration", "description": "Whether to allow the
exchange to proceed when the policy cannot be evalu [...]
+ "sslContextParameters": { "index": 18, "kind": "parameter", "displayName":
"Ssl Context Parameters", "group": "security", "label": "security", "required":
false, "type": "object", "javaType":
"org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false,
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "TLS configuration for the connection to the
OPA server i [...]
}
}
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/opa-component.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/opa-component.adoc
index d674ffa40aed..9af5c30402db 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/opa-component.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/opa-component.adoc
@@ -180,6 +180,37 @@ boolean verdict read out of it:
Both headers are written on every evaluation, so a verdict set by an inbound
message never survives into the
route.
+== Batch evaluation
+
+A route that splits a payload and authorizes each element pays one OPA
round-trip per element. Set `batch=true` and
+hand the producer a `List` instead: it builds one input document per element -
each element as the `body`, sharing
+the exchange's headers and properties - and evaluates them in a single call
through OPA's batch API (the SDK falls
+back to sequential requests when the server does not implement it).
+
+[source,java]
+------------------------------------------------------------
+from("direct:orders")
+ // the body is a List of orders to authorize
+ .to("opa:authz/orders/allow?batch=true")
+ // CamelOpaBatchDecision is now a List<Boolean> parallel to the body
+ .process(dropTheDeniedOrders);
+------------------------------------------------------------
+
+The per-element verdicts arrive in `CamelOpaBatchDecision`, a `List<Boolean>`
parallel to the input list: element
+_i_ is allowed when entry _i_ is `true`. Neither `CamelOpaDecisionAllow` nor
`CamelOpaDecision` is set in batch
+mode - there is no single verdict, and no single decision document - and both
are cleared on entry like the other
+decision headers, so a value an inbound message supplied never survives.
`CamelOpaPolicyPath` *is* set, to the same
+value a single evaluation records, so tooling can read it either way.
+
+Fail-closed applies *per element*: an element whose evaluation could not be
reached is denied (`false`), or allowed
+when `failOpen` is set, while every other element decides normally. The batch
is never denied as a whole because
+one element failed, nor allowed because most of it succeeded. A call that
fails entirely - the server could not be
+reached at all - fails the exchange rather than denying each element: it
carries no verdict at all, not a list of
+`false`. Under `failOpen` that same failure allows every element instead.
+
+`batch` requires `evaluationMode=rest`: it saves the per-element HTTP
round-trip, which has no meaning for in-process
+`wasm` evaluation, and the endpoint rejects the combination at startup.
+
[#authorizing-an-identity]
== Authorizing an identity
diff --git
a/components/camel-opa/src/generated/java/org/apache/camel/component/opa/OpaComponentConfigurer.java
b/components/camel-opa/src/generated/java/org/apache/camel/component/opa/OpaComponentConfigurer.java
index abaee2681400..c548f3b42415 100644
---
a/components/camel-opa/src/generated/java/org/apache/camel/component/opa/OpaComponentConfigurer.java
+++
b/components/camel-opa/src/generated/java/org/apache/camel/component/opa/OpaComponentConfigurer.java
@@ -34,6 +34,7 @@ public class OpaComponentConfigurer extends
PropertyConfigurerSupport implements
case "allowKey":
getOrCreateConfiguration(target).setAllowKey(property(camelContext,
java.lang.String.class, value)); return true;
case "autowiredenabled":
case "autowiredEnabled":
target.setAutowiredEnabled(property(camelContext, boolean.class, value));
return true;
+ case "batch":
getOrCreateConfiguration(target).setBatch(property(camelContext, boolean.class,
value)); return true;
case "bearertoken":
case "bearerToken":
getOrCreateConfiguration(target).setBearerToken(property(camelContext,
java.lang.String.class, value)); return true;
case "borrowtimeout":
@@ -88,6 +89,7 @@ public class OpaComponentConfigurer extends
PropertyConfigurerSupport implements
case "allowKey": return java.lang.String.class;
case "autowiredenabled":
case "autowiredEnabled": return boolean.class;
+ case "batch": return boolean.class;
case "bearertoken":
case "bearerToken": return java.lang.String.class;
case "borrowtimeout":
@@ -138,6 +140,7 @@ public class OpaComponentConfigurer extends
PropertyConfigurerSupport implements
case "allowKey": return getOrCreateConfiguration(target).getAllowKey();
case "autowiredenabled":
case "autowiredEnabled": return target.isAutowiredEnabled();
+ case "batch": return getOrCreateConfiguration(target).isBatch();
case "bearertoken":
case "bearerToken": return
getOrCreateConfiguration(target).getBearerToken();
case "borrowtimeout":
diff --git
a/components/camel-opa/src/generated/java/org/apache/camel/component/opa/OpaEndpointConfigurer.java
b/components/camel-opa/src/generated/java/org/apache/camel/component/opa/OpaEndpointConfigurer.java
index 469fa36c4faa..14aa10b613ce 100644
---
a/components/camel-opa/src/generated/java/org/apache/camel/component/opa/OpaEndpointConfigurer.java
+++
b/components/camel-opa/src/generated/java/org/apache/camel/component/opa/OpaEndpointConfigurer.java
@@ -25,6 +25,7 @@ public class OpaEndpointConfigurer extends
PropertyConfigurerSupport implements
switch (ignoreCase ? name.toLowerCase() : name) {
case "allowkey":
case "allowKey":
target.getConfiguration().setAllowKey(property(camelContext,
java.lang.String.class, value)); return true;
+ case "batch":
target.getConfiguration().setBatch(property(camelContext, boolean.class,
value)); return true;
case "bearertoken":
case "bearerToken":
target.getConfiguration().setBearerToken(property(camelContext,
java.lang.String.class, value)); return true;
case "borrowtimeout":
@@ -70,6 +71,7 @@ public class OpaEndpointConfigurer extends
PropertyConfigurerSupport implements
switch (ignoreCase ? name.toLowerCase() : name) {
case "allowkey":
case "allowKey": return java.lang.String.class;
+ case "batch": return boolean.class;
case "bearertoken":
case "bearerToken": return java.lang.String.class;
case "borrowtimeout":
@@ -111,6 +113,7 @@ public class OpaEndpointConfigurer extends
PropertyConfigurerSupport implements
switch (ignoreCase ? name.toLowerCase() : name) {
case "allowkey":
case "allowKey": return target.getConfiguration().getAllowKey();
+ case "batch": return target.getConfiguration().isBatch();
case "bearertoken":
case "bearerToken": return target.getConfiguration().getBearerToken();
case "borrowtimeout":
diff --git
a/components/camel-opa/src/generated/java/org/apache/camel/component/opa/OpaEndpointUriFactory.java
b/components/camel-opa/src/generated/java/org/apache/camel/component/opa/OpaEndpointUriFactory.java
index b18d621d92b8..f1e9bc602ef0 100644
---
a/components/camel-opa/src/generated/java/org/apache/camel/component/opa/OpaEndpointUriFactory.java
+++
b/components/camel-opa/src/generated/java/org/apache/camel/component/opa/OpaEndpointUriFactory.java
@@ -24,8 +24,9 @@ public class OpaEndpointUriFactory extends
org.apache.camel.support.component.En
private static final Set<String> ENDPOINT_IDENTITY_PROPERTY_NAMES;
private static final Map<String, String> MULTI_VALUE_PREFIXES;
static {
- Set<String> props = new HashSet<>(18);
+ Set<String> props = new HashSet<>(19);
props.add("allowKey");
+ props.add("batch");
props.add("bearerToken");
props.add("borrowTimeout");
props.add("connectionTimeout");
diff --git
a/components/camel-opa/src/generated/resources/META-INF/org/apache/camel/component/opa/opa.json
b/components/camel-opa/src/generated/resources/META-INF/org/apache/camel/component/opa/opa.json
index 099541bf5a47..a98f123dc071 100644
---
a/components/camel-opa/src/generated/resources/META-INF/org/apache/camel/component/opa/opa.json
+++
b/components/camel-opa/src/generated/resources/META-INF/org/apache/camel/component/opa/opa.json
@@ -25,52 +25,55 @@
},
"componentProperties": {
"allowKey": { "index": 0, "kind": "property", "displayName": "Allow Key",
"group": "producer", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "defaultValue": "allow", "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "The key to read the allow\/deny verdict from
when the policy returns an object rather than a plain b [...]
- "configuration": { "index": 1, "kind": "property", "displayName":
"Configuration", "group": "producer", "label": "", "required": false, "type":
"object", "javaType": "org.apache.camel.component.opa.OpaConfiguration",
"deprecated": false, "autowired": false, "secret": false, "description": "The
component configuration." },
- "entrypoint": { "index": 2, "kind": "property", "displayName":
"Entrypoint", "group": "producer", "label": "", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "The compiled entrypoint to evaluate in wasm
mode. This is not the same thing as the policy path: an entrypoint is fixed wh
[...]
- "evaluationMode": { "index": 3, "kind": "property", "displayName":
"Evaluation Mode", "group": "producer", "label": "", "required": false, "type":
"enum", "javaType": "java.lang.String", "enum": [ "rest", "wasm" ],
"deprecated": false, "autowired": false, "secret": false, "defaultValue":
"rest", "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "How the policy is evaluated. rest (the
default) calls a running [...]
- "includeBody": { "index": 4, "kind": "property", "displayName": "Include
Body", "group": "producer", "label": "", "required": false, "type": "boolean",
"javaType": "boolean", "deprecated": false, "autowired": false, "secret":
false, "defaultValue": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "Whether to send the message body to OPA as
part of the input document. Disabled by default: bodies can b [...]
- "includeHeaders": { "index": 5, "kind": "property", "displayName":
"Include Headers", "group": "producer", "label": "", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": false, "defaultValue": "*", "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "Comma-separated list of message header names
to send to OPA in the input document. The defau [...]
- "includeProperties": { "index": 6, "kind": "property", "displayName":
"Include Properties", "group": "producer", "label": "", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "Comma-separated list of exchange property
names to send to OPA in the input document, or {code } for all of [...]
- "lazyStartProducer": { "index": 7, "kind": "property", "displayName":
"Lazy Start Producer", "group": "producer", "label": "producer", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": false, "description":
"Whether the producer should be started lazy (on the first message). By
starting lazy you can use this to allow CamelContext and routes to startup in
situations where a producer may otherwise fail [...]
- "policyBundle": { "index": 8, "kind": "property", "displayName": "Policy
Bundle", "group": "producer", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "The WebAssembly policy to evaluate in wasm
mode, as produced by {code opa build -t wasm}. Accepts a {code file:}, {co [...]
- "serverUrl": { "index": 9, "kind": "property", "displayName": "Server
Url", "group": "producer", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "defaultValue": "http:\/\/localhost:8181",
"configurationClass": "org.apache.camel.component.opa.OpaConfiguration",
"configurationField": "configuration", "description": "The base URL of the OPA
server, without the {code \/v1\/data} suffix. The defaul [...]
- "autowiredEnabled": { "index": 10, "kind": "property", "displayName":
"Autowired Enabled", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": true, "description":
"Whether autowiring is enabled. This is used for automatic autowiring options
(the option must be marked as autowired) by looking up in the registry to find
if there is a single instance of matching [...]
- "borrowTimeout": { "index": 11, "kind": "property", "displayName": "Borrow
Timeout", "group": "advanced", "label": "advanced", "required": false, "type":
"duration", "javaType": "long", "deprecated": false, "autowired": false,
"secret": false, "defaultValue": "30000", "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "How long an exchange waits for a free
WebAssembly policy instance in wasm mode before the e [...]
- "connectionTimeout": { "index": 12, "kind": "property", "displayName":
"Connection Timeout", "group": "advanced", "label": "advanced", "required":
false, "type": "duration", "javaType": "long", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": "10000",
"configurationClass": "org.apache.camel.component.opa.OpaConfiguration",
"configurationField": "configuration", "description": "How long to wait for the
connection to the OPA server to be established, in rest mo [...]
- "opaClient": { "index": 13, "kind": "property", "displayName": "Opa
Client", "group": "advanced", "label": "advanced", "required": false, "type":
"object", "javaType": "com.styra.opa.OPAClient", "deprecated": false,
"deprecationNote": "", "autowired": true, "secret": false,
"configurationClass": "org.apache.camel.component.opa.OpaConfiguration",
"configurationField": "configuration", "description": "An existing OPAClient to
use. When set, serverUrl and bearerToken are ignored." },
- "poolSize": { "index": 14, "kind": "property", "displayName": "Pool Size",
"group": "advanced", "label": "advanced", "required": false, "type": "integer",
"javaType": "int", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": 8, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "How many WebAssembly policy instances to pool
in wasm mode. An instance carries mutable state and is not thre [...]
- "requestTimeout": { "index": 15, "kind": "property", "displayName":
"Request Timeout", "group": "advanced", "label": "advanced", "required": false,
"type": "duration", "javaType": "long", "deprecated": false, "autowired":
false, "secret": false, "defaultValue": "30000", "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "How long to wait for the decision once
connected, in rest mode. A request that times out [...]
- "healthCheckConsumerEnabled": { "index": 16, "kind": "property",
"displayName": "Health Check Consumer Enabled", "group": "health", "label":
"health", "required": false, "type": "boolean", "javaType": "boolean",
"deprecated": false, "autowired": false, "secret": false, "defaultValue": true,
"description": "Used for enabling or disabling all consumer based health checks
from this component" },
- "healthCheckProducerEnabled": { "index": 17, "kind": "property",
"displayName": "Health Check Producer Enabled", "group": "health", "label":
"health", "required": false, "type": "boolean", "javaType": "boolean",
"deprecated": false, "autowired": false, "secret": false, "defaultValue": true,
"description": "Used for enabling or disabling all producer based health checks
from this component. Notice: Camel has by default disabled all producer based
health-checks. You can turn on produce [...]
- "bearerToken": { "index": 18, "kind": "property", "displayName": "Bearer
Token", "group": "security", "label": "security", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": true, "security": "secret", "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "Bearer token sent to the OPA server in the
Authorization header, for an OPA instance that [...]
- "failOpen": { "index": 19, "kind": "property", "displayName": "Fail Open",
"group": "security", "label": "security", "required": false, "type": "boolean",
"javaType": "boolean", "deprecated": false, "autowired": false, "secret":
false, "security": "insecure:dev", "defaultValue": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "Whether to allow the exchange to proceed when
the policy cannot be evalua [...]
- "sslContextParameters": { "index": 20, "kind": "property", "displayName":
"Ssl Context Parameters", "group": "security", "label": "security", "required":
false, "type": "object", "javaType":
"org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false,
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "TLS configuration for the connection to the
OPA server in [...]
- "useGlobalSslContextParameters": { "index": 21, "kind": "property",
"displayName": "Use Global Ssl Context Parameters", "group": "security",
"label": "security", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Enable usage of global SSL context
parameters." }
+ "batch": { "index": 1, "kind": "property", "displayName": "Batch",
"group": "producer", "label": "producer", "required": false, "type": "boolean",
"javaType": "boolean", "deprecated": false, "autowired": false, "secret":
false, "defaultValue": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "Authorize a whole collection in one call. When
enabled the producer expects a List body, evaluates one input [...]
+ "configuration": { "index": 2, "kind": "property", "displayName":
"Configuration", "group": "producer", "label": "", "required": false, "type":
"object", "javaType": "org.apache.camel.component.opa.OpaConfiguration",
"deprecated": false, "autowired": false, "secret": false, "description": "The
component configuration." },
+ "entrypoint": { "index": 3, "kind": "property", "displayName":
"Entrypoint", "group": "producer", "label": "", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "The compiled entrypoint to evaluate in wasm
mode. This is not the same thing as the policy path: an entrypoint is fixed wh
[...]
+ "evaluationMode": { "index": 4, "kind": "property", "displayName":
"Evaluation Mode", "group": "producer", "label": "", "required": false, "type":
"enum", "javaType": "java.lang.String", "enum": [ "rest", "wasm" ],
"deprecated": false, "autowired": false, "secret": false, "defaultValue":
"rest", "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "How the policy is evaluated. rest (the
default) calls a running [...]
+ "includeBody": { "index": 5, "kind": "property", "displayName": "Include
Body", "group": "producer", "label": "", "required": false, "type": "boolean",
"javaType": "boolean", "deprecated": false, "autowired": false, "secret":
false, "defaultValue": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "Whether to send the message body to OPA as
part of the input document. Disabled by default: bodies can b [...]
+ "includeHeaders": { "index": 6, "kind": "property", "displayName":
"Include Headers", "group": "producer", "label": "", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": false, "defaultValue": "*", "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "Comma-separated list of message header names
to send to OPA in the input document. The defau [...]
+ "includeProperties": { "index": 7, "kind": "property", "displayName":
"Include Properties", "group": "producer", "label": "", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "Comma-separated list of exchange property
names to send to OPA in the input document, or {code } for all of [...]
+ "lazyStartProducer": { "index": 8, "kind": "property", "displayName":
"Lazy Start Producer", "group": "producer", "label": "producer", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": false, "description":
"Whether the producer should be started lazy (on the first message). By
starting lazy you can use this to allow CamelContext and routes to startup in
situations where a producer may otherwise fail [...]
+ "policyBundle": { "index": 9, "kind": "property", "displayName": "Policy
Bundle", "group": "producer", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "The WebAssembly policy to evaluate in wasm
mode, as produced by {code opa build -t wasm}. Accepts a {code file:}, {co [...]
+ "serverUrl": { "index": 10, "kind": "property", "displayName": "Server
Url", "group": "producer", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "defaultValue": "http:\/\/localhost:8181",
"configurationClass": "org.apache.camel.component.opa.OpaConfiguration",
"configurationField": "configuration", "description": "The base URL of the OPA
server, without the {code \/v1\/data} suffix. The defau [...]
+ "autowiredEnabled": { "index": 11, "kind": "property", "displayName":
"Autowired Enabled", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": true, "description":
"Whether autowiring is enabled. This is used for automatic autowiring options
(the option must be marked as autowired) by looking up in the registry to find
if there is a single instance of matching [...]
+ "borrowTimeout": { "index": 12, "kind": "property", "displayName": "Borrow
Timeout", "group": "advanced", "label": "advanced", "required": false, "type":
"duration", "javaType": "long", "deprecated": false, "autowired": false,
"secret": false, "defaultValue": "30000", "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "How long an exchange waits for a free
WebAssembly policy instance in wasm mode before the e [...]
+ "connectionTimeout": { "index": 13, "kind": "property", "displayName":
"Connection Timeout", "group": "advanced", "label": "advanced", "required":
false, "type": "duration", "javaType": "long", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": "10000",
"configurationClass": "org.apache.camel.component.opa.OpaConfiguration",
"configurationField": "configuration", "description": "How long to wait for the
connection to the OPA server to be established, in rest mo [...]
+ "opaClient": { "index": 14, "kind": "property", "displayName": "Opa
Client", "group": "advanced", "label": "advanced", "required": false, "type":
"object", "javaType": "com.styra.opa.OPAClient", "deprecated": false,
"deprecationNote": "", "autowired": true, "secret": false,
"configurationClass": "org.apache.camel.component.opa.OpaConfiguration",
"configurationField": "configuration", "description": "An existing OPAClient to
use. When set, serverUrl and bearerToken are ignored." },
+ "poolSize": { "index": 15, "kind": "property", "displayName": "Pool Size",
"group": "advanced", "label": "advanced", "required": false, "type": "integer",
"javaType": "int", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": 8, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "How many WebAssembly policy instances to pool
in wasm mode. An instance carries mutable state and is not thre [...]
+ "requestTimeout": { "index": 16, "kind": "property", "displayName":
"Request Timeout", "group": "advanced", "label": "advanced", "required": false,
"type": "duration", "javaType": "long", "deprecated": false, "autowired":
false, "secret": false, "defaultValue": "30000", "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "How long to wait for the decision once
connected, in rest mode. A request that times out [...]
+ "healthCheckConsumerEnabled": { "index": 17, "kind": "property",
"displayName": "Health Check Consumer Enabled", "group": "health", "label":
"health", "required": false, "type": "boolean", "javaType": "boolean",
"deprecated": false, "autowired": false, "secret": false, "defaultValue": true,
"description": "Used for enabling or disabling all consumer based health checks
from this component" },
+ "healthCheckProducerEnabled": { "index": 18, "kind": "property",
"displayName": "Health Check Producer Enabled", "group": "health", "label":
"health", "required": false, "type": "boolean", "javaType": "boolean",
"deprecated": false, "autowired": false, "secret": false, "defaultValue": true,
"description": "Used for enabling or disabling all producer based health checks
from this component. Notice: Camel has by default disabled all producer based
health-checks. You can turn on produce [...]
+ "bearerToken": { "index": 19, "kind": "property", "displayName": "Bearer
Token", "group": "security", "label": "security", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": true, "security": "secret", "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "Bearer token sent to the OPA server in the
Authorization header, for an OPA instance that [...]
+ "failOpen": { "index": 20, "kind": "property", "displayName": "Fail Open",
"group": "security", "label": "security", "required": false, "type": "boolean",
"javaType": "boolean", "deprecated": false, "autowired": false, "secret":
false, "security": "insecure:dev", "defaultValue": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "Whether to allow the exchange to proceed when
the policy cannot be evalua [...]
+ "sslContextParameters": { "index": 21, "kind": "property", "displayName":
"Ssl Context Parameters", "group": "security", "label": "security", "required":
false, "type": "object", "javaType":
"org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false,
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "TLS configuration for the connection to the
OPA server in [...]
+ "useGlobalSslContextParameters": { "index": 22, "kind": "property",
"displayName": "Use Global Ssl Context Parameters", "group": "security",
"label": "security", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Enable usage of global SSL context
parameters." }
},
"headers": {
"CamelOpaDecisionAllow": { "index": 0, "kind": "header", "displayName":
"", "group": "producer", "label": "producer", "required": false, "javaType":
"Boolean", "deprecated": false, "deprecationNote": "", "autowired": false,
"secret": false, "description": "The allow\/deny verdict of the policy
evaluation. Always overwritten by the component, so a value set by an inbound
message never survives into the route.", "constantName":
"org.apache.camel.component.opa.OpaConstants#DECISION_ALLOW" },
"CamelOpaDecision": { "index": 1, "kind": "header", "displayName": "",
"group": "producer", "label": "producer", "required": false, "javaType":
"Object", "deprecated": false, "deprecationNote": "", "autowired": false,
"secret": false, "description": "The raw decision document returned by OPA.
Useful for policies that return more than a boolean, such as obligations, row
filters or deny reasons.", "constantName":
"org.apache.camel.component.opa.OpaConstants#DECISION" },
"CamelOpaPolicyPath": { "index": 2, "kind": "header", "displayName": "",
"group": "producer", "label": "producer", "required": false, "javaType":
"String", "deprecated": false, "deprecationNote": "", "autowired": false,
"secret": false, "description": "The policy path that was evaluated. Set by the
component for observability; it is not read as an input and cannot be used to
select a different policy.", "constantName":
"org.apache.camel.component.opa.OpaConstants#POLICY_PATH" },
- "CamelOpaDecisionFailedOpen": { "index": 3, "kind": "header",
"displayName": "", "group": "producer", "label": "producer", "required": false,
"javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Set to true only when the exchange
proceeded because failOpen is enabled and the policy could not be evaluated -
nothing authorized it. Absent on every decision an actual policy made, so a
route or an audit trail can tell the [...]
+ "CamelOpaDecisionFailedOpen": { "index": 3, "kind": "header",
"displayName": "", "group": "producer", "label": "producer", "required": false,
"javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Set to true only when the exchange
proceeded because failOpen is enabled and the policy could not be evaluated -
nothing authorized it. Absent on every decision an actual policy made, so a
route or an audit trail can tell the [...]
+ "CamelOpaBatchDecision": { "index": 4, "kind": "header", "displayName":
"", "group": "producer", "label": "producer", "required": false, "javaType":
"java.util.List<Boolean>", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "The per-element
allow\/deny verdicts of a batch evaluation (batch=true), as a List of Boolean
parallel to the List body. Always overwritten by the component. An element
whose evaluation could not be reached is denie [...]
},
"properties": {
"policyPath": { "index": 0, "kind": "path", "displayName": "Policy Path",
"group": "producer", "label": "", "required": true, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Path of the Rego rule head
to evaluate, relative to the OPA data document. For a rule named allow in a
policy declaring package authz.orders, this is authz\/orders\/allow. The path
is taken from the endpoint only: i [...]
"allowKey": { "index": 1, "kind": "parameter", "displayName": "Allow Key",
"group": "producer", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "defaultValue": "allow", "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "The key to read the allow\/deny verdict from
when the policy returns an object rather than a plain [...]
- "entrypoint": { "index": 2, "kind": "parameter", "displayName":
"Entrypoint", "group": "producer", "label": "", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "The compiled entrypoint to evaluate in wasm
mode. This is not the same thing as the policy path: an entrypoint is fixed w
[...]
- "evaluationMode": { "index": 3, "kind": "parameter", "displayName":
"Evaluation Mode", "group": "producer", "label": "", "required": false, "type":
"enum", "javaType": "java.lang.String", "enum": [ "rest", "wasm" ],
"deprecated": false, "autowired": false, "secret": false, "defaultValue":
"rest", "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "How the policy is evaluated. rest (the
default) calls a runnin [...]
- "includeBody": { "index": 4, "kind": "parameter", "displayName": "Include
Body", "group": "producer", "label": "", "required": false, "type": "boolean",
"javaType": "boolean", "deprecated": false, "autowired": false, "secret":
false, "defaultValue": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "Whether to send the message body to OPA as
part of the input document. Disabled by default: bodies can [...]
- "includeHeaders": { "index": 5, "kind": "parameter", "displayName":
"Include Headers", "group": "producer", "label": "", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": false, "defaultValue": "*", "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "Comma-separated list of message header names
to send to OPA in the input document. The defa [...]
- "includeProperties": { "index": 6, "kind": "parameter", "displayName":
"Include Properties", "group": "producer", "label": "", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "Comma-separated list of exchange property
names to send to OPA in the input document, or {code } for all o [...]
- "policyBundle": { "index": 7, "kind": "parameter", "displayName": "Policy
Bundle", "group": "producer", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "The WebAssembly policy to evaluate in wasm
mode, as produced by {code opa build -t wasm}. Accepts a {code file:}, {c [...]
- "serverUrl": { "index": 8, "kind": "parameter", "displayName": "Server
Url", "group": "producer", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "defaultValue": "http:\/\/localhost:8181",
"configurationClass": "org.apache.camel.component.opa.OpaConfiguration",
"configurationField": "configuration", "description": "The base URL of the OPA
server, without the {code \/v1\/data} suffix. The defau [...]
- "lazyStartProducer": { "index": 9, "kind": "parameter", "displayName":
"Lazy Start Producer", "group": "producer (advanced)", "label":
"producer,advanced", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Whether the producer should be started
lazy (on the first message). By starting lazy you can use this to allow
CamelContext and routes to startup in situations where a produc [...]
- "borrowTimeout": { "index": 10, "kind": "parameter", "displayName":
"Borrow Timeout", "group": "advanced", "label": "advanced", "required": false,
"type": "duration", "javaType": "long", "deprecated": false, "autowired":
false, "secret": false, "defaultValue": "30000", "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "How long an exchange waits for a free
WebAssembly policy instance in wasm mode before the [...]
- "connectionTimeout": { "index": 11, "kind": "parameter", "displayName":
"Connection Timeout", "group": "advanced", "label": "advanced", "required":
false, "type": "duration", "javaType": "long", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": "10000",
"configurationClass": "org.apache.camel.component.opa.OpaConfiguration",
"configurationField": "configuration", "description": "How long to wait for the
connection to the OPA server to be established, in rest m [...]
- "opaClient": { "index": 12, "kind": "parameter", "displayName": "Opa
Client", "group": "advanced", "label": "advanced", "required": false, "type":
"object", "javaType": "com.styra.opa.OPAClient", "deprecated": false,
"deprecationNote": "", "autowired": true, "secret": false,
"configurationClass": "org.apache.camel.component.opa.OpaConfiguration",
"configurationField": "configuration", "description": "An existing OPAClient to
use. When set, serverUrl and bearerToken are ignored." },
- "poolSize": { "index": 13, "kind": "parameter", "displayName": "Pool
Size", "group": "advanced", "label": "advanced", "required": false, "type":
"integer", "javaType": "int", "deprecated": false, "autowired": false,
"secret": false, "defaultValue": 8, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "How many WebAssembly policy instances to pool
in wasm mode. An instance carries mutable state and is not thr [...]
- "requestTimeout": { "index": 14, "kind": "parameter", "displayName":
"Request Timeout", "group": "advanced", "label": "advanced", "required": false,
"type": "duration", "javaType": "long", "deprecated": false, "autowired":
false, "secret": false, "defaultValue": "30000", "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "How long to wait for the decision once
connected, in rest mode. A request that times out [...]
- "bearerToken": { "index": 15, "kind": "parameter", "displayName": "Bearer
Token", "group": "security", "label": "security", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": true, "security": "secret", "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "Bearer token sent to the OPA server in the
Authorization header, for an OPA instance tha [...]
- "failOpen": { "index": 16, "kind": "parameter", "displayName": "Fail
Open", "group": "security", "label": "security", "required": false, "type":
"boolean", "javaType": "boolean", "deprecated": false, "autowired": false,
"secret": false, "security": "insecure:dev", "defaultValue": false,
"configurationClass": "org.apache.camel.component.opa.OpaConfiguration",
"configurationField": "configuration", "description": "Whether to allow the
exchange to proceed when the policy cannot be evalu [...]
- "sslContextParameters": { "index": 17, "kind": "parameter", "displayName":
"Ssl Context Parameters", "group": "security", "label": "security", "required":
false, "type": "object", "javaType":
"org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false,
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "TLS configuration for the connection to the
OPA server i [...]
+ "batch": { "index": 2, "kind": "parameter", "displayName": "Batch",
"group": "producer", "label": "producer", "required": false, "type": "boolean",
"javaType": "boolean", "deprecated": false, "autowired": false, "secret":
false, "defaultValue": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "Authorize a whole collection in one call. When
enabled the producer expects a List body, evaluates one input [...]
+ "entrypoint": { "index": 3, "kind": "parameter", "displayName":
"Entrypoint", "group": "producer", "label": "", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "The compiled entrypoint to evaluate in wasm
mode. This is not the same thing as the policy path: an entrypoint is fixed w
[...]
+ "evaluationMode": { "index": 4, "kind": "parameter", "displayName":
"Evaluation Mode", "group": "producer", "label": "", "required": false, "type":
"enum", "javaType": "java.lang.String", "enum": [ "rest", "wasm" ],
"deprecated": false, "autowired": false, "secret": false, "defaultValue":
"rest", "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "How the policy is evaluated. rest (the
default) calls a runnin [...]
+ "includeBody": { "index": 5, "kind": "parameter", "displayName": "Include
Body", "group": "producer", "label": "", "required": false, "type": "boolean",
"javaType": "boolean", "deprecated": false, "autowired": false, "secret":
false, "defaultValue": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "Whether to send the message body to OPA as
part of the input document. Disabled by default: bodies can [...]
+ "includeHeaders": { "index": 6, "kind": "parameter", "displayName":
"Include Headers", "group": "producer", "label": "", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": false, "defaultValue": "*", "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "Comma-separated list of message header names
to send to OPA in the input document. The defa [...]
+ "includeProperties": { "index": 7, "kind": "parameter", "displayName":
"Include Properties", "group": "producer", "label": "", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "Comma-separated list of exchange property
names to send to OPA in the input document, or {code } for all o [...]
+ "policyBundle": { "index": 8, "kind": "parameter", "displayName": "Policy
Bundle", "group": "producer", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "The WebAssembly policy to evaluate in wasm
mode, as produced by {code opa build -t wasm}. Accepts a {code file:}, {c [...]
+ "serverUrl": { "index": 9, "kind": "parameter", "displayName": "Server
Url", "group": "producer", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "defaultValue": "http:\/\/localhost:8181",
"configurationClass": "org.apache.camel.component.opa.OpaConfiguration",
"configurationField": "configuration", "description": "The base URL of the OPA
server, without the {code \/v1\/data} suffix. The defau [...]
+ "lazyStartProducer": { "index": 10, "kind": "parameter", "displayName":
"Lazy Start Producer", "group": "producer (advanced)", "label":
"producer,advanced", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Whether the producer should be started
lazy (on the first message). By starting lazy you can use this to allow
CamelContext and routes to startup in situations where a produ [...]
+ "borrowTimeout": { "index": 11, "kind": "parameter", "displayName":
"Borrow Timeout", "group": "advanced", "label": "advanced", "required": false,
"type": "duration", "javaType": "long", "deprecated": false, "autowired":
false, "secret": false, "defaultValue": "30000", "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "How long an exchange waits for a free
WebAssembly policy instance in wasm mode before the [...]
+ "connectionTimeout": { "index": 12, "kind": "parameter", "displayName":
"Connection Timeout", "group": "advanced", "label": "advanced", "required":
false, "type": "duration", "javaType": "long", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": "10000",
"configurationClass": "org.apache.camel.component.opa.OpaConfiguration",
"configurationField": "configuration", "description": "How long to wait for the
connection to the OPA server to be established, in rest m [...]
+ "opaClient": { "index": 13, "kind": "parameter", "displayName": "Opa
Client", "group": "advanced", "label": "advanced", "required": false, "type":
"object", "javaType": "com.styra.opa.OPAClient", "deprecated": false,
"deprecationNote": "", "autowired": true, "secret": false,
"configurationClass": "org.apache.camel.component.opa.OpaConfiguration",
"configurationField": "configuration", "description": "An existing OPAClient to
use. When set, serverUrl and bearerToken are ignored." },
+ "poolSize": { "index": 14, "kind": "parameter", "displayName": "Pool
Size", "group": "advanced", "label": "advanced", "required": false, "type":
"integer", "javaType": "int", "deprecated": false, "autowired": false,
"secret": false, "defaultValue": 8, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "How many WebAssembly policy instances to pool
in wasm mode. An instance carries mutable state and is not thr [...]
+ "requestTimeout": { "index": 15, "kind": "parameter", "displayName":
"Request Timeout", "group": "advanced", "label": "advanced", "required": false,
"type": "duration", "javaType": "long", "deprecated": false, "autowired":
false, "secret": false, "defaultValue": "30000", "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "How long to wait for the decision once
connected, in rest mode. A request that times out [...]
+ "bearerToken": { "index": 16, "kind": "parameter", "displayName": "Bearer
Token", "group": "security", "label": "security", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": true, "security": "secret", "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "Bearer token sent to the OPA server in the
Authorization header, for an OPA instance tha [...]
+ "failOpen": { "index": 17, "kind": "parameter", "displayName": "Fail
Open", "group": "security", "label": "security", "required": false, "type":
"boolean", "javaType": "boolean", "deprecated": false, "autowired": false,
"secret": false, "security": "insecure:dev", "defaultValue": false,
"configurationClass": "org.apache.camel.component.opa.OpaConfiguration",
"configurationField": "configuration", "description": "Whether to allow the
exchange to proceed when the policy cannot be evalu [...]
+ "sslContextParameters": { "index": 18, "kind": "parameter", "displayName":
"Ssl Context Parameters", "group": "security", "label": "security", "required":
false, "type": "object", "javaType":
"org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false,
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.opa.OpaConfiguration", "configurationField":
"configuration", "description": "TLS configuration for the connection to the
OPA server i [...]
}
}
diff --git a/components/camel-opa/src/main/docs/opa-component.adoc
b/components/camel-opa/src/main/docs/opa-component.adoc
index d674ffa40aed..9af5c30402db 100644
--- a/components/camel-opa/src/main/docs/opa-component.adoc
+++ b/components/camel-opa/src/main/docs/opa-component.adoc
@@ -180,6 +180,37 @@ boolean verdict read out of it:
Both headers are written on every evaluation, so a verdict set by an inbound
message never survives into the
route.
+== Batch evaluation
+
+A route that splits a payload and authorizes each element pays one OPA
round-trip per element. Set `batch=true` and
+hand the producer a `List` instead: it builds one input document per element -
each element as the `body`, sharing
+the exchange's headers and properties - and evaluates them in a single call
through OPA's batch API (the SDK falls
+back to sequential requests when the server does not implement it).
+
+[source,java]
+------------------------------------------------------------
+from("direct:orders")
+ // the body is a List of orders to authorize
+ .to("opa:authz/orders/allow?batch=true")
+ // CamelOpaBatchDecision is now a List<Boolean> parallel to the body
+ .process(dropTheDeniedOrders);
+------------------------------------------------------------
+
+The per-element verdicts arrive in `CamelOpaBatchDecision`, a `List<Boolean>`
parallel to the input list: element
+_i_ is allowed when entry _i_ is `true`. Neither `CamelOpaDecisionAllow` nor
`CamelOpaDecision` is set in batch
+mode - there is no single verdict, and no single decision document - and both
are cleared on entry like the other
+decision headers, so a value an inbound message supplied never survives.
`CamelOpaPolicyPath` *is* set, to the same
+value a single evaluation records, so tooling can read it either way.
+
+Fail-closed applies *per element*: an element whose evaluation could not be
reached is denied (`false`), or allowed
+when `failOpen` is set, while every other element decides normally. The batch
is never denied as a whole because
+one element failed, nor allowed because most of it succeeded. A call that
fails entirely - the server could not be
+reached at all - fails the exchange rather than denying each element: it
carries no verdict at all, not a list of
+`false`. Under `failOpen` that same failure allows every element instead.
+
+`batch` requires `evaluationMode=rest`: it saves the per-element HTTP
round-trip, which has no meaning for in-process
+`wasm` evaluation, and the endpoint rejects the combination at startup.
+
[#authorizing-an-identity]
== Authorizing an identity
diff --git
a/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaConfiguration.java
b/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaConfiguration.java
index 392e98257a4f..53fc17b783e7 100644
---
a/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaConfiguration.java
+++
b/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaConfiguration.java
@@ -71,6 +71,9 @@ public class OpaConfiguration implements Cloneable {
@UriParam(label = "security", security = "insecure:dev")
private boolean failOpen;
+ @UriParam(label = "producer")
+ private boolean batch;
+
@UriParam(label = "advanced",
description = "An existing OPAClient to use. When set, serverUrl
and bearerToken are ignored.")
@Metadata(autowired = true)
@@ -281,6 +284,23 @@ public class OpaConfiguration implements Cloneable {
this.failOpen = failOpen;
}
+ public boolean isBatch() {
+ return batch;
+ }
+
+ /**
+ * Authorize a whole collection in one call. When enabled the producer
expects a {@code List} body, evaluates one
+ * input document per element - each element as the {@code body}, sharing
the exchange's headers and properties -
+ * and returns the per-element verdicts in the {@code
CamelOpaBatchDecision} header, a {@code List<Boolean>}
+ * parallel to the input. An element whose evaluation could not be reached
is denied, unless {@code failOpen} is
+ * set; the batch is never allowed or denied as a whole because one
element failed. Only for
+ * {@code evaluationMode=rest}: it saves the per-element HTTP round-trip
via OPA's batch API, which has no meaning
+ * for in-process {@code wasm}.
+ */
+ public void setBatch(boolean batch) {
+ this.batch = batch;
+ }
+
/**
* An already-configured {@link OPAClient} to use instead of letting the
endpoint create one.
*/
diff --git
a/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaConstants.java
b/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaConstants.java
index 60811d4b9b83..583b0dd6f49e 100644
---
a/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaConstants.java
+++
b/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaConstants.java
@@ -47,6 +47,13 @@ public final class OpaConstants {
javaType = "Boolean")
public static final String DECISION_FAILED_OPEN = HEADER_PREFIX +
"DecisionFailedOpen";
+ @Metadata(label = "producer",
+ description = "The per-element allow/deny verdicts of a batch
evaluation (batch=true), as a List of"
+ + " Boolean parallel to the List body. Always
overwritten by the component. An element"
+ + " whose evaluation could not be reached is
denied, unless failOpen is set.",
+ javaType = "java.util.List<Boolean>")
+ public static final String BATCH_DECISION = HEADER_PREFIX +
"BatchDecision";
+
private OpaConstants() {
}
}
diff --git
a/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaEndpoint.java
b/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaEndpoint.java
index 8a621193f8b4..2cadd776128a 100644
---
a/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaEndpoint.java
+++
b/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaEndpoint.java
@@ -71,6 +71,12 @@ public class OpaEndpoint extends DefaultEndpoint {
super.doStart();
String mode = configuration.getEvaluationMode();
if (WASM_MODE.equalsIgnoreCase(mode)) {
+ if (configuration.isBatch()) {
+ throw new IllegalArgumentException(
+ "batch is not supported with evaluationMode=wasm: it
saves the per-element HTTP round-trip via"
+ + " OPA's batch API, which
has no meaning for in-process evaluation."
+ + " Use
evaluationMode=rest.");
+ }
warnAboutIgnoredServerOptions();
evaluator = createWasmEvaluator();
} else if (!REST_MODE.equalsIgnoreCase(mode)) {
diff --git
a/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaPolicyEvaluator.java
b/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaPolicyEvaluator.java
index e7ff69f0f5ea..f0e9d8eb57ec 100644
---
a/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaPolicyEvaluator.java
+++
b/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaPolicyEvaluator.java
@@ -16,6 +16,7 @@
*/
package org.apache.camel.component.opa;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
@@ -129,6 +130,106 @@ public abstract class OpaPolicyEvaluator {
*/
protected abstract Object evaluateDecision(Map<String, Object> input)
throws Exception;
+ /**
+ * Evaluates one input document per element in a single batch. The map is
keyed so a result can be matched back to
+ * its element; each value carries either the decision or the failure that
stopped it being reached. Only the REST
+ * evaluator implements this - wasm evaluates in-process, where batching
saves nothing - so the default refuses.
+ */
+ protected Map<String, BatchElement> evaluateBatchDecisions(Map<String,
Map<String, Object>> inputs) throws Exception {
+ throw new UnsupportedOperationException("batch evaluation is only
supported with evaluationMode=rest");
+ }
+
+ /**
+ * Authorizes a list in one call and records the per-element verdicts in
{@link OpaConstants#BATCH_DECISION}, a
+ * {@code List<Boolean>} parallel to the input.
+ * <p/>
+ * Fail-closed is per element: an element whose evaluation could not be
reached is denied (or allowed under
+ * {@code failOpen}) while the others decide normally. Only a batch call
that fails as a whole - the server could
+ * not be reached at all - denies (or, under {@code failOpen}, allows)
every element.
+ */
+ public List<Boolean> evaluateBatch(Exchange exchange, List<?> elements)
throws OpaPolicyEvaluationException {
+ clearDecisionHeaders(exchange);
+ // an empty list has nothing to authorize. Short-circuit before the
engine call: an empty batch is a case the
+ // OPA SDK does not define, and letting it reach the server could turn
"nothing to decide" into a whole-batch
+ // failure - and so, fail-closed, into an error thrown for an empty
list. Answer it deterministically instead.
+ if (elements.isEmpty()) {
+ List<Boolean> verdicts = List.of();
+ setBatchDecisionHeaders(exchange, verdicts);
+ return verdicts;
+ }
+ Map<String, Map<String, Object>> inputs = new LinkedHashMap<>();
+ for (int i = 0; i < elements.size(); i++) {
+ inputs.put(Integer.toString(i), buildInput(exchange,
elements.get(i), true));
+ }
+
+ List<Boolean> verdicts = new ArrayList<>(elements.size());
+ try {
+ Map<String, BatchElement> results = evaluateBatchDecisions(inputs);
+ for (int i = 0; i < elements.size(); i++) {
+ verdicts.add(verdictFor(i, results != null ?
results.get(Integer.toString(i)) : null));
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new OpaPolicyEvaluationException(
+ "Interrupted while evaluating policy " + getPolicyPath() +
" in batch", exchange, e);
+ } catch (Exception e) {
+ // the batch call itself failed, so nothing was decided; fail
closed for every element unless failOpen
+ if (!failOpen) {
+ throw new OpaPolicyEvaluationException(
+ "Failed to evaluate policy " + getPolicyPath() + " in
batch", exchange, e);
+ }
+ LOG.warn("Batch policy {} could not be evaluated, allowing all {}
elements because failOpen is enabled."
+ + " Reason: {}",
+ getPolicyPath(), elements.size(), e.getMessage());
+ for (int i = 0; i < elements.size(); i++) {
+ verdicts.add(Boolean.TRUE);
+ }
+ }
+
+ setBatchDecisionHeaders(exchange, verdicts);
+ return verdicts;
+ }
+
+ private boolean verdictFor(int index, BatchElement element) {
+ if (element != null && element.succeeded()) {
+ return isAllowed(element.decision());
+ }
+ // a single element could not be reached: deny it (or allow under
failOpen) without failing the whole batch
+ if (failOpen) {
+ String reason = element != null && element.failure() != null ?
element.failure().getMessage() : "no result";
+ LOG.warn("Batch element {} of policy {} could not be evaluated,
allowing it because failOpen is enabled."
+ + " Reason: {}",
+ index, getPolicyPath(), reason);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * One element's outcome in a batch: either a decision document, or the
failure that stopped it being reached.
+ */
+ protected static final class BatchElement {
+ private final Object decision;
+ private final Exception failure;
+
+ BatchElement(Object decision, Exception failure) {
+ this.decision = decision;
+ this.failure = failure;
+ }
+
+ Object decision() {
+ return decision;
+ }
+
+ Exception failure() {
+ return failure;
+ }
+
+ boolean succeeded() {
+ return failure == null;
+ }
+ }
+
protected String getPolicyPath() {
return policyPath;
}
@@ -137,6 +238,14 @@ public abstract class OpaPolicyEvaluator {
* Builds the {@code input} document handed to OPA.
*/
protected Map<String, Object> buildInput(Exchange exchange) {
+ return buildInput(exchange, exchange.getMessage().getBody(),
includeBody);
+ }
+
+ /**
+ * Builds the OPA input document with an explicit body. Batch evaluation
calls this once per list element, passing
+ * the element as the body so every element is authorized against the same
shared headers and properties.
+ */
+ protected Map<String, Object> buildInput(Exchange exchange, Object body,
boolean withBody) {
Map<String, Object> input = new LinkedHashMap<>();
Map<String, Object> headers = new LinkedHashMap<>();
for (Map.Entry<String, Object> entry :
exchange.getMessage().getHeaders().entrySet()) {
@@ -169,8 +278,8 @@ public abstract class OpaPolicyEvaluator {
input.put("properties", properties);
}
}
- if (includeBody) {
- input.put("body", toJsonSafe(exchange,
exchange.getMessage().getBody()));
+ if (withBody) {
+ input.put("body", toJsonSafe(exchange, body));
}
input.put("exchangeId", exchange.getExchangeId());
if (exchange.getFromRouteId() != null) {
@@ -240,6 +349,7 @@ public abstract class OpaPolicyEvaluator {
// as attacker-settable as the verdict itself: left in place, a sender
could preload it false and make a
// fail-open read as a decision a policy actually made
message.removeHeader(OpaConstants.DECISION_FAILED_OPEN);
+ message.removeHeader(OpaConstants.BATCH_DECISION);
}
private void setDecisionHeaders(Exchange exchange, Object decision,
boolean allowed) {
@@ -249,6 +359,17 @@ public abstract class OpaPolicyEvaluator {
exchange.getMessage().setHeader(OpaConstants.POLICY_PATH, policyPath);
}
+ /**
+ * Records the batch outcome on the exchange: the per-element verdict
list, and the policy path, so that tooling
+ * reading {@link OpaConstants#POLICY_PATH} sees the same value in batch
mode as it does after a single evaluation.
+ * As in {@link #evaluate}, a fail-closed batch failure never reaches
here, leaving the exchange carrying no
+ * verdict.
+ */
+ private void setBatchDecisionHeaders(Exchange exchange, List<Boolean>
verdicts) {
+ exchange.getMessage().setHeader(OpaConstants.BATCH_DECISION, verdicts);
+ exchange.getMessage().setHeader(OpaConstants.POLICY_PATH, policyPath);
+ }
+
private static boolean isIncluded(Set<String> filter, String name) {
return filter == null || filter.contains(name);
}
@@ -277,7 +398,8 @@ public abstract class OpaPolicyEvaluator {
return OpaConstants.DECISION_ALLOW.equalsIgnoreCase(name)
|| OpaConstants.DECISION.equalsIgnoreCase(name)
|| OpaConstants.POLICY_PATH.equalsIgnoreCase(name)
- || OpaConstants.DECISION_FAILED_OPEN.equalsIgnoreCase(name);
+ || OpaConstants.DECISION_FAILED_OPEN.equalsIgnoreCase(name)
+ || OpaConstants.BATCH_DECISION.equalsIgnoreCase(name);
}
/**
diff --git
a/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaProducer.java
b/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaProducer.java
index 110d8367f88f..0dbaa901a1ef 100644
---
a/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaProducer.java
+++
b/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaProducer.java
@@ -16,6 +16,8 @@
*/
package org.apache.camel.component.opa;
+import java.util.List;
+
import org.apache.camel.Exchange;
import org.apache.camel.health.HealthCheckHelper;
import org.apache.camel.health.WritableHealthCheckRepository;
@@ -78,6 +80,17 @@ public class OpaProducer extends DefaultProducer {
public void process(Exchange exchange) throws Exception {
// the verdict is reported through the decision headers, so that the
route can act on it with a
// filter or a choice; the body is left untouched
- getEndpoint().getEvaluator().evaluate(exchange);
+ OpaPolicyEvaluator evaluator = getEndpoint().getEvaluator();
+ if (getEndpoint().getConfiguration().isBatch()) {
+ Object body = exchange.getMessage().getBody();
+ if (!(body instanceof List<?> elements)) {
+ throw new IllegalArgumentException(
+ "batch=true requires a List body, but the body was "
+ + (body == null ? "null" :
body.getClass().getName()));
+ }
+ evaluator.evaluateBatch(exchange, elements);
+ } else {
+ evaluator.evaluate(exchange);
+ }
}
}
diff --git
a/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaRestEvaluator.java
b/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaRestEvaluator.java
index 10a67c19eecb..53b7ccb365c5 100644
---
a/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaRestEvaluator.java
+++
b/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaRestEvaluator.java
@@ -16,11 +16,13 @@
*/
package org.apache.camel.component.opa;
+import java.util.LinkedHashMap;
import java.util.Map;
import javax.net.ssl.SSLContext;
import com.styra.opa.OPAClient;
+import com.styra.opa.OPAResult;
import org.apache.camel.util.ObjectHelper;
/**
@@ -78,4 +80,28 @@ public class OpaRestEvaluator extends OpaPolicyEvaluator
implements AutoCloseabl
// the WASM engine is made to behave identically
return client.evaluate(getPolicyPath(), input, Object.class);
}
+
+ @Override
+ protected Map<String, BatchElement> evaluateBatchDecisions(Map<String,
Map<String, Object>> inputs)
+ throws Exception {
+ // one HTTP round-trip via OPA's batch endpoint; the SDK falls back to
sequential calls if the server does
+ // not implement it. Each entry carries its own decision or its own
failure, so one bad element does not sink
+ // the batch.
+ Map<String, Object> batchInputs = new LinkedHashMap<>(inputs);
+ Map<String, OPAResult> results = client.evaluateBatch(getPolicyPath(),
batchInputs);
+ Map<String, BatchElement> outcomes = new LinkedHashMap<>();
+ for (Map.Entry<String, OPAResult> entry : results.entrySet()) {
+ OPAResult result = entry.getValue();
+ if (result != null && result.success()) {
+ outcomes.put(entry.getKey(), new
BatchElement(result.getValue(), null));
+ } else {
+ Exception failure = result != null ? result.getException() :
null;
+ if (failure == null) {
+ failure = new IllegalStateException("no result returned
for batch element " + entry.getKey());
+ }
+ outcomes.put(entry.getKey(), new BatchElement(null, failure));
+ }
+ }
+ return outcomes;
+ }
}
diff --git
a/components/camel-opa/src/test/java/org/apache/camel/component/opa/OpaBatchEvaluationTest.java
b/components/camel-opa/src/test/java/org/apache/camel/component/opa/OpaBatchEvaluationTest.java
new file mode 100644
index 000000000000..ab5e51c91135
--- /dev/null
+++
b/components/camel-opa/src/test/java/org/apache/camel/component/opa/OpaBatchEvaluationTest.java
@@ -0,0 +1,176 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.opa;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.styra.opa.OPAClient;
+import com.styra.opa.OPAException;
+import com.styra.opa.OPAResult;
+import org.apache.camel.BindToRegistry;
+import org.apache.camel.Exchange;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.anyMap;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Batch evaluation authorizes a List body in one call and reports a
per-element verdict list. Fail-closed is per
+ * element: an element that could not be evaluated is denied (or allowed under
failOpen) while its neighbours decide
+ * normally (CAMEL-24740).
+ */
+public class OpaBatchEvaluationTest extends CamelTestSupport {
+
+ private static final String PATH = "authz/allow";
+
+ @BindToRegistry("opaClient")
+ private final OPAClient client = mock(OPAClient.class);
+
+ private OPAResult failed() {
+ // a batch element whose evaluation could not be reached: the server
returned a result carrying an error
+ OPAResult result = mock(OPAResult.class);
+ when(result.success()).thenReturn(false);
+ return result;
+ }
+
+ private void stubBatch(Map<String, OPAResult> results) throws Exception {
+ when(client.evaluateBatch(eq(PATH), anyMap())).thenReturn(results);
+ }
+
+ @Test
+ void reportsAVerdictParallelToEachElement() throws Exception {
+ Map<String, OPAResult> results = new LinkedHashMap<>();
+ results.put("0", new OPAResult(Boolean.TRUE));
+ results.put("1", new OPAResult(Boolean.FALSE));
+ results.put("2", new OPAResult(Boolean.TRUE));
+ stubBatch(results);
+
+ Exchange out = template.request("opa:" + PATH +
"?opaClient=#opaClient&batch=true",
+ e -> e.getMessage().setBody(List.of("alice", "mallory",
"carol")));
+
+ assertThat(out.getException()).isNull();
+ assertThat(out.getMessage().getHeader(OpaConstants.BATCH_DECISION,
List.class))
+ .containsExactly(true, false, true);
+ // batch mode must set the policy-path header too, so observability
tooling reads the same
+ // CamelOpaPolicyPath as it does after a single evaluation
+ assertThat(out.getMessage().getHeader(OpaConstants.POLICY_PATH,
String.class)).isEqualTo(PATH);
+ }
+
+ @Test
+ void deniesAFailedElementButLetsItsNeighboursDecide() throws Exception {
+ Map<String, OPAResult> results = new LinkedHashMap<>();
+ results.put("0", new OPAResult(Boolean.TRUE));
+ results.put("1", failed());
+ results.put("2", new OPAResult(Boolean.TRUE));
+ stubBatch(results);
+
+ Exchange out = template.request("opa:" + PATH +
"?opaClient=#opaClient&batch=true",
+ e -> e.getMessage().setBody(List.of("a", "b", "c")));
+
+ // the middle element is denied because it could not be evaluated, not
because the whole batch failed
+ assertThat(out.getException()).isNull();
+ assertThat(out.getMessage().getHeader(OpaConstants.BATCH_DECISION,
List.class))
+ .containsExactly(true, false, true);
+ }
+
+ @Test
+ void allowsAFailedElementUnderFailOpen() throws Exception {
+ Map<String, OPAResult> results = new LinkedHashMap<>();
+ results.put("0", new OPAResult(Boolean.TRUE));
+ results.put("1", failed());
+ results.put("2", new OPAResult(Boolean.FALSE));
+ stubBatch(results);
+
+ Exchange out = template.request("opa:" + PATH +
"?opaClient=#opaClient&batch=true&failOpen=true",
+ e -> e.getMessage().setBody(List.of("a", "b", "c")));
+
+ // failOpen turns the unreachable element into an allow; the genuine
deny at index 2 is unaffected
+ assertThat(out.getException()).isNull();
+ assertThat(out.getMessage().getHeader(OpaConstants.BATCH_DECISION,
List.class))
+ .containsExactly(true, true, false);
+ }
+
+ @Test
+ void requiresAListBody() {
+ Exchange out = template.request("opa:" + PATH +
"?opaClient=#opaClient&batch=true",
+ e -> e.getMessage().setBody("not a list"));
+
+
assertThat(out.getException()).isInstanceOf(IllegalArgumentException.class);
+ assertThat(out.getException().getMessage()).contains("List");
+ }
+
+ @Test
+ void rejectsBatchInWasmModeAtStartup() {
+ assertThatThrownBy(() -> context.getEndpoint(
+ "opa:" + PATH +
"?evaluationMode=wasm&policyBundle=classpath:authz.wasm&batch=true").start())
+ .isInstanceOf(Exception.class)
+ .hasMessageContaining("batch");
+ }
+
+ @Test
+ void reportsAnEmptyVerdictListForAnEmptyBatch() throws Exception {
+ // an empty list is answered without calling the SDK: an empty batch
input is undefined there, so the
+ // short-circuit makes it a deterministic empty verdict list. The
policy-path header is still set.
+ Exchange out = template.request("opa:" + PATH +
"?opaClient=#opaClient&batch=true",
+ e -> e.getMessage().setBody(List.of()));
+
+ assertThat(out.getException()).isNull();
+ assertThat(out.getMessage().getHeader(OpaConstants.BATCH_DECISION,
List.class)).isEmpty();
+ assertThat(out.getMessage().getHeader(OpaConstants.POLICY_PATH,
String.class)).isEqualTo(PATH);
+ verify(client, never()).evaluateBatch(anyString(), anyMap());
+ }
+
+ @Test
+ void failsClosedWhenTheWholeBatchCannotBeEvaluated() throws Exception {
+ // the batch call itself fails - the server could not be reached at
all - so nothing was decided. With
+ // failOpen off, every element is denied by failing the exchange, not
by returning a verdict list.
+ when(client.evaluateBatch(eq(PATH), anyMap())).thenThrow(new
OPAException("connection refused"));
+
+ Exchange out = template.request("opa:" + PATH +
"?opaClient=#opaClient&batch=true",
+ e -> e.getMessage().setBody(List.of("a", "b")));
+
+ assertThat(out.getException())
+ .isInstanceOf(OpaPolicyEvaluationException.class)
+ .hasMessageContaining("in batch");
+ // fail-closed leaves no verdict on the exchange, mirroring the
single-evaluation failure path
+
assertThat(out.getMessage().getHeader(OpaConstants.BATCH_DECISION)).isNull();
+ }
+
+ @Test
+ void allowsEveryElementUnderFailOpenWhenTheWholeBatchFails() throws
Exception {
+ // failOpen turns a whole-batch failure into an allow for every
element, parallel to the single-evaluation
+ // failOpen path
+ when(client.evaluateBatch(eq(PATH), anyMap())).thenThrow(new
OPAException("connection refused"));
+
+ Exchange out = template.request("opa:" + PATH +
"?opaClient=#opaClient&batch=true&failOpen=true",
+ e -> e.getMessage().setBody(List.of("a", "b", "c")));
+
+ assertThat(out.getException()).isNull();
+ assertThat(out.getMessage().getHeader(OpaConstants.BATCH_DECISION,
List.class))
+ .containsExactly(true, true, true);
+ }
+}
diff --git
a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/OpaComponentBuilderFactory.java
b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/OpaComponentBuilderFactory.java
index 3b1c544f969a..dbf83c3b4e6a 100644
---
a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/OpaComponentBuilderFactory.java
+++
b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/OpaComponentBuilderFactory.java
@@ -74,6 +74,32 @@ public interface OpaComponentBuilderFactory {
return this;
}
+
+ /**
+ * Authorize a whole collection in one call. When enabled the producer
+ * expects a List body, evaluates one input document per element - each
+ * element as the body, sharing the exchange's headers and properties -
+ * and returns the per-element verdicts in the CamelOpaBatchDecision
+ * header, a List parallel to the input. An element whose evaluation
+ * could not be reached is denied, unless failOpen is set; the batch is
+ * never allowed or denied as a whole because one element failed. Only
+ * for {code evaluationMode=rest}: it saves the per-element HTTP
+ * round-trip via OPA's batch API, which has no meaning for in-process
+ * wasm.
+ *
+ * The option is a: <code>boolean</code> type.
+ *
+ * Default: false
+ * Group: producer
+ *
+ * @param batch the value to set
+ * @return the dsl builder
+ */
+ default OpaComponentBuilder batch(boolean batch) {
+ doSetProperty("batch", batch);
+ return this;
+ }
+
/**
* The component configuration.
*
@@ -519,6 +545,7 @@ public interface OpaComponentBuilderFactory {
Object value) {
switch (name) {
case "allowKey": getOrCreateConfiguration((OpaComponent)
component).setAllowKey((java.lang.String) value); return true;
+ case "batch": getOrCreateConfiguration((OpaComponent)
component).setBatch((boolean) value); return true;
case "configuration": ((OpaComponent)
component).setConfiguration((org.apache.camel.component.opa.OpaConfiguration)
value); return true;
case "entrypoint": getOrCreateConfiguration((OpaComponent)
component).setEntrypoint((java.lang.String) value); return true;
case "evaluationMode": getOrCreateConfiguration((OpaComponent)
component).setEvaluationMode((java.lang.String) value); return true;
diff --git
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/OpaEndpointBuilderFactory.java
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/OpaEndpointBuilderFactory.java
index d71b57df2d9f..693ad42de71c 100644
---
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/OpaEndpointBuilderFactory.java
+++
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/OpaEndpointBuilderFactory.java
@@ -65,6 +65,54 @@ public interface OpaEndpointBuilderFactory {
doSetProperty("allowKey", allowKey);
return this;
}
+ /**
+ * Authorize a whole collection in one call. When enabled the producer
+ * expects a List body, evaluates one input document per element - each
+ * element as the body, sharing the exchange's headers and properties -
+ * and returns the per-element verdicts in the CamelOpaBatchDecision
+ * header, a List parallel to the input. An element whose evaluation
+ * could not be reached is denied, unless failOpen is set; the batch is
+ * never allowed or denied as a whole because one element failed. Only
+ * for {code evaluationMode=rest}: it saves the per-element HTTP
+ * round-trip via OPA's batch API, which has no meaning for in-process
+ * wasm.
+ *
+ * The option is a: <code>boolean</code> type.
+ *
+ * Default: false
+ * Group: producer
+ *
+ * @param batch the value to set
+ * @return the dsl builder
+ */
+ default OpaEndpointBuilder batch(boolean batch) {
+ doSetProperty("batch", batch);
+ return this;
+ }
+ /**
+ * Authorize a whole collection in one call. When enabled the producer
+ * expects a List body, evaluates one input document per element - each
+ * element as the body, sharing the exchange's headers and properties -
+ * and returns the per-element verdicts in the CamelOpaBatchDecision
+ * header, a List parallel to the input. An element whose evaluation
+ * could not be reached is denied, unless failOpen is set; the batch is
+ * never allowed or denied as a whole because one element failed. Only
+ * for {code evaluationMode=rest}: it saves the per-element HTTP
+ * round-trip via OPA's batch API, which has no meaning for in-process
+ * wasm.
+ *
+ * The option will be converted to a <code>boolean</code> type.
+ *
+ * Default: false
+ * Group: producer
+ *
+ * @param batch the value to set
+ * @return the dsl builder
+ */
+ default OpaEndpointBuilder batch(String batch) {
+ doSetProperty("batch", batch);
+ return this;
+ }
/**
* The compiled entrypoint to evaluate in wasm mode. This is not the
* same thing as the policy path: an entrypoint is fixed when the
bundle
@@ -690,6 +738,21 @@ public interface OpaEndpointBuilderFactory {
public String opaDecisionFailedOpen() {
return "CamelOpaDecisionFailedOpen";
}
+ /**
+ * The per-element allow/deny verdicts of a batch evaluation
+ * (batch=true), as a List of Boolean parallel to the List body. Always
+ * overwritten by the component. An element whose evaluation could not
+ * be reached is denied, unless failOpen is set.
+ *
+ * The option is a: {@code java.util.List<Boolean>} type.
+ *
+ * Group: producer
+ *
+ * @return the name of the header {@code OpaBatchDecision}.
+ */
+ public String opaBatchDecision() {
+ return "CamelOpaBatchDecision";
+ }
}
static OpaEndpointBuilder endpointBuilder(String componentName, String
path) {
class OpaEndpointBuilderImpl extends AbstractEndpointBuilder
implements OpaEndpointBuilder, AdvancedOpaEndpointBuilder {