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 0076168a5cd4 CAMEL-24784/CAMEL-24785: camel-opa - bound the REST
decision call, reuse one HTTP client, and support sslContextParameters (#26524)
0076168a5cd4 is described below
commit 0076168a5cd42719dc4f1f873c9286cda12cd447
Author: Andrea Cosentino <[email protected]>
AuthorDate: Thu Sep 17 10:05:37 2026 +0200
CAMEL-24784/CAMEL-24785: camel-opa - bound the REST decision call, reuse
one HTTP client, and support sslContextParameters (#26524)
camel-opa: bound the REST decision call with a request timeout
(CAMEL-24784), reuse a single HTTP client
across invocations instead of creating one per request, and support
sslContextParameters for TLS to the OPA
server (CAMEL-24785).
Co-authored-by: Claude Opus 4.8 <[email protected]>
---
.../org/apache/camel/catalog/components/opa.json | 27 ++-
.../apache/camel/catalog/docs/opa-component.adoc | 37 ++++
.../component/opa/OpaComponentConfigurer.java | 24 +++
.../camel/component/opa/OpaEndpointConfigurer.java | 18 ++
.../camel/component/opa/OpaEndpointUriFactory.java | 5 +-
.../org/apache/camel/component/opa/opa.json | 27 ++-
.../camel-opa/src/main/docs/opa-component.adoc | 37 ++++
.../apache/camel/component/opa/OpaComponent.java | 19 +-
.../camel/component/opa/OpaConfiguration.java | 48 +++++
.../apache/camel/component/opa/OpaEndpoint.java | 48 ++++-
.../apache/camel/component/opa/OpaHealthProbe.java | 28 ++-
.../apache/camel/component/opa/OpaHttpClient.java | 90 ++++++++
.../apache/camel/component/opa/OpaProducer.java | 2 +-
.../component/opa/OpaProducerHealthCheck.java | 9 +-
.../camel/component/opa/OpaRestEvaluator.java | 43 +++-
.../component/opa/security/OpaSecurityPolicy.java | 89 +++++++-
.../opa/security/OpaSecurityPolicyHealthCheck.java | 9 +-
.../component/opa/OpaProducerHealthCheckTest.java | 8 +-
.../camel/component/opa/OpaRestTransportTest.java | 234 +++++++++++++++++++++
.../security/OpaSecurityPolicyHealthCheckTest.java | 4 +-
.../src/test/resources/README-tls-fixture.md | 16 ++
.../camel-opa/src/test/resources/opa-server.p12 | Bin 0 -> 2674 bytes
.../component/dsl/OpaComponentBuilderFactory.java | 82 ++++++++
.../endpoint/dsl/OpaEndpointBuilderFactory.java | 112 ++++++++++
24 files changed, 963 insertions(+), 53 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 11cd8ccebcbc..27178ee61b22 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
@@ -36,12 +36,16 @@
"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 [...]
- "opaClient": { "index": 12, "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": 13, "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 [...]
- "healthCheckConsumerEnabled": { "index": 14, "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": 15, "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": 16, "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": 17, "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 [...]
+ "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." }
},
"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" },
@@ -60,9 +64,12 @@
"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 [...]
- "opaClient": { "index": 11, "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": 12, "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 [...]
- "bearerToken": { "index": 13, "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": 14, "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 [...]
+ "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 [...]
}
}
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 4df6be0ac77e..0a373925f776 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
@@ -278,6 +278,43 @@ exchanges evaluate at once, and `borrowTimeout` when a
single evaluation is legi
run long enough to matter is usually better served by `evaluationMode=rest`,
where the decision point is a
separate process that a timeout can abandon.
+== Connecting to the server
+
+In `rest` mode the component talks to OPA over HTTP, and both waits are
bounded:
+
+* `connectionTimeout` (default 10s) — establishing the connection.
+* `requestTimeout` (default 30s) — waiting for the decision once connected.
+
+Neither is optional in practice. A refused connection fails immediately, but a
server that *accepts* and then
+stops answering — wedged, mid-restart, or behind a load balancer holding the
socket — would otherwise park the
+routing thread indefinitely. For a component that fails closed that is worse
than a denial: it never reaches the
+point of deciding. A timeout is treated as a failure to reach a verdict, so it
denies, or proceeds if `failOpen`
+is set, like any other such failure.
+
+TLS is configured with `sslContextParameters`, the usual
xref:manual::camel-configuration-utilities.adoc[JSSE
+utility]:
+
+[source,java]
+------------------------------------------------------------
+from("platform-http:/orders")
+
.to("opa:authz/orders/allow?serverUrl=https://opa:8181&sslContextParameters=#opaTls")
+------------------------------------------------------------
+
+Set `useGlobalSslContextParameters=true` on the component to pick up the
context-wide configuration instead.
+
+That is what lets a workload present a client certificate to an OPA server
requiring mutual TLS — a SPIFFE
+X.509-SVID, for example, so the application authenticates to the policy
decision point as itself rather than
+relying on network position:
+
+[source,java]
+------------------------------------------------------------
+SpiffeSSLContextParameters spiffe = new SpiffeSSLContextParameters();
+spiffe.setCamelContext(context);
+context.getRegistry().bind("opaTls", spiffe);
+------------------------------------------------------------
+
+None of this applies in `wasm` mode, where there is no server to reach.
+
== Failure handling
The component fails closed. If the policy cannot be evaluated at all — the OPA
server is unreachable, times out,
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 e726ff7dd3f2..abaee2681400 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
@@ -39,6 +39,8 @@ public class OpaComponentConfigurer extends
PropertyConfigurerSupport implements
case "borrowtimeout":
case "borrowTimeout":
getOrCreateConfiguration(target).setBorrowTimeout(property(camelContext,
java.time.Duration.class, value).toMillis()); return true;
case "configuration": target.setConfiguration(property(camelContext,
org.apache.camel.component.opa.OpaConfiguration.class, value)); return true;
+ case "connectiontimeout":
+ case "connectionTimeout":
getOrCreateConfiguration(target).setConnectionTimeout(property(camelContext,
java.time.Duration.class, value).toMillis()); return true;
case "entrypoint":
getOrCreateConfiguration(target).setEntrypoint(property(camelContext,
java.lang.String.class, value)); return true;
case "evaluationmode":
case "evaluationMode":
getOrCreateConfiguration(target).setEvaluationMode(property(camelContext,
java.lang.String.class, value)); return true;
@@ -62,8 +64,14 @@ public class OpaComponentConfigurer extends
PropertyConfigurerSupport implements
case "policyBundle":
getOrCreateConfiguration(target).setPolicyBundle(property(camelContext,
java.lang.String.class, value)); return true;
case "poolsize":
case "poolSize":
getOrCreateConfiguration(target).setPoolSize(property(camelContext, int.class,
value)); return true;
+ case "requesttimeout":
+ case "requestTimeout":
getOrCreateConfiguration(target).setRequestTimeout(property(camelContext,
java.time.Duration.class, value).toMillis()); return true;
case "serverurl":
case "serverUrl":
getOrCreateConfiguration(target).setServerUrl(property(camelContext,
java.lang.String.class, value)); return true;
+ case "sslcontextparameters":
+ case "sslContextParameters":
getOrCreateConfiguration(target).setSslContextParameters(property(camelContext,
org.apache.camel.support.jsse.SSLContextParameters.class, value)); return true;
+ case "useglobalsslcontextparameters":
+ case "useGlobalSslContextParameters":
target.setUseGlobalSslContextParameters(property(camelContext, boolean.class,
value)); return true;
default: return false;
}
}
@@ -85,6 +93,8 @@ public class OpaComponentConfigurer extends
PropertyConfigurerSupport implements
case "borrowtimeout":
case "borrowTimeout": return long.class;
case "configuration": return
org.apache.camel.component.opa.OpaConfiguration.class;
+ case "connectiontimeout":
+ case "connectionTimeout": return long.class;
case "entrypoint": return java.lang.String.class;
case "evaluationmode":
case "evaluationMode": return java.lang.String.class;
@@ -108,8 +118,14 @@ public class OpaComponentConfigurer extends
PropertyConfigurerSupport implements
case "policyBundle": return java.lang.String.class;
case "poolsize":
case "poolSize": return int.class;
+ case "requesttimeout":
+ case "requestTimeout": return long.class;
case "serverurl":
case "serverUrl": return java.lang.String.class;
+ case "sslcontextparameters":
+ case "sslContextParameters": return
org.apache.camel.support.jsse.SSLContextParameters.class;
+ case "useglobalsslcontextparameters":
+ case "useGlobalSslContextParameters": return boolean.class;
default: return null;
}
}
@@ -127,6 +143,8 @@ public class OpaComponentConfigurer extends
PropertyConfigurerSupport implements
case "borrowtimeout":
case "borrowTimeout": return
getOrCreateConfiguration(target).getBorrowTimeout();
case "configuration": return target.getConfiguration();
+ case "connectiontimeout":
+ case "connectionTimeout": return
getOrCreateConfiguration(target).getConnectionTimeout();
case "entrypoint": return
getOrCreateConfiguration(target).getEntrypoint();
case "evaluationmode":
case "evaluationMode": return
getOrCreateConfiguration(target).getEvaluationMode();
@@ -150,8 +168,14 @@ public class OpaComponentConfigurer extends
PropertyConfigurerSupport implements
case "policyBundle": return
getOrCreateConfiguration(target).getPolicyBundle();
case "poolsize":
case "poolSize": return getOrCreateConfiguration(target).getPoolSize();
+ case "requesttimeout":
+ case "requestTimeout": return
getOrCreateConfiguration(target).getRequestTimeout();
case "serverurl":
case "serverUrl": return
getOrCreateConfiguration(target).getServerUrl();
+ case "sslcontextparameters":
+ case "sslContextParameters": return
getOrCreateConfiguration(target).getSslContextParameters();
+ case "useglobalsslcontextparameters":
+ case "useGlobalSslContextParameters": return
target.isUseGlobalSslContextParameters();
default: return null;
}
}
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 31c2e21a0ad7..469fa36c4faa 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
@@ -29,6 +29,8 @@ public class OpaEndpointConfigurer extends
PropertyConfigurerSupport implements
case "bearerToken":
target.getConfiguration().setBearerToken(property(camelContext,
java.lang.String.class, value)); return true;
case "borrowtimeout":
case "borrowTimeout":
target.getConfiguration().setBorrowTimeout(property(camelContext,
java.time.Duration.class, value).toMillis()); return true;
+ case "connectiontimeout":
+ case "connectionTimeout":
target.getConfiguration().setConnectionTimeout(property(camelContext,
java.time.Duration.class, value).toMillis()); return true;
case "entrypoint":
target.getConfiguration().setEntrypoint(property(camelContext,
java.lang.String.class, value)); return true;
case "evaluationmode":
case "evaluationMode":
target.getConfiguration().setEvaluationMode(property(camelContext,
java.lang.String.class, value)); return true;
@@ -48,8 +50,12 @@ public class OpaEndpointConfigurer extends
PropertyConfigurerSupport implements
case "policyBundle":
target.getConfiguration().setPolicyBundle(property(camelContext,
java.lang.String.class, value)); return true;
case "poolsize":
case "poolSize":
target.getConfiguration().setPoolSize(property(camelContext, int.class,
value)); return true;
+ case "requesttimeout":
+ case "requestTimeout":
target.getConfiguration().setRequestTimeout(property(camelContext,
java.time.Duration.class, value).toMillis()); return true;
case "serverurl":
case "serverUrl":
target.getConfiguration().setServerUrl(property(camelContext,
java.lang.String.class, value)); return true;
+ case "sslcontextparameters":
+ case "sslContextParameters":
target.getConfiguration().setSslContextParameters(property(camelContext,
org.apache.camel.support.jsse.SSLContextParameters.class, value)); return true;
default: return false;
}
}
@@ -68,6 +74,8 @@ public class OpaEndpointConfigurer extends
PropertyConfigurerSupport implements
case "bearerToken": return java.lang.String.class;
case "borrowtimeout":
case "borrowTimeout": return long.class;
+ case "connectiontimeout":
+ case "connectionTimeout": return long.class;
case "entrypoint": return java.lang.String.class;
case "evaluationmode":
case "evaluationMode": return java.lang.String.class;
@@ -87,8 +95,12 @@ public class OpaEndpointConfigurer extends
PropertyConfigurerSupport implements
case "policyBundle": return java.lang.String.class;
case "poolsize":
case "poolSize": return int.class;
+ case "requesttimeout":
+ case "requestTimeout": return long.class;
case "serverurl":
case "serverUrl": return java.lang.String.class;
+ case "sslcontextparameters":
+ case "sslContextParameters": return
org.apache.camel.support.jsse.SSLContextParameters.class;
default: return null;
}
}
@@ -103,6 +115,8 @@ public class OpaEndpointConfigurer extends
PropertyConfigurerSupport implements
case "bearerToken": return target.getConfiguration().getBearerToken();
case "borrowtimeout":
case "borrowTimeout": return
target.getConfiguration().getBorrowTimeout();
+ case "connectiontimeout":
+ case "connectionTimeout": return
target.getConfiguration().getConnectionTimeout();
case "entrypoint": return target.getConfiguration().getEntrypoint();
case "evaluationmode":
case "evaluationMode": return
target.getConfiguration().getEvaluationMode();
@@ -122,8 +136,12 @@ public class OpaEndpointConfigurer extends
PropertyConfigurerSupport implements
case "policyBundle": return
target.getConfiguration().getPolicyBundle();
case "poolsize":
case "poolSize": return target.getConfiguration().getPoolSize();
+ case "requesttimeout":
+ case "requestTimeout": return
target.getConfiguration().getRequestTimeout();
case "serverurl":
case "serverUrl": return target.getConfiguration().getServerUrl();
+ case "sslcontextparameters":
+ case "sslContextParameters": return
target.getConfiguration().getSslContextParameters();
default: return null;
}
}
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 5848d96093a9..b18d621d92b8 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,10 +24,11 @@ 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<>(15);
+ Set<String> props = new HashSet<>(18);
props.add("allowKey");
props.add("bearerToken");
props.add("borrowTimeout");
+ props.add("connectionTimeout");
props.add("entrypoint");
props.add("evaluationMode");
props.add("failOpen");
@@ -39,7 +40,9 @@ public class OpaEndpointUriFactory extends
org.apache.camel.support.component.En
props.add("policyBundle");
props.add("policyPath");
props.add("poolSize");
+ props.add("requestTimeout");
props.add("serverUrl");
+ props.add("sslContextParameters");
PROPERTY_NAMES = Collections.unmodifiableSet(props);
Set<String> secretProps = new HashSet<>(1);
secretProps.add("bearerToken");
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 11cd8ccebcbc..27178ee61b22 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
@@ -36,12 +36,16 @@
"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 [...]
- "opaClient": { "index": 12, "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": 13, "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 [...]
- "healthCheckConsumerEnabled": { "index": 14, "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": 15, "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": 16, "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": 17, "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 [...]
+ "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." }
},
"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" },
@@ -60,9 +64,12 @@
"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 [...]
- "opaClient": { "index": 11, "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": 12, "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 [...]
- "bearerToken": { "index": 13, "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": 14, "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 [...]
+ "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 [...]
}
}
diff --git a/components/camel-opa/src/main/docs/opa-component.adoc
b/components/camel-opa/src/main/docs/opa-component.adoc
index 4df6be0ac77e..0a373925f776 100644
--- a/components/camel-opa/src/main/docs/opa-component.adoc
+++ b/components/camel-opa/src/main/docs/opa-component.adoc
@@ -278,6 +278,43 @@ exchanges evaluate at once, and `borrowTimeout` when a
single evaluation is legi
run long enough to matter is usually better served by `evaluationMode=rest`,
where the decision point is a
separate process that a timeout can abandon.
+== Connecting to the server
+
+In `rest` mode the component talks to OPA over HTTP, and both waits are
bounded:
+
+* `connectionTimeout` (default 10s) — establishing the connection.
+* `requestTimeout` (default 30s) — waiting for the decision once connected.
+
+Neither is optional in practice. A refused connection fails immediately, but a
server that *accepts* and then
+stops answering — wedged, mid-restart, or behind a load balancer holding the
socket — would otherwise park the
+routing thread indefinitely. For a component that fails closed that is worse
than a denial: it never reaches the
+point of deciding. A timeout is treated as a failure to reach a verdict, so it
denies, or proceeds if `failOpen`
+is set, like any other such failure.
+
+TLS is configured with `sslContextParameters`, the usual
xref:manual::camel-configuration-utilities.adoc[JSSE
+utility]:
+
+[source,java]
+------------------------------------------------------------
+from("platform-http:/orders")
+
.to("opa:authz/orders/allow?serverUrl=https://opa:8181&sslContextParameters=#opaTls")
+------------------------------------------------------------
+
+Set `useGlobalSslContextParameters=true` on the component to pick up the
context-wide configuration instead.
+
+That is what lets a workload present a client certificate to an OPA server
requiring mutual TLS — a SPIFFE
+X.509-SVID, for example, so the application authenticates to the policy
decision point as itself rather than
+relying on network position:
+
+[source,java]
+------------------------------------------------------------
+SpiffeSSLContextParameters spiffe = new SpiffeSSLContextParameters();
+spiffe.setCamelContext(context);
+context.getRegistry().bind("opaTls", spiffe);
+------------------------------------------------------------
+
+None of this applies in `wasm` mode, where there is no server to reach.
+
== Failure handling
The component fails closed. If the policy cannot be evaluated at all — the OPA
server is unreachable, times out,
diff --git
a/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaComponent.java
b/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaComponent.java
index 96660e274da2..47fc55ffa75c 100644
---
a/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaComponent.java
+++
b/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaComponent.java
@@ -20,6 +20,7 @@ import java.util.Map;
import org.apache.camel.CamelContext;
import org.apache.camel.Endpoint;
+import org.apache.camel.SSLContextParametersAware;
import org.apache.camel.spi.Metadata;
import org.apache.camel.spi.annotations.Component;
import org.apache.camel.support.HealthCheckComponent;
@@ -30,11 +31,14 @@ import org.apache.camel.util.StringHelper;
* Open Policy Agent component.
*/
@Component("opa")
-public class OpaComponent extends HealthCheckComponent {
+public class OpaComponent extends HealthCheckComponent implements
SSLContextParametersAware {
@Metadata
private OpaConfiguration configuration = new OpaConfiguration();
+ @Metadata(label = "security", defaultValue = "false")
+ private boolean useGlobalSslContextParameters;
+
public OpaComponent() {
}
@@ -42,6 +46,19 @@ public class OpaComponent extends HealthCheckComponent {
super(context);
}
+ @Override
+ public boolean isUseGlobalSslContextParameters() {
+ return useGlobalSslContextParameters;
+ }
+
+ /**
+ * Enable usage of global SSL context parameters.
+ */
+ @Override
+ public void setUseGlobalSslContextParameters(boolean
useGlobalSslContextParameters) {
+ this.useGlobalSslContextParameters = useGlobalSslContextParameters;
+ }
+
@Override
protected Endpoint createEndpoint(String uri, String remaining,
Map<String, Object> parameters) throws Exception {
if (ObjectHelper.isEmpty(remaining)) {
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 98489b2659a8..9c3753d796c0 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
@@ -21,6 +21,7 @@ import org.apache.camel.RuntimeCamelException;
import org.apache.camel.spi.Metadata;
import org.apache.camel.spi.UriParam;
import org.apache.camel.spi.UriParams;
+import org.apache.camel.support.jsse.SSLContextParameters;
@UriParams
public class OpaConfiguration implements Cloneable {
@@ -57,6 +58,14 @@ public class OpaConfiguration implements Cloneable {
@UriParam(label = "advanced", defaultValue = "30000", javaType =
"java.time.Duration")
private long borrowTimeout = 30000;
+ @UriParam(label = "advanced", defaultValue = "10000", javaType =
"java.time.Duration")
+ private long connectionTimeout = 10000;
+ @UriParam(label = "advanced", defaultValue = "30000", javaType =
"java.time.Duration")
+ private long requestTimeout = 30000;
+
+ @UriParam(label = "security")
+ private SSLContextParameters sslContextParameters;
+
@UriParam(label = "security", security = "insecure:dev")
private boolean failOpen;
@@ -218,6 +227,45 @@ public class OpaConfiguration implements Cloneable {
this.borrowTimeout = borrowTimeout;
}
+ public long getConnectionTimeout() {
+ return connectionTimeout;
+ }
+
+ /**
+ * How long to wait for the connection to the OPA server to be
established, in {@code rest} mode. The SDK's own
+ * transport applies no timeout at all, so a server that never answers
would otherwise park the calling thread
+ * indefinitely rather than letting the component fail closed.
+ */
+ public void setConnectionTimeout(long connectionTimeout) {
+ this.connectionTimeout = connectionTimeout;
+ }
+
+ public long getRequestTimeout() {
+ return requestTimeout;
+ }
+
+ /**
+ * How long to wait for the decision once connected, in {@code rest} mode.
A request that times out is an evaluation
+ * failure rather than a deny, so it fails closed - or proceeds when
{@code failOpen} is set - like any other
+ * failure to reach a verdict.
+ */
+ public void setRequestTimeout(long requestTimeout) {
+ this.requestTimeout = requestTimeout;
+ }
+
+ public SSLContextParameters getSslContextParameters() {
+ return sslContextParameters;
+ }
+
+ /**
+ * TLS configuration for the connection to the OPA server in {@code rest}
mode. Needed to trust a server whose
+ * certificate comes from a private CA, and to present a client
certificate to a server that requires mutual TLS - a
+ * SPIFFE X.509-SVID, for instance, so the workload authenticates to the
policy decision point as itself.
+ */
+ public void setSslContextParameters(SSLContextParameters
sslContextParameters) {
+ this.sslContextParameters = sslContextParameters;
+ }
+
/**
* Whether to allow the exchange to proceed when the policy cannot be
evaluated at all, for example because the OPA
* server is unreachable. Disabled by default so that an unreachable
policy decision point denies rather than grants
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 175bb7f85408..495f78f3184b 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
@@ -16,6 +16,8 @@
*/
package org.apache.camel.component.opa;
+import javax.net.ssl.SSLContext;
+
import com.styra.opa.OPAClient;
import org.apache.camel.Category;
import org.apache.camel.Component;
@@ -27,6 +29,7 @@ import org.apache.camel.spi.UriEndpoint;
import org.apache.camel.spi.UriParam;
import org.apache.camel.spi.UriPath;
import org.apache.camel.support.DefaultEndpoint;
+import org.apache.camel.support.jsse.SSLContextParameters;
import org.apache.camel.util.ObjectHelper;
/**
@@ -52,6 +55,7 @@ public class OpaEndpoint extends DefaultEndpoint {
private OPAClient opaClient;
private volatile OpaPolicyEvaluator evaluator;
+ private volatile SSLContext sslContext;
public OpaEndpoint(final String uri, final Component component, final
OpaConfiguration configuration) {
super(uri, component);
@@ -70,13 +74,45 @@ public class OpaEndpoint extends DefaultEndpoint {
throw new IllegalArgumentException(
"Unknown evaluationMode '" + mode + "'; expected one of "
+ REST_MODE + ", " + WASM_MODE);
} else {
- opaClient = configuration.getOpaClient() != null
- ? configuration.getOpaClient()
- :
OpaRestEvaluator.createClient(configuration.getServerUrl(),
configuration.getBearerToken());
- evaluator = new OpaRestEvaluator(
- opaClient, policyPath, configuration.getAllowKey(),
configuration.getIncludeHeaders(),
- configuration.getIncludeProperties(),
configuration.isIncludeBody(), configuration.isFailOpen());
+ if (configuration.getOpaClient() != null) {
+ opaClient = configuration.getOpaClient();
+ evaluator = new OpaRestEvaluator(
+ opaClient, null, policyPath,
configuration.getAllowKey(), configuration.getIncludeHeaders(),
+ configuration.getIncludeProperties(),
configuration.isIncludeBody(),
+ configuration.isFailOpen());
+ } else {
+ sslContext = createSslContext();
+ OpaHttpClient transport = OpaRestEvaluator.createTransport(
+ configuration.getBearerToken(),
+ configuration.getConnectionTimeout(),
configuration.getRequestTimeout(),
+ sslContext);
+ opaClient =
OpaRestEvaluator.createClient(configuration.getServerUrl(), transport);
+ evaluator = new OpaRestEvaluator(
+ opaClient, transport, policyPath,
configuration.getAllowKey(),
+ configuration.getIncludeHeaders(),
+ configuration.getIncludeProperties(),
configuration.isIncludeBody(),
+ configuration.isFailOpen());
+ }
+ }
+ }
+
+ /**
+ * Resolves the endpoint's TLS configuration, falling back to the
context's global one when the component opts in.
+ */
+ private SSLContext createSslContext() throws Exception {
+ SSLContextParameters ssl = configuration.getSslContextParameters();
+ if (ssl == null) {
+ ssl = getComponent().retrieveGlobalSslContextParameters();
}
+ return ssl != null ? ssl.createSSLContext(getCamelContext()) : null;
+ }
+
+ /**
+ * The TLS configuration the decision call resolved to, so the producer's
readiness check probes the server the same
+ * way rather than failing a handshake the decision call passes.
+ */
+ SSLContext getSslContext() {
+ return sslContext;
}
private OpaPolicyEvaluator createWasmEvaluator() throws Exception {
diff --git
a/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaHealthProbe.java
b/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaHealthProbe.java
index b354052c0154..1f9daf4ebc53 100644
---
a/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaHealthProbe.java
+++
b/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaHealthProbe.java
@@ -21,6 +21,10 @@ import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.net.ssl.SSLContext;
import org.apache.camel.health.HealthCheckResultBuilder;
import org.apache.camel.util.FileUtil;
@@ -45,9 +49,27 @@ public final class OpaHealthProbe {
*/
private static final HttpClient HTTP_CLIENT =
HttpClient.newBuilder().connectTimeout(TIMEOUT).build();
+ /**
+ * One client per distinct TLS configuration, because an {@link
SSLContext} can only be set when the client is
+ * built. A probe that ignored it would fail its handshake against the
very server the decision call reaches happily
+ * - reporting DOWN, and with it an application that never becomes ready.
+ * <p/>
+ * Keyed on identity and never evicted, which is bounded in practice: the
key is an {@code SSLContext} built from an
+ * endpoint's {@code sslContextParameters}, and a deployment has one or
two of those, not one per exchange.
+ */
+ private static final Map<SSLContext, HttpClient> TLS_CLIENTS = new
ConcurrentHashMap<>();
+
private OpaHealthProbe() {
}
+ private static HttpClient clientFor(SSLContext sslContext) {
+ if (sslContext == null) {
+ return HTTP_CLIENT;
+ }
+ return TLS_CLIENTS.computeIfAbsent(sslContext,
+ ctx ->
HttpClient.newBuilder().connectTimeout(TIMEOUT).sslContext(ctx).build());
+ }
+
/**
* Probes the OPA server's health endpoint and records the outcome on the
builder.
* <p/>
@@ -58,9 +80,11 @@ public final class OpaHealthProbe {
* @param serverUrl base URL of the OPA server, without the /v1/data
suffix
* @param bearerToken token for OPA API authentication, or null when OPA
does not require one
* @param policyPath the policy this check is reporting for, recorded as
a detail
+ * @param sslContext the TLS configuration the decision call uses, or
null for the JVM default
*/
public static void probe(
- HealthCheckResultBuilder builder, String serverUrl, String
bearerToken, String policyPath) {
+ HealthCheckResultBuilder builder, String serverUrl, String
bearerToken, String policyPath,
+ SSLContext sslContext) {
builder.detail("opa.serverUrl", URISupport.sanitizeUri(serverUrl));
builder.detail("opa.policyPath", policyPath);
@@ -75,7 +99,7 @@ public final class OpaHealthProbe {
}
try {
- HttpResponse<Void> response = HTTP_CLIENT.send(request.build(),
HttpResponse.BodyHandlers.discarding());
+ HttpResponse<Void> response =
clientFor(sslContext).send(request.build(),
HttpResponse.BodyHandlers.discarding());
if (response.statusCode() == 200) {
builder.up();
} else {
diff --git
a/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaHttpClient.java
b/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaHttpClient.java
new file mode 100644
index 000000000000..bd537e6173b9
--- /dev/null
+++
b/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaHttpClient.java
@@ -0,0 +1,90 @@
+/*
+ * 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.io.IOException;
+import java.io.InputStream;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.time.Duration;
+
+import javax.net.ssl.SSLContext;
+
+import com.styra.opa.openapi.utils.HTTPClient;
+import org.apache.camel.util.ObjectHelper;
+
+/**
+ * The HTTP transport the OPA SDK uses to reach the server.
+ * <p/>
+ * Supplied rather than left to the SDK, whose default {@code
SpeakeasyHTTPClient} is a one-liner around
+ * {@code HttpClient.newHttpClient()} with two consequences a policy decision
point cannot afford:
+ * <ul>
+ * <li><b>Nothing bounds the call.</b> That factory sets no connect timeout
and the SDK sets no request timeout, so both
+ * are the JDK default of "wait indefinitely". A server that accepts the
connection and then goes quiet parks the
+ * calling thread for ever - and a component that fails closed never reaches
the point of denying, it simply stops.
+ * {@code failOpen} does not help, because it sits downstream of a call that
never returns.</li>
+ * <li><b>It builds a client per request.</b> On the Java 17 baseline {@link
HttpClient} is not {@link AutoCloseable},
+ * so each one holds its selector thread and executor until it is collected -
once per message, on the path an
+ * {@code OpaSecurityPolicy} guards.</li>
+ * </ul>
+ * One client is built here per evaluator and reused, and every request is
re-issued carrying a timeout.
+ */
+public class OpaHttpClient implements HTTPClient, AutoCloseable {
+
+ private static final String AUTHORIZATION = "Authorization";
+
+ private final HttpClient client;
+ private final Duration requestTimeout;
+ private final String bearerToken;
+
+ OpaHttpClient(long connectionTimeout, long requestTimeout, SSLContext
sslContext, String bearerToken) {
+ HttpClient.Builder builder = HttpClient.newBuilder()
+ .connectTimeout(Duration.ofMillis(connectionTimeout));
+ if (sslContext != null) {
+ builder.sslContext(sslContext);
+ }
+ this.client = builder.build();
+ this.requestTimeout = Duration.ofMillis(requestTimeout);
+ // the SDK has no constructor taking a transport and headers together,
so the token is applied here
+ // instead of through OPAClient(String, Map) - the request that goes
out is the same either way
+ // isNotEmpty, not a null check: an unset placeholder resolves to "",
and sending
+ // "Authorization: Bearer " is worse than sending nothing at all
+ this.bearerToken = ObjectHelper.isNotEmpty(bearerToken) ? bearerToken
: null;
+ }
+
+ @Override
+ public void close() throws Exception {
+ if (client instanceof AutoCloseable c) {
+ c.close();
+ }
+ }
+
+ @Override
+ public HttpResponse<InputStream> send(HttpRequest request) throws
IOException, InterruptedException {
+ // the SDK hands us a finished request, and HttpRequest is immutable -
rebuilding it is the only way to
+ // attach a timeout. The filter keeps every header the SDK set,
dropping only an Authorization we are
+ // about to replace, because header() appends rather than overwrites
+ HttpRequest.Builder builder = HttpRequest
+ .newBuilder(request, (name, value) -> bearerToken == null ||
!AUTHORIZATION.equalsIgnoreCase(name))
+ .timeout(requestTimeout);
+ if (bearerToken != null) {
+ builder.header(AUTHORIZATION, "Bearer " + bearerToken);
+ }
+ return client.send(builder.build(),
HttpResponse.BodyHandlers.ofInputStream());
+ }
+}
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 0e1e2c11f22d..3d6e2e4c25a2 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
@@ -58,7 +58,7 @@ public class OpaProducer extends DefaultProducer {
configuration.getServerUrl(),
configuration.getBearerToken(),
// the endpoint URI is unique within the context, so two
endpoints sharing a policy path but
// pointing at different servers get distinct health-check
ids instead of colliding
- getEndpoint().getPolicyPath(),
getEndpoint().getEndpointUri());
+ getEndpoint().getPolicyPath(),
getEndpoint().getEndpointUri(), getEndpoint().getSslContext());
producerHealthCheck.setEnabled(getEndpoint().getComponent().isHealthCheckProducerEnabled());
healthCheckRepository.addHealthCheck(producerHealthCheck);
}
diff --git
a/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaProducerHealthCheck.java
b/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaProducerHealthCheck.java
index d91946850d99..6305a4de280c 100644
---
a/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaProducerHealthCheck.java
+++
b/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaProducerHealthCheck.java
@@ -18,6 +18,8 @@ package org.apache.camel.component.opa;
import java.util.Map;
+import javax.net.ssl.SSLContext;
+
import org.apache.camel.health.HealthCheckResultBuilder;
import org.apache.camel.impl.health.AbstractHealthCheck;
import org.apache.camel.util.URISupport;
@@ -34,18 +36,21 @@ public class OpaProducerHealthCheck extends
AbstractHealthCheck {
private final String serverUrl;
private final String bearerToken;
private final String policyPath;
+ private final SSLContext sslContext;
- public OpaProducerHealthCheck(String serverUrl, String bearerToken, String
policyPath, String id) {
+ public OpaProducerHealthCheck(String serverUrl, String bearerToken, String
policyPath, String id,
+ SSLContext sslContext) {
// the id is built from the endpoint URI so that two endpoints sharing
a policy path stay distinct, but that
// URI carries the bearerToken in the clear and the id is published in
the health output, so sanitize it
super("camel", "producer:opa-" + URISupport.sanitizeUri(id));
this.serverUrl = serverUrl;
this.bearerToken = bearerToken;
this.policyPath = policyPath;
+ this.sslContext = sslContext;
}
@Override
protected void doCall(HealthCheckResultBuilder builder, Map<String,
Object> options) {
- OpaHealthProbe.probe(builder, serverUrl, bearerToken, policyPath);
+ OpaHealthProbe.probe(builder, serverUrl, bearerToken, policyPath,
sslContext);
}
}
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 4746ddc89500..10a67c19eecb 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
@@ -18,33 +18,58 @@ package org.apache.camel.component.opa;
import java.util.Map;
+import javax.net.ssl.SSLContext;
+
import com.styra.opa.OPAClient;
import org.apache.camel.util.ObjectHelper;
/**
* Evaluates the policy by calling a running OPA server over its REST Data API.
*/
-public class OpaRestEvaluator extends OpaPolicyEvaluator {
+public class OpaRestEvaluator extends OpaPolicyEvaluator implements
AutoCloseable {
private final OPAClient client;
+ private final OpaHttpClient transport;
- public OpaRestEvaluator(OPAClient client, String policyPath, String
allowKey, String includeHeaders,
+ public OpaRestEvaluator(OPAClient client, OpaHttpClient transport, String
policyPath, String allowKey,
+ String includeHeaders,
String includeProperties, boolean includeBody,
boolean failOpen) {
super(policyPath, allowKey, includeHeaders, includeProperties,
includeBody, failOpen);
this.client = ObjectHelper.notNull(client, "client");
+ this.transport = transport;
}
/**
- * Creates a client for an OPA server, optionally authenticating with a
bearer token.
+ * Creates the HTTP transport for an OPA server connection.
*
- * @param serverUrl base URL of the OPA server, without the /v1/data
suffix
- * @param bearerToken token for OPA API authentication, or null when OPA
does not require one
+ * @param bearerToken token for OPA API authentication, or null when
OPA does not require one
+ * @param connectionTimeout how long to wait for the connection to be
established, in milliseconds
+ * @param requestTimeout how long to wait for the decision once
connected, in milliseconds
+ * @param sslContext TLS configuration for the connection, or null
for the JVM default
*/
- public static OPAClient createClient(String serverUrl, String bearerToken)
{
- if (ObjectHelper.isNotEmpty(bearerToken)) {
- return new OPAClient(serverUrl, Map.of("Authorization", "Bearer "
+ bearerToken));
+ public static OpaHttpClient createTransport(
+ String bearerToken, long connectionTimeout, long requestTimeout,
SSLContext sslContext) {
+ return new OpaHttpClient(connectionTimeout, requestTimeout,
sslContext, bearerToken);
+ }
+
+ /**
+ * Creates a client for an OPA server, over a transport this component
controls.
+ * <p/>
+ * The SDK's own default transport builds a fresh {@code HttpClient} per
request with no timeouts at all; see
+ * {@link OpaHttpClient} for why neither is acceptable on the path that
decides authorization.
+ *
+ * @param serverUrl base URL of the OPA server, without the /v1/data suffix
+ * @param transport the HTTP transport to use for all requests
+ */
+ public static OPAClient createClient(String serverUrl, OpaHttpClient
transport) {
+ return new OPAClient(serverUrl, transport);
+ }
+
+ @Override
+ public void close() throws Exception {
+ if (transport != null) {
+ transport.close();
}
- return new OPAClient(serverUrl);
}
@Override
diff --git
a/components/camel-opa/src/main/java/org/apache/camel/component/opa/security/OpaSecurityPolicy.java
b/components/camel-opa/src/main/java/org/apache/camel/component/opa/security/OpaSecurityPolicy.java
index 3c3a60c907a1..a8dfc21cb742 100644
---
a/components/camel-opa/src/main/java/org/apache/camel/component/opa/security/OpaSecurityPolicy.java
+++
b/components/camel-opa/src/main/java/org/apache/camel/component/opa/security/OpaSecurityPolicy.java
@@ -16,14 +16,23 @@
*/
package org.apache.camel.component.opa.security;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+
+import javax.net.ssl.SSLContext;
+
import com.styra.opa.OPAClient;
+import org.apache.camel.CamelContext;
import org.apache.camel.NamedNode;
import org.apache.camel.Processor;
import org.apache.camel.Route;
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.component.opa.OpaHttpClient;
import org.apache.camel.component.opa.OpaPolicyEvaluator;
import org.apache.camel.component.opa.OpaRestEvaluator;
import org.apache.camel.health.HealthCheckRegistry;
import org.apache.camel.spi.AuthorizationPolicy;
+import org.apache.camel.support.jsse.SSLContextParameters;
import org.apache.camel.util.ObjectHelper;
import org.apache.camel.util.StringHelper;
import org.slf4j.Logger;
@@ -59,10 +68,14 @@ public class OpaSecurityPolicy implements
AuthorizationPolicy {
private OPAClient opaClient;
private boolean healthCheckEnabled = true;
+ private long connectionTimeout = 10000;
+ private long requestTimeout = 30000;
+ private SSLContextParameters sslContextParameters;
private volatile OpaPolicyEvaluator evaluator;
private volatile OpaSecurityPolicyHealthCheck healthCheck;
private volatile boolean ownsClient;
+ private volatile SSLContext sslContext;
public OpaSecurityPolicy() {
}
@@ -76,13 +89,25 @@ public class OpaSecurityPolicy implements
AuthorizationPolicy {
public void beforeWrap(Route route, NamedNode definition) {
if (evaluator == null) {
StringHelper.notEmpty(policyPath, "policyPath", this);
+ OpaHttpClient transport = null;
if (opaClient == null) {
// createClient moved to OpaRestEvaluator when the evaluator
became an abstract base
- opaClient = OpaRestEvaluator.createClient(serverUrl,
bearerToken);
+ sslContext = createSslContext(route.getCamelContext());
+ transport = OpaRestEvaluator.createTransport(
+ bearerToken, connectionTimeout, requestTimeout,
sslContext);
+ opaClient = OpaRestEvaluator.createClient(serverUrl,
transport);
ownsClient = true;
}
evaluator = new OpaRestEvaluator(
- opaClient, policyPath, allowKey, includeHeaders,
includeProperties, includeBody, failOpen);
+ opaClient, transport, policyPath, allowKey,
includeHeaders, includeProperties, includeBody,
+ failOpen);
+ // a Policy has no stop hook of its own, so the transport would
outlive the routes it was built for.
+ // Registering the evaluator as a service hands its close() to the
context's shutdown
+ try {
+ route.getCamelContext().addService(evaluator);
+ } catch (Exception e) {
+ throw new RuntimeCamelException("Could not register the
evaluator for policy " + policyPath, e);
+ }
}
// after validation, so a policy that is missing its policyPath fails
without leaving a ".../null" check
// behind in the registry
@@ -105,7 +130,7 @@ public class OpaSecurityPolicy implements
AuthorizationPolicy {
if (registry == null) {
return;
}
- healthCheck = new OpaSecurityPolicyHealthCheck(serverUrl, bearerToken,
policyPath);
+ healthCheck = new OpaSecurityPolicyHealthCheck(serverUrl, bearerToken,
policyPath, sslContext);
registry.register(healthCheck);
}
@@ -211,6 +236,64 @@ public class OpaSecurityPolicy implements
AuthorizationPolicy {
this.failOpen = failOpen;
}
+ /**
+ * The policy is a bean rather than a {@code CamelContextAware} service,
so the context comes from the route it is
+ * wrapping - which is the only place one is available.
+ */
+ private SSLContext createSslContext(CamelContext camelContext) {
+ if (sslContextParameters == null) {
+ return null;
+ }
+ try {
+ return sslContextParameters.createSSLContext(camelContext);
+ } catch (GeneralSecurityException | IOException e) {
+ // beforeWrap cannot throw checked exceptions, and a policy whose
TLS configuration is broken must not
+ // start a route that would then talk to OPA over the JVM default
trust material instead
+ throw new RuntimeCamelException("Could not build the SSLContext
for policy " + policyPath, e);
+ }
+ }
+
+ public long getConnectionTimeout() {
+ return connectionTimeout;
+ }
+
+ /**
+ * How long to wait for the connection to the OPA server to be
established. The SDK's own transport applies no
+ * timeout, so a server that never answers would otherwise park the
routing thread rather than letting the policy
+ * fail closed.
+ */
+ public void setConnectionTimeout(long connectionTimeout) {
+ this.connectionTimeout = connectionTimeout;
+ }
+
+ public long getRequestTimeout() {
+ return requestTimeout;
+ }
+
+ /**
+ * How long to wait for the decision once connected. A request that times
out is an evaluation failure rather than a
+ * deny, so the policy denies the exchange unless {@code failOpen} is set.
+ */
+ public void setRequestTimeout(long requestTimeout) {
+ this.requestTimeout = requestTimeout;
+ }
+
+ public SSLContextParameters getSslContextParameters() {
+ return sslContextParameters;
+ }
+
+ /**
+ * TLS configuration for the connection to the OPA server. Needed to trust
a server whose certificate comes from a
+ * private CA, and to present a client certificate to a server requiring
mutual TLS.
+ * <p/>
+ * Note: {@code useGlobalSslContextParameters} on the {@link
org.apache.camel.component.opa.OpaComponent} has no
+ * effect here. {@code OpaSecurityPolicy} is a standalone bean and is not
bound to any component instance, so the
+ * global SSL context cannot be resolved automatically. Set this field
explicitly when TLS is required.
+ */
+ public void setSslContextParameters(SSLContextParameters
sslContextParameters) {
+ this.sslContextParameters = sslContextParameters;
+ }
+
public boolean isHealthCheckEnabled() {
return healthCheckEnabled;
}
diff --git
a/components/camel-opa/src/main/java/org/apache/camel/component/opa/security/OpaSecurityPolicyHealthCheck.java
b/components/camel-opa/src/main/java/org/apache/camel/component/opa/security/OpaSecurityPolicyHealthCheck.java
index 75e4aad3a7cf..2c53a3447005 100644
---
a/components/camel-opa/src/main/java/org/apache/camel/component/opa/security/OpaSecurityPolicyHealthCheck.java
+++
b/components/camel-opa/src/main/java/org/apache/camel/component/opa/security/OpaSecurityPolicyHealthCheck.java
@@ -18,6 +18,8 @@ package org.apache.camel.component.opa.security;
import java.util.Map;
+import javax.net.ssl.SSLContext;
+
import org.apache.camel.component.opa.OpaHealthProbe;
import org.apache.camel.health.HealthCheckResultBuilder;
import org.apache.camel.impl.health.AbstractHealthCheck;
@@ -36,18 +38,21 @@ public class OpaSecurityPolicyHealthCheck extends
AbstractHealthCheck {
private final String serverUrl;
private final String bearerToken;
private final String policyPath;
+ private final SSLContext sslContext;
- public OpaSecurityPolicyHealthCheck(String serverUrl, String bearerToken,
String policyPath) {
+ public OpaSecurityPolicyHealthCheck(String serverUrl, String bearerToken,
String policyPath,
+ SSLContext sslContext) {
// serverUrl and policyPath together identify the decision this policy
enforces, so two policies pointing at
// different servers stay distinct; sanitized because the id is
published in the health output
super("camel", "security-policy:opa-" +
URISupport.sanitizeUri(serverUrl + "/" + policyPath));
this.serverUrl = serverUrl;
this.bearerToken = bearerToken;
this.policyPath = policyPath;
+ this.sslContext = sslContext;
}
@Override
protected void doCall(HealthCheckResultBuilder builder, Map<String,
Object> options) {
- OpaHealthProbe.probe(builder, serverUrl, bearerToken, policyPath);
+ OpaHealthProbe.probe(builder, serverUrl, bearerToken, policyPath,
sslContext);
}
}
diff --git
a/components/camel-opa/src/test/java/org/apache/camel/component/opa/OpaProducerHealthCheckTest.java
b/components/camel-opa/src/test/java/org/apache/camel/component/opa/OpaProducerHealthCheckTest.java
index 649c373ac548..7e0ee58d1cbb 100644
---
a/components/camel-opa/src/test/java/org/apache/camel/component/opa/OpaProducerHealthCheckTest.java
+++
b/components/camel-opa/src/test/java/org/apache/camel/component/opa/OpaProducerHealthCheckTest.java
@@ -52,7 +52,7 @@ class OpaProducerHealthCheckTest {
}
private static HealthCheck.Result call(String serverUrl) {
- OpaProducerHealthCheck check = new OpaProducerHealthCheck(serverUrl,
null, "authz/allow", "authz/allow");
+ OpaProducerHealthCheck check = new OpaProducerHealthCheck(serverUrl,
null, "authz/allow", "authz/allow", null);
check.setEnabled(true);
return check.call(Map.of());
}
@@ -63,7 +63,7 @@ class OpaProducerHealthCheckTest {
// health output, so the token must not survive into it
OpaProducerHealthCheck check = new OpaProducerHealthCheck(
"http://localhost:8181", "s3cr3t-token", "authz/allow",
-
"opa://authz/allow?bearerToken=s3cr3t-token&serverUrl=http://localhost:8181");
+
"opa://authz/allow?bearerToken=s3cr3t-token&serverUrl=http://localhost:8181",
null);
assertThat(check.getId()).doesNotContain("s3cr3t-token");
assertThat(check.getId()).contains("serverUrl=http://localhost:8181");
@@ -73,10 +73,10 @@ class OpaProducerHealthCheckTest {
void givesEndpointsOnDifferentServersDistinctIds() {
OpaProducerHealthCheck primary = new OpaProducerHealthCheck(
"http://opa-primary:8181", null, "authz/allow",
- "opa://authz/allow?serverUrl=http://opa-primary:8181");
+ "opa://authz/allow?serverUrl=http://opa-primary:8181", null);
OpaProducerHealthCheck secondary = new OpaProducerHealthCheck(
"http://opa-secondary:8181", null, "authz/allow",
- "opa://authz/allow?serverUrl=http://opa-secondary:8181");
+ "opa://authz/allow?serverUrl=http://opa-secondary:8181", null);
assertThat(primary.getId()).isNotEqualTo(secondary.getId());
assertThat(primary).isNotEqualTo(secondary);
diff --git
a/components/camel-opa/src/test/java/org/apache/camel/component/opa/OpaRestTransportTest.java
b/components/camel-opa/src/test/java/org/apache/camel/component/opa/OpaRestTransportTest.java
new file mode 100644
index 000000000000..8bb3096a273f
--- /dev/null
+++
b/components/camel-opa/src/test/java/org/apache/camel/component/opa/OpaRestTransportTest.java
@@ -0,0 +1,234 @@
+/*
+ * 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.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyStore;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+
+import com.sun.net.httpserver.HttpServer;
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsServer;
+import org.apache.camel.Exchange;
+import org.apache.camel.support.jsse.KeyStoreParameters;
+import org.apache.camel.support.jsse.SSLContextParameters;
+import org.apache.camel.support.jsse.TrustManagersParameters;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Timeout;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * The transport underneath {@code evaluationMode=rest}.
+ * <p/>
+ * A server that refuses a connection fails fast and is already covered; what
is not, and what an operator actually
+ * meets, is a server that <em>accepts</em> and then says nothing. The SDK's
own transport applies no timeout of any
+ * kind, so that case parked the routing thread for ever - and a component
that fails closed never reached the point of
+ * denying, it simply stopped.
+ */
+public class OpaRestTransportTest extends CamelTestSupport {
+
+ private static final String KEYSTORE = "opa-server.p12";
+ private static final String PASSWORD = "changeit";
+
+ private final List<Closeable> open = new ArrayList<>();
+
+ private interface Closeable extends AutoCloseable {
+ @Override
+ void close() throws IOException;
+ }
+
+ @AfterEach
+ void closeSockets() throws Exception {
+ for (Closeable c : open) {
+ c.close();
+ }
+ open.clear();
+ }
+
+ /**
+ * A listener that completes the TCP handshake and then never answers.
Deliberately not an HttpServer: the point is
+ * a peer that is reachable but mute, which is what a wedged OPA looks
like from the client side.
+ */
+ private int mutePort() throws IOException {
+ ServerSocket listener = new ServerSocket(0);
+ open.add(listener::close);
+ Thread accepter = new Thread(() -> {
+ try {
+ listener.accept();
+ // hold it: no read, no write, no close. Ends when the test
closes the listener.
+ } catch (IOException e) {
+ // the listener was closed as the test finished
+ }
+ }, "opa-mute-server");
+ accepter.setDaemon(true);
+ accepter.start();
+ return listener.getLocalPort();
+ }
+
+ /**
+ * An HTTPS listener presenting the throwaway self-signed certificate from
{@code opa-server.p12}, answering the
+ * policy query the way a real OPA would.
+ */
+ private int tlsPort() throws Exception {
+ KeyStore keyStore = KeyStore.getInstance("PKCS12");
+ try (InputStream in = getClass().getResourceAsStream("/" + KEYSTORE)) {
+ keyStore.load(in, PASSWORD.toCharArray());
+ }
+ KeyManagerFactory kmf =
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+ kmf.init(keyStore, PASSWORD.toCharArray());
+ SSLContext serverContext = SSLContext.getInstance("TLS");
+ serverContext.init(kmf.getKeyManagers(), null, null);
+
+ HttpsServer server = HttpsServer.create(new
InetSocketAddress("localhost", 0), 0);
+ server.setHttpsConfigurator(new HttpsConfigurator(serverContext));
+ server.createContext("/v1/data/authz/allow", exchange -> {
+ byte[] body = "{\"result\":true}".getBytes(StandardCharsets.UTF_8);
+ exchange.getResponseHeaders().add("Content-Type",
"application/json");
+ exchange.sendResponseHeaders(200, body.length);
+ try (OutputStream out = exchange.getResponseBody()) {
+ out.write(body);
+ }
+ });
+ server.start();
+ open.add(() -> server.stop(0));
+ return server.getAddress().getPort();
+ }
+
+ /** Trust material holding exactly the fixture certificate, and nothing
else. */
+ private SSLContextParameters trustingTheFixture() {
+ KeyStoreParameters ks = new KeyStoreParameters();
+ ks.setCamelContext(context);
+ ks.setResource(KEYSTORE);
+ ks.setPassword(PASSWORD);
+ ks.setType("PKCS12");
+
+ TrustManagersParameters tm = new TrustManagersParameters();
+ tm.setCamelContext(context);
+ tm.setKeyStore(ks);
+
+ SSLContextParameters ssl = new SSLContextParameters();
+ ssl.setCamelContext(context);
+ ssl.setTrustManagers(tm);
+ return ssl;
+ }
+
+ @Test
+ @Timeout(60)
+ void reachesAnHttpsServerWhenSslContextParametersTrustIt() throws
Exception {
+ context.getRegistry().bind("opaTls", trustingTheFixture());
+
+ Exchange out = template.request(
+ "opa:authz/allow?serverUrl=https://localhost:" + tlsPort() +
"&sslContextParameters=#opaTls", e -> {
+ });
+
+ assertThat(out.getException()).isNull();
+
assertThat(out.getMessage().getHeader(OpaConstants.DECISION_ALLOW)).isEqualTo(true);
+ }
+
+ @Test
+ @Timeout(60)
+ void failsClosedAgainstTheSameServerWithoutTheTrustMaterial() throws
Exception {
+ // same listener, same policy, same request - only
sslContextParameters is missing. If this passed, the
+ // option would not be doing anything and the test above would prove
nothing
+ Exchange out = template.request(
+ "opa:authz/allow?serverUrl=https://localhost:" + tlsPort(), e
-> {
+ });
+
+
assertThat(out.getException()).isInstanceOf(OpaPolicyEvaluationException.class);
+
assertThat(out.getMessage().getHeader(OpaConstants.DECISION_ALLOW)).isNull();
+ }
+
+ /** A plain listener that answers every policy query and records the
Authorization it was sent. */
+ private int recordingPort(AtomicReference<String> seen) throws Exception {
+ HttpServer server = HttpServer.create(new
InetSocketAddress("localhost", 0), 0);
+ server.createContext("/v1/data/authz/allow", exchange -> {
+ seen.set(exchange.getRequestHeaders().getFirst("Authorization"));
+ byte[] body = "{\"result\":true}".getBytes(StandardCharsets.UTF_8);
+ exchange.getResponseHeaders().add("Content-Type",
"application/json");
+ exchange.sendResponseHeaders(200, body.length);
+ try (OutputStream out = exchange.getResponseBody()) {
+ out.write(body);
+ }
+ });
+ server.start();
+ open.add(() -> server.stop(0));
+ return server.getAddress().getPort();
+ }
+
+ @Test
+ @Timeout(60)
+ void sendsTheBearerTokenWhenOneIsConfigured() throws Exception {
+ AtomicReference<String> seen = new AtomicReference<>();
+
+ template.request("opa:authz/allow?serverUrl=http://localhost:" +
recordingPort(seen) + "&bearerToken=s3cr3t",
+ e -> {
+ });
+
+ assertThat(seen.get()).isEqualTo("Bearer s3cr3t");
+ }
+
+ @Test
+ @Timeout(60)
+ void sendsNoAuthorizationAtAllWhenTheTokenIsEmpty() throws Exception {
+ // an unset placeholder resolves to "", and "Authorization: Bearer "
is not an absent header - it is a
+ // malformed credential, which a server enforcing tokens answers with
a 401 rather than ignoring
+ AtomicReference<String> seen = new AtomicReference<>("not called");
+
+ template.request("opa:authz/allow?serverUrl=http://localhost:" +
recordingPort(seen) + "&bearerToken=",
+ e -> {
+ });
+
+ assertThat(seen.get()).isNull();
+ }
+
+ @Test
+ @Timeout(60)
+ void failsClosedWhenTheServerAcceptsAndThenSaysNothing() throws Exception {
+ Exchange out = template.request(
+ "opa:authz/allow?serverUrl=http://localhost:" + mutePort() +
"&requestTimeout=500", e -> {
+ });
+
+
assertThat(out.getException()).isInstanceOf(OpaPolicyEvaluationException.class);
+
assertThat(out.getMessage().getHeader(OpaConstants.DECISION_ALLOW)).isNull();
+ }
+
+ @Test
+ @Timeout(60)
+ void proceedsOnTheSameStallWhenFailOpenIsSet() throws Exception {
+ // a timeout is a failure to reach a verdict, not a deny, so it is
handled like every other such failure
+ Exchange out = template.request(
+ "opa:authz/allow?serverUrl=http://localhost:" + mutePort() +
"&requestTimeout=500&failOpen=true",
+ e -> {
+ });
+
+ assertThat(out.getException()).isNull();
+
assertThat(out.getMessage().getHeader(OpaConstants.DECISION_ALLOW)).isEqualTo(true);
+ }
+}
diff --git
a/components/camel-opa/src/test/java/org/apache/camel/component/opa/security/OpaSecurityPolicyHealthCheckTest.java
b/components/camel-opa/src/test/java/org/apache/camel/component/opa/security/OpaSecurityPolicyHealthCheckTest.java
index 123f9fe90a0c..5042ea75839c 100644
---
a/components/camel-opa/src/test/java/org/apache/camel/component/opa/security/OpaSecurityPolicyHealthCheckTest.java
+++
b/components/camel-opa/src/test/java/org/apache/camel/component/opa/security/OpaSecurityPolicyHealthCheckTest.java
@@ -112,7 +112,7 @@ public class OpaSecurityPolicyHealthCheckTest extends
CamelTestSupport {
// non-canonical path with a redirect, and the probe's client does not
follow redirects, so a healthy
// server was reported DOWN. Every other test here uses a slash-free
URL, which is how it went unnoticed.
OpaSecurityPolicyHealthCheck check
- = new OpaSecurityPolicyHealthCheck(serverUrl + "/", null,
"authz/allow");
+ = new OpaSecurityPolicyHealthCheck(serverUrl + "/", null,
"authz/allow", null);
check.setEnabled(true);
HealthCheck.Result result = check.call(Map.of());
@@ -123,7 +123,7 @@ public class OpaSecurityPolicyHealthCheckTest extends
CamelTestSupport {
@Test
void reportsDownWhenTheServerCannotBeReached() {
OpaSecurityPolicyHealthCheck check
- = new OpaSecurityPolicyHealthCheck("http://localhost:1", null,
"authz/allow");
+ = new OpaSecurityPolicyHealthCheck("http://localhost:1", null,
"authz/allow", null);
check.setEnabled(true);
HealthCheck.Result result = check.call(Map.of());
diff --git a/components/camel-opa/src/test/resources/README-tls-fixture.md
b/components/camel-opa/src/test/resources/README-tls-fixture.md
new file mode 100644
index 000000000000..7c027c7af306
--- /dev/null
+++ b/components/camel-opa/src/test/resources/README-tls-fixture.md
@@ -0,0 +1,16 @@
+# TLS test fixture
+
+`opa-server.p12` is a throwaway self-signed keypair for
`OpaRestTransportTest`, used both as the HTTPS
+listener's key material and as the truststore the passing case is given. It is
not used anywhere outside the
+tests and grants nothing: the private key is public in this repository.
+
+Regenerate with:
+
+```sh
+keytool -genkeypair -alias opa -keyalg RSA -keysize 2048 -validity 36500 \
+ -dname "CN=localhost, OU=camel-opa tests, O=Apache Camel" \
+ -ext "SAN=dns:localhost,ip:127.0.0.1" \
+ -keystore opa-server.p12 -storetype PKCS12 -storepass changeit -keypass
changeit
+```
+
+The certificate is valid for 100 years, so the tests will not start failing on
an expiry nobody is watching.
diff --git a/components/camel-opa/src/test/resources/opa-server.p12
b/components/camel-opa/src/test/resources/opa-server.p12
new file mode 100644
index 000000000000..1a2db1d13df8
Binary files /dev/null and
b/components/camel-opa/src/test/resources/opa-server.p12 differ
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 7613350eb30a..3b1c544f969a 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
@@ -309,6 +309,27 @@ public interface OpaComponentBuilderFactory {
return this;
}
+
+ /**
+ * How long to wait for the connection to the OPA server to be
+ * established, in rest mode. The SDK's own transport applies no
timeout
+ * at all, so a server that never answers would otherwise park the
+ * calling thread indefinitely rather than letting the component fail
+ * closed.
+ *
+ * The option is a: <code>long</code> type.
+ *
+ * Default: 10000
+ * Group: advanced
+ *
+ * @param connectionTimeout the value to set
+ * @return the dsl builder
+ */
+ default OpaComponentBuilder connectionTimeout(long connectionTimeout) {
+ doSetProperty("connectionTimeout", connectionTimeout);
+ return this;
+ }
+
/**
* An existing OPAClient to use. When set, serverUrl and bearerToken
are
* ignored.
@@ -347,6 +368,26 @@ public interface OpaComponentBuilderFactory {
}
+ /**
+ * How long to wait for the decision once connected, in rest mode. A
+ * request that times out is an evaluation failure rather than a deny,
+ * so it fails closed - or proceeds when failOpen is set - like any
+ * other failure to reach a verdict.
+ *
+ * The option is a: <code>long</code> type.
+ *
+ * Default: 30000
+ * Group: advanced
+ *
+ * @param requestTimeout the value to set
+ * @return the dsl builder
+ */
+ default OpaComponentBuilder requestTimeout(long requestTimeout) {
+ doSetProperty("requestTimeout", requestTimeout);
+ return this;
+ }
+
+
/**
* Used for enabling or disabling all consumer based health checks from
* this component.
@@ -419,6 +460,43 @@ public interface OpaComponentBuilderFactory {
doSetProperty("failOpen", failOpen);
return this;
}
+
+ /**
+ * TLS configuration for the connection to the OPA server in rest mode.
+ * Needed to trust a server whose certificate comes from a private CA,
+ * and to present a client certificate to a server that requires mutual
+ * TLS - a SPIFFE X.509-SVID, for instance, so the workload
+ * authenticates to the policy decision point as itself.
+ *
+ * The option is a:
+ *
<code>org.apache.camel.support.jsse.SSLContextParameters</code>
type.
+ *
+ * Group: security
+ *
+ * @param sslContextParameters the value to set
+ * @return the dsl builder
+ */
+ default OpaComponentBuilder
sslContextParameters(org.apache.camel.support.jsse.SSLContextParameters
sslContextParameters) {
+ doSetProperty("sslContextParameters", sslContextParameters);
+ return this;
+ }
+
+
+ /**
+ * Enable usage of global SSL context parameters.
+ *
+ * The option is a: <code>boolean</code> type.
+ *
+ * Default: false
+ * Group: security
+ *
+ * @param useGlobalSslContextParameters the value to set
+ * @return the dsl builder
+ */
+ default OpaComponentBuilder useGlobalSslContextParameters(boolean
useGlobalSslContextParameters) {
+ doSetProperty("useGlobalSslContextParameters",
useGlobalSslContextParameters);
+ return this;
+ }
}
class OpaComponentBuilderImpl
@@ -452,12 +530,16 @@ public interface OpaComponentBuilderFactory {
case "serverUrl": getOrCreateConfiguration((OpaComponent)
component).setServerUrl((java.lang.String) value); return true;
case "autowiredEnabled": ((OpaComponent)
component).setAutowiredEnabled((boolean) value); return true;
case "borrowTimeout": getOrCreateConfiguration((OpaComponent)
component).setBorrowTimeout((long) value); return true;
+ case "connectionTimeout": getOrCreateConfiguration((OpaComponent)
component).setConnectionTimeout((long) value); return true;
case "opaClient": getOrCreateConfiguration((OpaComponent)
component).setOpaClient((com.styra.opa.OPAClient) value); return true;
case "poolSize": getOrCreateConfiguration((OpaComponent)
component).setPoolSize((int) value); return true;
+ case "requestTimeout": getOrCreateConfiguration((OpaComponent)
component).setRequestTimeout((long) value); return true;
case "healthCheckConsumerEnabled": ((OpaComponent)
component).setHealthCheckConsumerEnabled((boolean) value); return true;
case "healthCheckProducerEnabled": ((OpaComponent)
component).setHealthCheckProducerEnabled((boolean) value); return true;
case "bearerToken": getOrCreateConfiguration((OpaComponent)
component).setBearerToken((java.lang.String) value); return true;
case "failOpen": getOrCreateConfiguration((OpaComponent)
component).setFailOpen((boolean) value); return true;
+ case "sslContextParameters":
getOrCreateConfiguration((OpaComponent)
component).setSslContextParameters((org.apache.camel.support.jsse.SSLContextParameters)
value); return true;
+ case "useGlobalSslContextParameters": ((OpaComponent)
component).setUseGlobalSslContextParameters((boolean) value); return true;
default: return false;
}
}
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 12c227cff31e..789bb694faa4 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
@@ -274,6 +274,44 @@ public interface OpaEndpointBuilderFactory {
doSetProperty("failOpen", failOpen);
return this;
}
+ /**
+ * TLS configuration for the connection to the OPA server in rest mode.
+ * Needed to trust a server whose certificate comes from a private CA,
+ * and to present a client certificate to a server that requires mutual
+ * TLS - a SPIFFE X.509-SVID, for instance, so the workload
+ * authenticates to the policy decision point as itself.
+ *
+ * The option is a:
+ * <code>org.apache.camel.support.jsse.SSLContextParameters</code>
type.
+ *
+ * Group: security
+ *
+ * @param sslContextParameters the value to set
+ * @return the dsl builder
+ */
+ default OpaEndpointBuilder
sslContextParameters(org.apache.camel.support.jsse.SSLContextParameters
sslContextParameters) {
+ doSetProperty("sslContextParameters", sslContextParameters);
+ return this;
+ }
+ /**
+ * TLS configuration for the connection to the OPA server in rest mode.
+ * Needed to trust a server whose certificate comes from a private CA,
+ * and to present a client certificate to a server that requires mutual
+ * TLS - a SPIFFE X.509-SVID, for instance, so the workload
+ * authenticates to the policy decision point as itself.
+ *
+ * The option will be converted to a
+ * <code>org.apache.camel.support.jsse.SSLContextParameters</code>
type.
+ *
+ * Group: security
+ *
+ * @param sslContextParameters the value to set
+ * @return the dsl builder
+ */
+ default OpaEndpointBuilder sslContextParameters(String
sslContextParameters) {
+ doSetProperty("sslContextParameters", sslContextParameters);
+ return this;
+ }
}
/**
@@ -372,6 +410,44 @@ public interface OpaEndpointBuilderFactory {
doSetProperty("borrowTimeout", borrowTimeout);
return this;
}
+ /**
+ * How long to wait for the connection to the OPA server to be
+ * established, in rest mode. The SDK's own transport applies no
timeout
+ * at all, so a server that never answers would otherwise park the
+ * calling thread indefinitely rather than letting the component fail
+ * closed.
+ *
+ * The option is a: <code>long</code> type.
+ *
+ * Default: 10000
+ * Group: advanced
+ *
+ * @param connectionTimeout the value to set
+ * @return the dsl builder
+ */
+ default AdvancedOpaEndpointBuilder connectionTimeout(long
connectionTimeout) {
+ doSetProperty("connectionTimeout", connectionTimeout);
+ return this;
+ }
+ /**
+ * How long to wait for the connection to the OPA server to be
+ * established, in rest mode. The SDK's own transport applies no
timeout
+ * at all, so a server that never answers would otherwise park the
+ * calling thread indefinitely rather than letting the component fail
+ * closed.
+ *
+ * The option will be converted to a <code>long</code> type.
+ *
+ * Default: 10000
+ * Group: advanced
+ *
+ * @param connectionTimeout the value to set
+ * @return the dsl builder
+ */
+ default AdvancedOpaEndpointBuilder connectionTimeout(String
connectionTimeout) {
+ doSetProperty("connectionTimeout", connectionTimeout);
+ return this;
+ }
/**
* An existing OPAClient to use. When set, serverUrl and bearerToken
are
* ignored.
@@ -439,6 +515,42 @@ public interface OpaEndpointBuilderFactory {
doSetProperty("poolSize", poolSize);
return this;
}
+ /**
+ * How long to wait for the decision once connected, in rest mode. A
+ * request that times out is an evaluation failure rather than a deny,
+ * so it fails closed - or proceeds when failOpen is set - like any
+ * other failure to reach a verdict.
+ *
+ * The option is a: <code>long</code> type.
+ *
+ * Default: 30000
+ * Group: advanced
+ *
+ * @param requestTimeout the value to set
+ * @return the dsl builder
+ */
+ default AdvancedOpaEndpointBuilder requestTimeout(long requestTimeout)
{
+ doSetProperty("requestTimeout", requestTimeout);
+ return this;
+ }
+ /**
+ * How long to wait for the decision once connected, in rest mode. A
+ * request that times out is an evaluation failure rather than a deny,
+ * so it fails closed - or proceeds when failOpen is set - like any
+ * other failure to reach a verdict.
+ *
+ * The option will be converted to a <code>long</code> type.
+ *
+ * Default: 30000
+ * Group: advanced
+ *
+ * @param requestTimeout the value to set
+ * @return the dsl builder
+ */
+ default AdvancedOpaEndpointBuilder requestTimeout(String
requestTimeout) {
+ doSetProperty("requestTimeout", requestTimeout);
+ return this;
+ }
}
public interface OpaBuilders {