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 72c3c6fde1d9 CAMEL-24743: camel-opa - skip the producer health check
in wasm mode and warn on ignored server options (#26669)
72c3c6fde1d9 is described below
commit 72c3c6fde1d98cc66562f4c8f60221cb8a216e41
Author: Andrea Cosentino <[email protected]>
AuthorDate: Mon Sep 21 18:21:13 2026 +0200
CAMEL-24743: camel-opa - skip the producer health check in wasm mode and
warn on ignored server options (#26669)
Co-authored-by: Claude Opus 4.8 <[email protected]>
---
.../apache/camel/catalog/docs/opa-component.adoc | 11 +--
.../camel-opa/src/main/docs/opa-component.adoc | 11 +--
.../camel/component/opa/OpaConfiguration.java | 4 +-
.../apache/camel/component/opa/OpaEndpoint.java | 25 ++++++-
.../apache/camel/component/opa/OpaProducer.java | 7 +-
.../component/opa/OpaWasmModeValidationTest.java | 80 ++++++++++++++++++++++
6 files changed, 125 insertions(+), 13 deletions(-)
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 0a373925f776..89752125d723 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
@@ -259,9 +259,11 @@ Which to choose:
| Unreachable decision point | a real failure mode | cannot happen
|===
-`serverUrl`, `bearerToken` and `failOpen` have no meaning in `wasm` mode —
there is no server to address,
-authenticate to, or fail to reach — and no health check is registered, because
there is nothing to probe. An
-absent health check is not a healthy one.
+`serverUrl` and `bearerToken` have no meaning in `wasm` mode — there is no
server to address or authenticate
+to, and the endpoint warns at startup if either was set — and no health check
is registered, because there is
+nothing to probe. An absent health check is not a healthy one. `failOpen`
still applies: a `wasm` evaluation
+can fail (a busy pool, a bad bundle), and `failOpen` governs whether that
failure denies the exchange or lets
+it through, exactly as in `rest` mode.
The decision contract is identical in both modes: the same headers, the same
`allowKey` handling, and an
undefined decision fails closed the same way. A route does not need to know
which engine evaluated it.
@@ -350,7 +352,8 @@ That is why the policy's check is on by default rather than
opt-in like the prod
Neither check is registered when an `opaClient` was injected: that client may
point anywhere and neither the
endpoint nor the policy has a way to ask it where, so probing the configured
`serverUrl` would report on a server
-they may never talk to. The endpoint check is also skipped when no `serverUrl`
was given.
+they may never talk to. The endpoint check is also skipped when no `serverUrl`
was given, and in `wasm` mode,
+where the policy is evaluated in-process and there is no server to probe.
== Security notes
diff --git a/components/camel-opa/src/main/docs/opa-component.adoc
b/components/camel-opa/src/main/docs/opa-component.adoc
index 0a373925f776..89752125d723 100644
--- a/components/camel-opa/src/main/docs/opa-component.adoc
+++ b/components/camel-opa/src/main/docs/opa-component.adoc
@@ -259,9 +259,11 @@ Which to choose:
| Unreachable decision point | a real failure mode | cannot happen
|===
-`serverUrl`, `bearerToken` and `failOpen` have no meaning in `wasm` mode —
there is no server to address,
-authenticate to, or fail to reach — and no health check is registered, because
there is nothing to probe. An
-absent health check is not a healthy one.
+`serverUrl` and `bearerToken` have no meaning in `wasm` mode — there is no
server to address or authenticate
+to, and the endpoint warns at startup if either was set — and no health check
is registered, because there is
+nothing to probe. An absent health check is not a healthy one. `failOpen`
still applies: a `wasm` evaluation
+can fail (a busy pool, a bad bundle), and `failOpen` governs whether that
failure denies the exchange or lets
+it through, exactly as in `rest` mode.
The decision contract is identical in both modes: the same headers, the same
`allowKey` handling, and an
undefined decision fails closed the same way. A route does not need to know
which engine evaluated it.
@@ -350,7 +352,8 @@ That is why the policy's check is on by default rather than
opt-in like the prod
Neither check is registered when an `opaClient` was injected: that client may
point anywhere and neither the
endpoint nor the policy has a way to ask it where, so probing the configured
`serverUrl` would report on a server
-they may never talk to. The endpoint check is also skipped when no `serverUrl`
was given.
+they may never talk to. The endpoint check is also skipped when no `serverUrl`
was given, and in `wasm` mode,
+where the policy is evaluated in-process and there is no server to probe.
== Security notes
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 9c3753d796c0..392e98257a4f 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
@@ -26,8 +26,10 @@ import org.apache.camel.support.jsse.SSLContextParameters;
@UriParams
public class OpaConfiguration implements Cloneable {
+ static final String DEFAULT_SERVER_URL = "http://localhost:8181";
+
@UriParam(defaultValue = "http://localhost:8181")
- private String serverUrl = "http://localhost:8181";
+ private String serverUrl = DEFAULT_SERVER_URL;
@UriParam(defaultValue = "allow")
private String allowKey = "allow";
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 495f78f3184b..d8bafb72c338 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
@@ -31,6 +31,8 @@ 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;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* Evaluate Open Policy Agent (Rego) policies against an Exchange and record
the allow/deny decision on it.
@@ -40,9 +42,11 @@ import org.apache.camel.util.ObjectHelper;
headersClass = OpaConstants.class)
public class OpaEndpoint extends DefaultEndpoint {
- private static final String WASM_MODE = "wasm";
+ static final String WASM_MODE = "wasm";
private static final String REST_MODE = "rest";
+ private static final Logger LOG =
LoggerFactory.getLogger(OpaEndpoint.class);
+
@UriPath(description = "Path of the Rego rule head to evaluate, relative
to the OPA data document. For a rule"
+ " named allow in a policy declaring package
authz.orders, this is authz/orders/allow."
+ " The path is taken from the endpoint only: it is
deliberately not overridable by a"
@@ -67,6 +71,7 @@ public class OpaEndpoint extends DefaultEndpoint {
super.doStart();
String mode = configuration.getEvaluationMode();
if (WASM_MODE.equalsIgnoreCase(mode)) {
+ warnAboutIgnoredServerOptions();
evaluator = createWasmEvaluator();
} else if (!REST_MODE.equalsIgnoreCase(mode)) {
// silently falling back to rest would leave a typo'd mode running
against a server while quietly
@@ -115,6 +120,24 @@ public class OpaEndpoint extends DefaultEndpoint {
return sslContext;
}
+ /**
+ * {@code serverUrl} and {@code bearerToken} address and authenticate to
an OPA server, of which there is none in
+ * {@code wasm} mode, so they are ignored - a startup warning is clearer
than silence for an operator who set one
+ * and expects it to take effect. {@code failOpen} is deliberately not
among these: a {@code wasm} evaluation can
+ * still fail (a busy pool, a bad bundle), and {@code failOpen} governs
that outcome exactly as in {@code rest}
+ * mode, so it applies in both.
+ */
+ private void warnAboutIgnoredServerOptions() {
+ if (ObjectHelper.isNotEmpty(configuration.getBearerToken())) {
+ LOG.warn("bearerToken is ignored when evaluationMode=wasm: there
is no server to authenticate to");
+ }
+ if (ObjectHelper.isNotEmpty(configuration.getServerUrl())
+ &&
!OpaConfiguration.DEFAULT_SERVER_URL.equals(configuration.getServerUrl())) {
+ LOG.warn("serverUrl '{}' is ignored when evaluationMode=wasm: the
policy is evaluated in-process",
+ configuration.getServerUrl());
+ }
+ }
+
private OpaPolicyEvaluator createWasmEvaluator() throws Exception {
if (ObjectHelper.isEmpty(configuration.getPolicyBundle())) {
throw new IllegalArgumentException(
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 3d6e2e4c25a2..110d8367f88f 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
@@ -41,9 +41,10 @@ public class OpaProducer extends DefaultProducer {
super.doStart();
OpaConfiguration configuration = getEndpoint().getConfiguration();
- // an injected client can point anywhere, and the endpoint has no way
to ask it where; only probe a
- // server we were told the address of
- if (configuration.getOpaClient() != null ||
ObjectHelper.isEmpty(configuration.getServerUrl())) {
+ // an injected client can point anywhere, and the endpoint has no way
to ask it where; wasm mode evaluates
+ // in-process with no server at all - in neither case is there a
server we know the address of to probe
+ if (configuration.getOpaClient() != null ||
ObjectHelper.isEmpty(configuration.getServerUrl())
+ ||
OpaEndpoint.WASM_MODE.equalsIgnoreCase(configuration.getEvaluationMode())) {
return;
}
diff --git
a/components/camel-opa/src/test/java/org/apache/camel/component/opa/OpaWasmModeValidationTest.java
b/components/camel-opa/src/test/java/org/apache/camel/component/opa/OpaWasmModeValidationTest.java
new file mode 100644
index 000000000000..e4661972652d
--- /dev/null
+++
b/components/camel-opa/src/test/java/org/apache/camel/component/opa/OpaWasmModeValidationTest.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.opa;
+
+import java.util.List;
+
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.health.HealthCheck;
+import org.apache.camel.health.HealthCheckHelper;
+import org.apache.camel.health.WritableHealthCheckRepository;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+
+/**
+ * In {@code wasm} mode the policy is evaluated in-process, so there is no OPA
server to probe and no producer health
+ * check is registered. {@code failOpen}, on the other hand, still governs an
evaluation failure (a busy pool, a bad
+ * bundle) in {@code wasm} mode too, so it must not be rejected (CAMEL-24743).
+ */
+public class OpaWasmModeValidationTest extends CamelTestSupport {
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ // a rest-mode route registers a producer readiness check
(positive control), a wasm-mode route
+ // sharing the same policy path must not - the difference is
exactly what this test asserts
+
from("direct:rest").to("opa:authz/allow?serverUrl=http://opa-rest:8181");
+
from("direct:wasm").to("opa:authz/allow?evaluationMode=wasm&policyBundle=classpath:authz.wasm");
+ }
+ };
+ }
+
+ private List<HealthCheck> producerChecks() {
+ WritableHealthCheckRepository repository =
HealthCheckHelper.getHealthCheckRepository(
+ context, "producers", WritableHealthCheckRepository.class);
+ assertThat(repository).isNotNull();
+ // producer health checks are disabled globally by default, so enable
the repository to read them back
+ repository.setEnabled(true);
+ return repository.stream().toList();
+ }
+
+ @Test
+ void registersTheCheckForTheRestRouteButNotTheWasmRoute() {
+ List<HealthCheck> checks = producerChecks();
+ // exactly one check, and it is the rest route's - the wasm route
evaluates in-process with no server to probe
+ assertThat(checks).hasSize(1);
+ assertThat(checks.get(0).getId()).contains("opa-rest");
+ }
+
+ @Test
+ void acceptsFailOpenInWasmMode() {
+ // failOpen governs an evaluation failure (a busy pool, a bad bundle),
which happens in wasm too, so it is a
+ // valid option here and starting the route must not throw
+ assertThatCode(() -> context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("direct:failopen")
+
.to("opa:authz/allow?evaluationMode=wasm&policyBundle=classpath:authz.wasm&failOpen=true");
+ }
+ })).doesNotThrowAnyException();
+ }
+}