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 1f762d21591e CAMEL-24738: camel-opa - make a failOpen decision
distinguishable from a real one
1f762d21591e is described below
commit 1f762d21591e06b5ccb067b80a981234ee6c824a
Author: Andrea Cosentino <[email protected]>
AuthorDate: Wed Sep 23 10:32:33 2026 +0200
CAMEL-24738: camel-opa - make a failOpen decision distinguishable from a
real one
When failOpen lets an exchange proceed despite an evaluation failure it now
carries CamelOpaDecisionFailedOpen=true, so a route or an audit can tell a
fail-open allow apart from a genuine policy allow. The marker is set only
on the
deliberate failOpen path, cleared on entry like the other decision headers,
and
withheld from the OPA input document so a sender cannot inject it. It
applies to
both the producer and OpaSecurityPolicy, which share the evaluator.
Closes #26664
Co-authored-by: Claude Opus 4.8 <[email protected]>
---
.../org/apache/camel/catalog/components/opa.json | 3 +-
.../apache/camel/catalog/docs/opa-component.adoc | 24 ++++++++++-
.../org/apache/camel/component/opa/opa.json | 3 +-
.../camel-opa/src/main/docs/opa-component.adoc | 24 ++++++++++-
.../apache/camel/component/opa/OpaConstants.java | 8 ++++
.../camel/component/opa/OpaPolicyEvaluator.java | 10 ++++-
.../camel/component/opa/OpaProducerTest.java | 48 ++++++++++++++++++++++
.../opa/security/OpaSecurityPolicyTest.java | 23 +++++++++++
.../endpoint/dsl/OpaEndpointBuilderFactory.java | 16 ++++++++
9 files changed, 154 insertions(+), 5 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 27178ee61b22..099541bf5a47 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
@@ -50,7 +50,8 @@
"headers": {
"CamelOpaDecisionAllow": { "index": 0, "kind": "header", "displayName":
"", "group": "producer", "label": "producer", "required": false, "javaType":
"Boolean", "deprecated": false, "deprecationNote": "", "autowired": false,
"secret": false, "description": "The allow\/deny verdict of the policy
evaluation. Always overwritten by the component, so a value set by an inbound
message never survives into the route.", "constantName":
"org.apache.camel.component.opa.OpaConstants#DECISION_ALLOW" },
"CamelOpaDecision": { "index": 1, "kind": "header", "displayName": "",
"group": "producer", "label": "producer", "required": false, "javaType":
"Object", "deprecated": false, "deprecationNote": "", "autowired": false,
"secret": false, "description": "The raw decision document returned by OPA.
Useful for policies that return more than a boolean, such as obligations, row
filters or deny reasons.", "constantName":
"org.apache.camel.component.opa.OpaConstants#DECISION" },
- "CamelOpaPolicyPath": { "index": 2, "kind": "header", "displayName": "",
"group": "producer", "label": "producer", "required": false, "javaType":
"String", "deprecated": false, "deprecationNote": "", "autowired": false,
"secret": false, "description": "The policy path that was evaluated. Set by the
component for observability; it is not read as an input and cannot be used to
select a different policy.", "constantName":
"org.apache.camel.component.opa.OpaConstants#POLICY_PATH" }
+ "CamelOpaPolicyPath": { "index": 2, "kind": "header", "displayName": "",
"group": "producer", "label": "producer", "required": false, "javaType":
"String", "deprecated": false, "deprecationNote": "", "autowired": false,
"secret": false, "description": "The policy path that was evaluated. Set by the
component for observability; it is not read as an input and cannot be used to
select a different policy.", "constantName":
"org.apache.camel.component.opa.OpaConstants#POLICY_PATH" },
+ "CamelOpaDecisionFailedOpen": { "index": 3, "kind": "header",
"displayName": "", "group": "producer", "label": "producer", "required": false,
"javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Set to true only when the exchange
proceeded because failOpen is enabled and the policy could not be evaluated -
nothing authorized it. Absent on every decision an actual policy made, so a
route or an audit trail can tell the [...]
},
"properties": {
"policyPath": { "index": 0, "kind": "path", "displayName": "Policy Path",
"group": "producer", "label": "", "required": true, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Path of the Rego rule head
to evaluate, relative to the OPA data document. For a rule named allow in a
policy declaring package authz.orders, this is authz\/orders\/allow. The path
is taken from the endpoint only: i [...]
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 89752125d723..e4a344a4eb86 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
@@ -317,6 +317,7 @@ context.getRegistry().bind("opaTls", spiffe);
None of this applies in `wasm` mode, where there is no server to reach.
+[[failure-handling]]
== Failure handling
The component fails closed. If the policy cannot be evaluated at all — the OPA
server is unreachable, times out,
@@ -328,6 +329,25 @@ decision point available".
Setting `failOpen=true` reverses this and lets the exchange proceed when the
policy cannot be evaluated. It exists
for development and for non-critical policies, and should not be enabled in
production.
+An exchange that proceeds that way carries `CamelOpaDecisionFailedOpen=true`.
`CamelOpaDecisionAllow` is `true`
+in both cases, and on its own it cannot tell "a policy allowed this" from "no
policy ran and we were told to
+proceed" — which is exactly the distinction an audit trail needs. The marker
is set only on the fail-open path,
+so a route can branch on it and an operator can alert on its presence:
+
+[source,java]
+------------------------------------------------------------
+from("platform-http:/orders")
+ .to("opa:authz/orders/allow?failOpen=true")
+ .choice()
+ .when(header(OpaConstants.DECISION_FAILED_OPEN).isEqualTo(true))
+ .to("log:unauthorized?level=WARN")
+ .end()
+ .to("direct:orders")
+------------------------------------------------------------
+
+Like the other decision headers it is cleared before every evaluation, so a
message cannot arrive claiming
+`CamelOpaDecisionFailedOpen=false` and disguise an unauthorized exchange as
one a policy allowed.
+
Note that OPA reports an *undefined* decision — no rule matched and the policy
declares no default — as an error
rather than as a deny, so it fails closed as well. Give every decision rule a
default, as in the example above,
so the policy always returns a verdict.
@@ -348,7 +368,9 @@ more important of the two: a denied producer merely records
a verdict the route
`CamelAuthorizationException` and stops the exchange, so an unreachable server
there fails every message outright.
That is why the policy's check is on by default rather than opt-in like the
producer's. Set
`healthCheckEnabled=false` on the policy for a route that should stay ready
regardless — one running `failOpen`, say
-— in preference to hiding the check with `camel.health.exclude-pattern`.
+— in preference to hiding the check with `camel.health.exclude-pattern`. A
policy running `failOpen` marks the
+exchanges it let through with `CamelOpaDecisionFailedOpen`, exactly as the
producer does; the evaluator is shared,
+so everything in <<failure-handling>> about that header applies to
`.policy(...)` too.
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
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 27178ee61b22..099541bf5a47 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
@@ -50,7 +50,8 @@
"headers": {
"CamelOpaDecisionAllow": { "index": 0, "kind": "header", "displayName":
"", "group": "producer", "label": "producer", "required": false, "javaType":
"Boolean", "deprecated": false, "deprecationNote": "", "autowired": false,
"secret": false, "description": "The allow\/deny verdict of the policy
evaluation. Always overwritten by the component, so a value set by an inbound
message never survives into the route.", "constantName":
"org.apache.camel.component.opa.OpaConstants#DECISION_ALLOW" },
"CamelOpaDecision": { "index": 1, "kind": "header", "displayName": "",
"group": "producer", "label": "producer", "required": false, "javaType":
"Object", "deprecated": false, "deprecationNote": "", "autowired": false,
"secret": false, "description": "The raw decision document returned by OPA.
Useful for policies that return more than a boolean, such as obligations, row
filters or deny reasons.", "constantName":
"org.apache.camel.component.opa.OpaConstants#DECISION" },
- "CamelOpaPolicyPath": { "index": 2, "kind": "header", "displayName": "",
"group": "producer", "label": "producer", "required": false, "javaType":
"String", "deprecated": false, "deprecationNote": "", "autowired": false,
"secret": false, "description": "The policy path that was evaluated. Set by the
component for observability; it is not read as an input and cannot be used to
select a different policy.", "constantName":
"org.apache.camel.component.opa.OpaConstants#POLICY_PATH" }
+ "CamelOpaPolicyPath": { "index": 2, "kind": "header", "displayName": "",
"group": "producer", "label": "producer", "required": false, "javaType":
"String", "deprecated": false, "deprecationNote": "", "autowired": false,
"secret": false, "description": "The policy path that was evaluated. Set by the
component for observability; it is not read as an input and cannot be used to
select a different policy.", "constantName":
"org.apache.camel.component.opa.OpaConstants#POLICY_PATH" },
+ "CamelOpaDecisionFailedOpen": { "index": 3, "kind": "header",
"displayName": "", "group": "producer", "label": "producer", "required": false,
"javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Set to true only when the exchange
proceeded because failOpen is enabled and the policy could not be evaluated -
nothing authorized it. Absent on every decision an actual policy made, so a
route or an audit trail can tell the [...]
},
"properties": {
"policyPath": { "index": 0, "kind": "path", "displayName": "Policy Path",
"group": "producer", "label": "", "required": true, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Path of the Rego rule head
to evaluate, relative to the OPA data document. For a rule named allow in a
policy declaring package authz.orders, this is authz\/orders\/allow. The path
is taken from the endpoint only: i [...]
diff --git a/components/camel-opa/src/main/docs/opa-component.adoc
b/components/camel-opa/src/main/docs/opa-component.adoc
index 89752125d723..e4a344a4eb86 100644
--- a/components/camel-opa/src/main/docs/opa-component.adoc
+++ b/components/camel-opa/src/main/docs/opa-component.adoc
@@ -317,6 +317,7 @@ context.getRegistry().bind("opaTls", spiffe);
None of this applies in `wasm` mode, where there is no server to reach.
+[[failure-handling]]
== Failure handling
The component fails closed. If the policy cannot be evaluated at all — the OPA
server is unreachable, times out,
@@ -328,6 +329,25 @@ decision point available".
Setting `failOpen=true` reverses this and lets the exchange proceed when the
policy cannot be evaluated. It exists
for development and for non-critical policies, and should not be enabled in
production.
+An exchange that proceeds that way carries `CamelOpaDecisionFailedOpen=true`.
`CamelOpaDecisionAllow` is `true`
+in both cases, and on its own it cannot tell "a policy allowed this" from "no
policy ran and we were told to
+proceed" — which is exactly the distinction an audit trail needs. The marker
is set only on the fail-open path,
+so a route can branch on it and an operator can alert on its presence:
+
+[source,java]
+------------------------------------------------------------
+from("platform-http:/orders")
+ .to("opa:authz/orders/allow?failOpen=true")
+ .choice()
+ .when(header(OpaConstants.DECISION_FAILED_OPEN).isEqualTo(true))
+ .to("log:unauthorized?level=WARN")
+ .end()
+ .to("direct:orders")
+------------------------------------------------------------
+
+Like the other decision headers it is cleared before every evaluation, so a
message cannot arrive claiming
+`CamelOpaDecisionFailedOpen=false` and disguise an unauthorized exchange as
one a policy allowed.
+
Note that OPA reports an *undefined* decision — no rule matched and the policy
declares no default — as an error
rather than as a deny, so it fails closed as well. Give every decision rule a
default, as in the example above,
so the policy always returns a verdict.
@@ -348,7 +368,9 @@ more important of the two: a denied producer merely records
a verdict the route
`CamelAuthorizationException` and stops the exchange, so an unreachable server
there fails every message outright.
That is why the policy's check is on by default rather than opt-in like the
producer's. Set
`healthCheckEnabled=false` on the policy for a route that should stay ready
regardless — one running `failOpen`, say
-— in preference to hiding the check with `camel.health.exclude-pattern`.
+— in preference to hiding the check with `camel.health.exclude-pattern`. A
policy running `failOpen` marks the
+exchanges it let through with `CamelOpaDecisionFailedOpen`, exactly as the
producer does; the evaluator is shared,
+so everything in <<failure-handling>> about that header applies to
`.policy(...)` too.
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
diff --git
a/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaConstants.java
b/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaConstants.java
index 2c04119f583c..60811d4b9b83 100644
---
a/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaConstants.java
+++
b/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaConstants.java
@@ -39,6 +39,14 @@ public final class OpaConstants {
javaType = "String")
public static final String POLICY_PATH = HEADER_PREFIX + "PolicyPath";
+ @Metadata(label = "producer",
+ description = "Set to true only when the exchange proceeded
because failOpen is enabled and the policy"
+ + " could not be evaluated - nothing authorized
it. Absent on every decision an actual"
+ + " policy made, so a route or an audit trail can
tell the two apart rather than seeing"
+ + " the same CamelOpaDecisionAllow=true for both.",
+ javaType = "Boolean")
+ public static final String DECISION_FAILED_OPEN = HEADER_PREFIX +
"DecisionFailedOpen";
+
private OpaConstants() {
}
}
diff --git
a/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaPolicyEvaluator.java
b/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaPolicyEvaluator.java
index 927d186d1e76..e7ff69f0f5ea 100644
---
a/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaPolicyEvaluator.java
+++
b/components/camel-opa/src/main/java/org/apache/camel/component/opa/OpaPolicyEvaluator.java
@@ -100,6 +100,10 @@ public abstract class OpaPolicyEvaluator {
+ " enabled. Reason: {}",
policyPath, e.getMessage());
setDecisionHeaders(exchange, null, true);
+ // the verdict header alone cannot distinguish "a policy
allowed this" from "no policy ran and we
+ // were told to proceed"; an auditor asking which exchanges
went through unauthorized needs a
+ // signal it can filter on, not a log line
+
exchange.getMessage().setHeader(OpaConstants.DECISION_FAILED_OPEN, true);
return true;
}
throw new OpaPolicyEvaluationException(
@@ -233,6 +237,9 @@ public abstract class OpaPolicyEvaluator {
message.removeHeader(OpaConstants.DECISION_ALLOW);
message.removeHeader(OpaConstants.DECISION);
message.removeHeader(OpaConstants.POLICY_PATH);
+ // as attacker-settable as the verdict itself: left in place, a sender
could preload it false and make a
+ // fail-open read as a decision a policy actually made
+ message.removeHeader(OpaConstants.DECISION_FAILED_OPEN);
}
private void setDecisionHeaders(Exchange exchange, Object decision,
boolean allowed) {
@@ -269,7 +276,8 @@ public abstract class OpaPolicyEvaluator {
private static boolean isDecisionHeader(String name) {
return OpaConstants.DECISION_ALLOW.equalsIgnoreCase(name)
|| OpaConstants.DECISION.equalsIgnoreCase(name)
- || OpaConstants.POLICY_PATH.equalsIgnoreCase(name);
+ || OpaConstants.POLICY_PATH.equalsIgnoreCase(name)
+ || OpaConstants.DECISION_FAILED_OPEN.equalsIgnoreCase(name);
}
/**
diff --git
a/components/camel-opa/src/test/java/org/apache/camel/component/opa/OpaProducerTest.java
b/components/camel-opa/src/test/java/org/apache/camel/component/opa/OpaProducerTest.java
index 6127fe65fbf2..a3162cce859e 100644
---
a/components/camel-opa/src/test/java/org/apache/camel/component/opa/OpaProducerTest.java
+++
b/components/camel-opa/src/test/java/org/apache/camel/component/opa/OpaProducerTest.java
@@ -157,6 +157,52 @@ class OpaProducerTest extends CamelTestSupport {
assertThat(out.getMessage().getHeader(OpaConstants.DECISION_ALLOW)).isEqualTo(false);
}
+ @Test
+ void marksAnExchangeThatOnlyProceededBecauseOfFailOpen() throws Exception {
+ when(client.evaluate(eq(PATH), anyMap(),
eq(Object.class))).thenThrow(new OPAException("connection refused"));
+
+ Exchange out = template.request(ENDPOINT + "&failOpen=true", e -> {
+ });
+
+ assertThat(out.getException()).isNull();
+
assertThat(out.getMessage().getHeader(OpaConstants.DECISION_ALLOW)).isEqualTo(true);
+
assertThat(out.getMessage().getHeader(OpaConstants.DECISION_FAILED_OPEN)).isEqualTo(true);
+ }
+
+ @Test
+ void doesNotMarkADecisionAPolicyActuallyMade() throws Exception {
+ // the point of the marker is that it separates the two, so an allow
from a real policy must not carry it
+ givenDecision(Boolean.TRUE);
+
+ Exchange out = template.request(ENDPOINT + "&failOpen=true", e -> {
+ });
+
+
assertThat(out.getMessage().getHeader(OpaConstants.DECISION_ALLOW)).isEqualTo(true);
+
assertThat(out.getMessage().getHeader(OpaConstants.DECISION_FAILED_OPEN)).isNull();
+ }
+
+ @Test
+ void doesNotLetAnInboundMessageClaimItDidNotFailOpen() throws Exception {
+ // as settable by a sender as the verdict was: left in place,
"FailedOpen=false" would disguise an
+ // unauthorized exchange as one a policy allowed - which is the audit
trail this header exists to give
+ when(client.evaluate(eq(PATH), anyMap(),
eq(Object.class))).thenThrow(new OPAException("connection refused"));
+
+ Exchange out = template.request(ENDPOINT + "&failOpen=true",
+ e ->
e.getMessage().setHeader(OpaConstants.DECISION_FAILED_OPEN, false));
+
+
assertThat(out.getMessage().getHeader(OpaConstants.DECISION_FAILED_OPEN)).isEqualTo(true);
+ }
+
+ @Test
+ void clearsAClaimedFailOpenMarkerOnAnOrdinaryDecision() throws Exception {
+ givenDecision(Boolean.TRUE);
+
+ Exchange out = template.request(ENDPOINT,
+ e ->
e.getMessage().setHeader(OpaConstants.DECISION_FAILED_OPEN, true));
+
+
assertThat(out.getMessage().getHeader(OpaConstants.DECISION_FAILED_OPEN)).isNull();
+ }
+
@Test
void failsClosedWhenThePolicyCannotBeEvaluated() throws Exception {
when(client.evaluate(eq(PATH), anyMap(),
eq(Object.class))).thenThrow(new OPAException("connection refused"));
@@ -167,6 +213,8 @@ class OpaProducerTest extends CamelTestSupport {
assertThat(out.getException()).isInstanceOf(OpaPolicyEvaluationException.class)
.hasMessageContaining(PATH);
assertThat(out.getMessage().getHeader(OpaConstants.DECISION_ALLOW)).isNull();
+ // the marker is for the deliberate failOpen path only, not for any
exception the evaluator happens to hit
+
assertThat(out.getMessage().getHeader(OpaConstants.DECISION_FAILED_OPEN)).isNull();
}
@Test
diff --git
a/components/camel-opa/src/test/java/org/apache/camel/component/opa/security/OpaSecurityPolicyTest.java
b/components/camel-opa/src/test/java/org/apache/camel/component/opa/security/OpaSecurityPolicyTest.java
index 55589ec971a6..c36ed3a365f3 100644
---
a/components/camel-opa/src/test/java/org/apache/camel/component/opa/security/OpaSecurityPolicyTest.java
+++
b/components/camel-opa/src/test/java/org/apache/camel/component/opa/security/OpaSecurityPolicyTest.java
@@ -43,18 +43,25 @@ class OpaSecurityPolicyTest extends CamelTestSupport {
private final OPAClient client = mock(OPAClient.class);
private final OpaSecurityPolicy policy = new OpaSecurityPolicy();
+ private final OpaSecurityPolicy failOpenPolicy = new OpaSecurityPolicy();
@Override
protected RouteBuilder createRouteBuilder() {
policy.setPolicyPath(PATH);
policy.setOpaClient(client);
policy.setIncludeProperties("subject");
+ failOpenPolicy.setPolicyPath(PATH);
+ failOpenPolicy.setOpaClient(client);
+ failOpenPolicy.setFailOpen(true);
return new RouteBuilder() {
@Override
public void configure() {
from("direct:start")
.policy(policy)
.to("mock:result");
+ from("direct:failOpen")
+ .policy(failOpenPolicy)
+ .to("mock:failOpen");
}
};
}
@@ -111,9 +118,25 @@ class OpaSecurityPolicyTest extends CamelTestSupport {
assertThat(out.getException()).isInstanceOf(CamelAuthorizationException.class)
.hasCauseInstanceOf(OpaPolicyEvaluationException.class);
+ // the marker belongs to the deliberate failOpen path only - a denial
is not a fail-open
+
assertThat(out.getMessage().getHeader(OpaConstants.DECISION_FAILED_OPEN)).isNull();
result.assertIsSatisfied();
}
+ @Test
+ void marksAnExchangeTheFailOpenPolicyLetThrough() throws Exception {
+ when(client.evaluate(eq(PATH), anyMap(),
eq(Object.class))).thenThrow(new OPAException("connection refused"));
+ MockEndpoint result = getMockEndpoint("mock:failOpen");
+ result.expectedMessageCount(1);
+
+ Exchange out = template.request("direct:failOpen", e ->
e.getMessage().setBody("an order"));
+
+ assertThat(out.getException()).isNull();
+ result.assertIsSatisfied();
+
assertThat(out.getMessage().getHeader(OpaConstants.DECISION_ALLOW)).isEqualTo(true);
+
assertThat(out.getMessage().getHeader(OpaConstants.DECISION_FAILED_OPEN)).isEqualTo(true);
+ }
+
@SuppressWarnings("unchecked")
@Test
void handsThePolicyAnIdentityCarriedAsAnExchangeProperty() throws
Exception {
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 789bb694faa4..d71b57df2d9f 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
@@ -674,6 +674,22 @@ public interface OpaEndpointBuilderFactory {
public String opaPolicyPath() {
return "CamelOpaPolicyPath";
}
+ /**
+ * Set to true only when the exchange proceeded because failOpen is
+ * enabled and the policy could not be evaluated - nothing authorized
+ * it. Absent on every decision an actual policy made, so a route or an
+ * audit trail can tell the two apart rather than seeing the same
+ * CamelOpaDecisionAllow=true for both.
+ *
+ * The option is a: {@code Boolean} type.
+ *
+ * Group: producer
+ *
+ * @return the name of the header {@code OpaDecisionFailedOpen}.
+ */
+ public String opaDecisionFailedOpen() {
+ return "CamelOpaDecisionFailedOpen";
+ }
}
static OpaEndpointBuilder endpointBuilder(String componentName, String
path) {
class OpaEndpointBuilderImpl extends AbstractEndpointBuilder
implements OpaEndpointBuilder, AdvancedOpaEndpointBuilder {