This is an automated email from the ASF dual-hosted git repository.

davsclaus 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 d53bba35d81e CAMEL-26559: camel-opa, camel-openfga, camel-spiffe - add 
the security policy beans to the bean catalog (#29144)
d53bba35d81e is described below

commit d53bba35d81e35dc6c11e252a9fff8d24ca58007
Author: Andrea Cosentino <[email protected]>
AuthorDate: Wed Nov 4 19:53:02 2026 +0100

    CAMEL-26559: camel-opa, camel-openfga, camel-spiffe - add the security 
policy beans to the bean catalog (#29144)
    
    The OPA, OpenFGA and SPIFFE security policies are now listed in the bean 
catalog with their options, including useGlobalSslContextParameters, with 
generated configurers.
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../org/apache/camel/catalog/beans.properties      |   4 +
 .../camel/catalog/beans/OpaSecurityPolicy.json     |  16 ++
 .../camel/catalog/beans/OpenFgaSecurityPolicy.json |  16 ++
 .../catalog/beans/SpiffeSSLContextParameters.json  |  16 ++
 .../camel/catalog/beans/SpiffeSecurityPolicy.json  |  16 ++
 .../org/apache/camel/catalog/CamelCatalogTest.java |  27 +++
 .../opa/security/OpaSecurityPolicyConfigurer.java  | 157 +++++++++++++
 .../services/org/apache/camel/bean.properties      |   7 +
 .../org/apache/camel/bean/OpaSecurityPolicy.json   |  16 ++
 ....camel.component.opa.security.OpaSecurityPolicy |   2 +
 .../component/opa/security/OpaSecurityPolicy.java  |  73 ++++++
 .../opa/security/OpaSecurityPolicyBindingTest.java |  95 ++++++++
 .../security/OpenFgaSecurityPolicyConfigurer.java  | 178 +++++++++++++++
 .../services/org/apache/camel/bean.properties      |   7 +
 .../apache/camel/bean/OpenFgaSecurityPolicy.json   |  16 ++
 ...omponent.openfga.security.OpenFgaSecurityPolicy |   2 +
 .../openfga/security/OpenFgaSecurityPolicy.java    | 252 ++++++++++++++++-----
 .../security/OpenFgaSecurityPolicyOptionsTest.java | 178 +++++++++++++++
 .../SpiffeSSLContextParametersConfigurer.java      |  76 +++++++
 .../spiffe/SpiffeSecurityPolicyConfigurer.java     |  64 ++++++
 .../services/org/apache/camel/bean.properties      |   7 +
 .../camel/bean/SpiffeSSLContextParameters.json     |  16 ++
 .../apache/camel/bean/SpiffeSecurityPolicy.json    |  16 ++
 ...mel.component.spiffe.SpiffeSSLContextParameters |   2 +
 ...che.camel.component.spiffe.SpiffeSecurityPolicy |   2 +
 .../spiffe/SpiffeSSLContextParameters.java         |   9 +
 .../component/spiffe/SpiffeSecurityPolicy.java     |  19 ++
 .../component/spiffe/SpiffeBeanBindingTest.java    |  86 +++++++
 .../java/org/apache/camel/util/ArtifactUtils.java  |   8 +
 29 files changed, 1321 insertions(+), 62 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans.properties
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans.properties
index db6b94aa0746..b54d21b7ebc8 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans.properties
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans.properties
@@ -47,10 +47,14 @@ MemoryAggregationRepository
 MemoryIdempotentRepository
 MemoryKeyValueRepository
 MongoDbIdempotentRepository
+OpaSecurityPolicy
+OpenFgaSecurityPolicy
 OpensearchBulkRequestAggregationStrategy
 RedisAggregationRepository
 RedisKeyValueRepository
 SimpleScheduledRoutePolicy
+SpiffeSSLContextParameters
+SpiffeSecurityPolicy
 SpringCacheIdempotentRepository
 SpringRedisIdempotentRepository
 StringAggregationStrategy
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans/OpaSecurityPolicy.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans/OpaSecurityPolicy.json
new file mode 100644
index 000000000000..aa6c5e6247e8
--- /dev/null
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans/OpaSecurityPolicy.json
@@ -0,0 +1,16 @@
+{
+  "bean": {
+    "kind": "bean",
+    "name": "OpaSecurityPolicy",
+    "javaType": "org.apache.camel.component.opa.security.OpaSecurityPolicy",
+    "interfaceType": "org.apache.camel.spi.AuthorizationPolicy",
+    "title": "Opa Security Policy",
+    "description": "An authorization policy that asks Open Policy Agent 
whether the exchange may proceed. A deny throws CamelAuthorizationException, so 
the part of the route it guards does not run.",
+    "deprecated": false,
+    "groupId": "org.apache.camel",
+    "artifactId": "camel-opa",
+    "version": "4.24.0-SNAPSHOT",
+    "properties": { "serverUrl": { "index": 0, "kind": "property", 
"displayName": "Server Url", "required": false, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": "http:\/\/localhost:8181", "description": "The base URL of the 
OPA server, without the \/v1\/data suffix. Not used in wasm mode." }, 
"policyPath": { "index": 1, "kind": "property", "displayName": "Policy Path", 
"required": true, "type": "string", "javaT [...]
+  }
+}
+
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans/OpenFgaSecurityPolicy.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans/OpenFgaSecurityPolicy.json
new file mode 100644
index 000000000000..967a61fc9e11
--- /dev/null
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans/OpenFgaSecurityPolicy.json
@@ -0,0 +1,16 @@
+{
+  "bean": {
+    "kind": "bean",
+    "name": "OpenFgaSecurityPolicy",
+    "javaType": 
"org.apache.camel.component.openfga.security.OpenFgaSecurityPolicy",
+    "interfaceType": "org.apache.camel.spi.AuthorizationPolicy",
+    "title": "Open Fga Security Policy",
+    "description": "An authorization policy that asks OpenFGA whether a 
subject has a relation to an object before the part of the route it guards 
runs. A deny throws CamelAuthorizationException.",
+    "deprecated": false,
+    "groupId": "org.apache.camel",
+    "artifactId": "camel-openfga",
+    "version": "4.24.0-SNAPSHOT",
+    "properties": { "apiUrl": { "index": 0, "kind": "property", "displayName": 
"Api Url", "required": false, "type": "string", "javaType": "java.lang.String", 
"deprecated": false, "autowired": false, "secret": false, "defaultValue": 
"http:\/\/localhost:8080", "description": "The base URL of the OpenFGA HTTP 
API." }, "storeId": { "index": 1, "kind": "property", "displayName": "Store 
Id", "required": true, "type": "string", "javaType": "java.lang.String", 
"deprecated": false, "autowired":  [...]
+  }
+}
+
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans/SpiffeSSLContextParameters.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans/SpiffeSSLContextParameters.json
new file mode 100644
index 000000000000..ca445236ce47
--- /dev/null
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans/SpiffeSSLContextParameters.json
@@ -0,0 +1,16 @@
+{
+  "bean": {
+    "kind": "bean",
+    "name": "SpiffeSSLContextParameters",
+    "javaType": "org.apache.camel.component.spiffe.SpiffeSSLContextParameters",
+    "interfaceType": "org.apache.camel.support.jsse.SSLContextParameters",
+    "title": "Spiffe SSLContext Parameters",
+    "description": "SSLContextParameters whose SSLContext is backed by the 
SPIFFE Workload API: the X.509-SVID and the trust bundles are fetched live and 
rotated automatically, giving mutual TLS to any component that accepts an 
sslContextParameters reference. Set acceptedSpiffeIds or acceptAnySpiffeId; the 
inherited options, such as serverParameters.clientAuthentication, still apply.",
+    "deprecated": false,
+    "groupId": "org.apache.camel",
+    "artifactId": "camel-spiffe",
+    "version": "4.24.0-SNAPSHOT",
+    "properties": { "spiffeSocketPath": { "index": 0, "kind": "property", 
"displayName": "Spiffe Socket Path", "label": "security", "required": false, 
"type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": false, "description": "Path to the SPIFFE 
Workload API endpoint (for example unix:\/\/\/tmp\/agent.sock). When not set, 
the SPIFFE_ENDPOINT_SOCKET environment variable is used." }, 
"acceptedSpiffeIds": { "index": 1, "kind": "property", "dis [...]
+  }
+}
+
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans/SpiffeSecurityPolicy.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans/SpiffeSecurityPolicy.json
new file mode 100644
index 000000000000..f132d90abb38
--- /dev/null
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans/SpiffeSecurityPolicy.json
@@ -0,0 +1,16 @@
+{
+  "bean": {
+    "kind": "bean",
+    "name": "SpiffeSecurityPolicy",
+    "javaType": "org.apache.camel.component.spiffe.SpiffeSecurityPolicy",
+    "interfaceType": "org.apache.camel.spi.AuthorizationPolicy",
+    "title": "Spiffe Security Policy",
+    "description": "An authorization policy that authorizes a route segment on 
the caller's SPIFFE ID, read from the peer certificate that SPIFFE mutual TLS 
verified. A peer that is not accepted, or presents no verified SVID, is denied 
with CamelAuthorizationException.",
+    "deprecated": false,
+    "groupId": "org.apache.camel",
+    "artifactId": "camel-spiffe",
+    "version": "4.24.0-SNAPSHOT",
+    "properties": { "acceptedSpiffeIds": { "index": 0, "kind": "property", 
"displayName": "Accepted Spiffe Ids", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "description": "Comma-separated allow-list of the peer SPIFFE 
IDs this route segment authorizes, for example 
spiffe:\/\/example.org\/frontend. Set exactly one of acceptedSpiffeIds and 
acceptAnySpiffeId: setting both is rejected, and setting neither fai [...]
+  }
+}
+
diff --git 
a/catalog/camel-catalog/src/test/java/org/apache/camel/catalog/CamelCatalogTest.java
 
b/catalog/camel-catalog/src/test/java/org/apache/camel/catalog/CamelCatalogTest.java
index 473a9e7b81f6..f7dfaf283c34 100644
--- 
a/catalog/camel-catalog/src/test/java/org/apache/camel/catalog/CamelCatalogTest.java
+++ 
b/catalog/camel-catalog/src/test/java/org/apache/camel/catalog/CamelCatalogTest.java
@@ -2256,6 +2256,33 @@ public class CamelCatalogTest {
         assertEquals(7, model.getOptions().size());
     }
 
+    private static Stream<Arguments> securityBeans() {
+        return Stream.of(
+                Arguments.of("OpaSecurityPolicy", 
"org.apache.camel.component.opa.security.OpaSecurityPolicy", "camel-opa",
+                        "org.apache.camel.spi.AuthorizationPolicy", 
"policyPath"),
+                Arguments.of("OpenFgaSecurityPolicy", 
"org.apache.camel.component.openfga.security.OpenFgaSecurityPolicy",
+                        "camel-openfga", 
"org.apache.camel.spi.AuthorizationPolicy", "storeId"),
+                Arguments.of("SpiffeSecurityPolicy", 
"org.apache.camel.component.spiffe.SpiffeSecurityPolicy",
+                        "camel-spiffe", 
"org.apache.camel.spi.AuthorizationPolicy", "acceptedSpiffeIds"),
+                Arguments.of("SpiffeSSLContextParameters", 
"org.apache.camel.component.spiffe.SpiffeSSLContextParameters",
+                        "camel-spiffe", 
"org.apache.camel.support.jsse.SSLContextParameters", "acceptedSpiffeIds"));
+    }
+
+    // camel run looks up a bean class it cannot load by its simple name here, 
to find the jar to download
+    @ParameterizedTest
+    @MethodSource("securityBeans")
+    void testSecurityBeanNamesItsComponent(
+            String name, String javaType, String artifactId, String 
interfaceType, String option) {
+        PojoBeanModel model = catalog.pojoBeanModel(name);
+        assertNotNull(model, name);
+
+        assertEquals(javaType, model.getJavaType());
+        assertEquals("org.apache.camel", model.getGroupId());
+        assertEquals(artifactId, model.getArtifactId());
+        assertEquals(interfaceType, model.getInterfaceType());
+        assertTrue(model.getOptions().stream().anyMatch(o -> 
option.equals(o.getName())), option);
+    }
+
     @Test
     public void testFindApiReferenceNames() {
         List<String> names = catalog.findApiReferenceNames();
diff --git 
a/components/camel-opa/src/generated/java/org/apache/camel/component/opa/security/OpaSecurityPolicyConfigurer.java
 
b/components/camel-opa/src/generated/java/org/apache/camel/component/opa/security/OpaSecurityPolicyConfigurer.java
new file mode 100644
index 000000000000..422629ed63db
--- /dev/null
+++ 
b/components/camel-opa/src/generated/java/org/apache/camel/component/opa/security/OpaSecurityPolicyConfigurer.java
@@ -0,0 +1,157 @@
+/* Generated by camel build tools - do NOT edit this file! */
+package org.apache.camel.component.opa.security;
+
+import javax.annotation.processing.Generated;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.spi.ExtendedPropertyConfigurerGetter;
+import org.apache.camel.spi.PropertyConfigurerGetter;
+import org.apache.camel.spi.ConfigurerStrategy;
+import org.apache.camel.spi.GeneratedPropertyConfigurer;
+import org.apache.camel.util.CaseInsensitiveMap;
+import org.apache.camel.component.opa.security.OpaSecurityPolicy;
+
+/**
+ * Generated by camel build tools - do NOT edit this file!
+ */
+@Generated("org.apache.camel.maven.packaging.GenerateConfigurerMojo")
+@SuppressWarnings("unchecked")
+public class OpaSecurityPolicyConfigurer extends 
org.apache.camel.support.component.PropertyConfigurerSupport implements 
GeneratedPropertyConfigurer, PropertyConfigurerGetter {
+
+    @Override
+    public boolean configure(CamelContext camelContext, Object obj, String 
name, Object value, boolean ignoreCase) {
+        org.apache.camel.component.opa.security.OpaSecurityPolicy target = 
(org.apache.camel.component.opa.security.OpaSecurityPolicy) obj;
+        switch (ignoreCase ? name.toLowerCase(Locale.ROOT) : name) {
+        case "allowkey":
+        case "allowKey": target.setAllowKey(property(camelContext, 
java.lang.String.class, value)); return true;
+        case "bearertoken":
+        case "bearerToken": target.setBearerToken(property(camelContext, 
java.lang.String.class, value)); return true;
+        case "borrowtimeout":
+        case "borrowTimeout": target.setBorrowTimeout(property(camelContext, 
long.class, value)); return true;
+        case "connectiontimeout":
+        case "connectionTimeout": 
target.setConnectionTimeout(property(camelContext, long.class, value)); return 
true;
+        case "entrypoint": target.setEntrypoint(property(camelContext, 
java.lang.String.class, value)); return true;
+        case "evaluationmode":
+        case "evaluationMode": target.setEvaluationMode(property(camelContext, 
java.lang.String.class, value)); return true;
+        case "failopen":
+        case "failOpen": target.setFailOpen(property(camelContext, 
boolean.class, value)); return true;
+        case "healthcheckenabled":
+        case "healthCheckEnabled": 
target.setHealthCheckEnabled(property(camelContext, boolean.class, value)); 
return true;
+        case "includebody":
+        case "includeBody": target.setIncludeBody(property(camelContext, 
boolean.class, value)); return true;
+        case "includeheaders":
+        case "includeHeaders": target.setIncludeHeaders(property(camelContext, 
java.lang.String.class, value)); return true;
+        case "includeproperties":
+        case "includeProperties": 
target.setIncludeProperties(property(camelContext, java.lang.String.class, 
value)); return true;
+        case "opaclient":
+        case "opaClient": target.setOpaClient(property(camelContext, 
com.styra.opa.OPAClient.class, value)); return true;
+        case "policybundle":
+        case "policyBundle": target.setPolicyBundle(property(camelContext, 
java.lang.String.class, value)); return true;
+        case "policypath":
+        case "policyPath": target.setPolicyPath(property(camelContext, 
java.lang.String.class, value)); return true;
+        case "poolsize":
+        case "poolSize": target.setPoolSize(property(camelContext, int.class, 
value)); return true;
+        case "requesttimeout":
+        case "requestTimeout": target.setRequestTimeout(property(camelContext, 
long.class, value)); return true;
+        case "serverurl":
+        case "serverUrl": target.setServerUrl(property(camelContext, 
java.lang.String.class, value)); return true;
+        case "sslcontextparameters":
+        case "sslContextParameters": 
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;
+        }
+    }
+
+    @Override
+    public Class<?> getOptionType(String name, boolean ignoreCase) {
+        switch (ignoreCase ? name.toLowerCase(Locale.ROOT) : name) {
+        case "allowkey":
+        case "allowKey": return java.lang.String.class;
+        case "bearertoken":
+        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;
+        case "failopen":
+        case "failOpen": return boolean.class;
+        case "healthcheckenabled":
+        case "healthCheckEnabled": return boolean.class;
+        case "includebody":
+        case "includeBody": return boolean.class;
+        case "includeheaders":
+        case "includeHeaders": return java.lang.String.class;
+        case "includeproperties":
+        case "includeProperties": return java.lang.String.class;
+        case "opaclient":
+        case "opaClient": return com.styra.opa.OPAClient.class;
+        case "policybundle":
+        case "policyBundle": return java.lang.String.class;
+        case "policypath":
+        case "policyPath": 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;
+        }
+    }
+
+    @Override
+    public Object getOptionValue(Object obj, String name, boolean ignoreCase) {
+        org.apache.camel.component.opa.security.OpaSecurityPolicy target = 
(org.apache.camel.component.opa.security.OpaSecurityPolicy) obj;
+        switch (ignoreCase ? name.toLowerCase(Locale.ROOT) : name) {
+        case "allowkey":
+        case "allowKey": return target.getAllowKey();
+        case "bearertoken":
+        case "bearerToken": return target.getBearerToken();
+        case "borrowtimeout":
+        case "borrowTimeout": return target.getBorrowTimeout();
+        case "connectiontimeout":
+        case "connectionTimeout": return target.getConnectionTimeout();
+        case "entrypoint": return target.getEntrypoint();
+        case "evaluationmode":
+        case "evaluationMode": return target.getEvaluationMode();
+        case "failopen":
+        case "failOpen": return target.isFailOpen();
+        case "healthcheckenabled":
+        case "healthCheckEnabled": return target.isHealthCheckEnabled();
+        case "includebody":
+        case "includeBody": return target.isIncludeBody();
+        case "includeheaders":
+        case "includeHeaders": return target.getIncludeHeaders();
+        case "includeproperties":
+        case "includeProperties": return target.getIncludeProperties();
+        case "opaclient":
+        case "opaClient": return target.getOpaClient();
+        case "policybundle":
+        case "policyBundle": return target.getPolicyBundle();
+        case "policypath":
+        case "policyPath": return target.getPolicyPath();
+        case "poolsize":
+        case "poolSize": return target.getPoolSize();
+        case "requesttimeout":
+        case "requestTimeout": return target.getRequestTimeout();
+        case "serverurl":
+        case "serverUrl": return target.getServerUrl();
+        case "sslcontextparameters":
+        case "sslContextParameters": return target.getSslContextParameters();
+        case "useglobalsslcontextparameters":
+        case "useGlobalSslContextParameters": return 
target.isUseGlobalSslContextParameters();
+        default: return null;
+        }
+    }
+}
+
diff --git 
a/components/camel-opa/src/generated/resources/META-INF/services/org/apache/camel/bean.properties
 
b/components/camel-opa/src/generated/resources/META-INF/services/org/apache/camel/bean.properties
new file mode 100644
index 000000000000..ab65de040613
--- /dev/null
+++ 
b/components/camel-opa/src/generated/resources/META-INF/services/org/apache/camel/bean.properties
@@ -0,0 +1,7 @@
+# Generated by camel build tools - do NOT edit this file!
+bean=OpaSecurityPolicy
+groupId=org.apache.camel
+artifactId=camel-opa
+version=4.24.0-SNAPSHOT
+projectName=Camel :: OPA
+projectDescription=Camel Open Policy Agent Component
diff --git 
a/components/camel-opa/src/generated/resources/META-INF/services/org/apache/camel/bean/OpaSecurityPolicy.json
 
b/components/camel-opa/src/generated/resources/META-INF/services/org/apache/camel/bean/OpaSecurityPolicy.json
new file mode 100644
index 000000000000..aa6c5e6247e8
--- /dev/null
+++ 
b/components/camel-opa/src/generated/resources/META-INF/services/org/apache/camel/bean/OpaSecurityPolicy.json
@@ -0,0 +1,16 @@
+{
+  "bean": {
+    "kind": "bean",
+    "name": "OpaSecurityPolicy",
+    "javaType": "org.apache.camel.component.opa.security.OpaSecurityPolicy",
+    "interfaceType": "org.apache.camel.spi.AuthorizationPolicy",
+    "title": "Opa Security Policy",
+    "description": "An authorization policy that asks Open Policy Agent 
whether the exchange may proceed. A deny throws CamelAuthorizationException, so 
the part of the route it guards does not run.",
+    "deprecated": false,
+    "groupId": "org.apache.camel",
+    "artifactId": "camel-opa",
+    "version": "4.24.0-SNAPSHOT",
+    "properties": { "serverUrl": { "index": 0, "kind": "property", 
"displayName": "Server Url", "required": false, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": "http:\/\/localhost:8181", "description": "The base URL of the 
OPA server, without the \/v1\/data suffix. Not used in wasm mode." }, 
"policyPath": { "index": 1, "kind": "property", "displayName": "Policy Path", 
"required": true, "type": "string", "javaT [...]
+  }
+}
+
diff --git 
a/components/camel-opa/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.component.opa.security.OpaSecurityPolicy
 
b/components/camel-opa/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.component.opa.security.OpaSecurityPolicy
new file mode 100644
index 000000000000..8976a9ba14da
--- /dev/null
+++ 
b/components/camel-opa/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.component.opa.security.OpaSecurityPolicy
@@ -0,0 +1,2 @@
+# Generated by camel build tools - do NOT edit this file!
+class=org.apache.camel.component.opa.security.OpaSecurityPolicyConfigurer
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 e60e1406e198..af4f7bc11a71 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
@@ -33,6 +33,8 @@ import org.apache.camel.component.opa.OpaRestEvaluator;
 import org.apache.camel.component.opa.OpaWasmEvaluator;
 import org.apache.camel.health.HealthCheckRegistry;
 import org.apache.camel.spi.AuthorizationPolicy;
+import org.apache.camel.spi.Configurer;
+import org.apache.camel.spi.Metadata;
 import org.apache.camel.support.jsse.SSLContextParameters;
 import org.apache.camel.util.ObjectHelper;
 import org.apache.camel.util.StringHelper;
@@ -54,6 +56,11 @@ import org.slf4j.LoggerFactory;
  * {@code onException} machinery applies. The decision is also recorded on the 
exchange in the {@code CamelOpaDecision}
  * headers.
  */
+@Metadata(label = "bean",
+          description = "An authorization policy that asks Open Policy Agent 
whether the exchange may proceed. A deny throws"
+                        + " CamelAuthorizationException, so the part of the 
route it guards does not run.",
+          annotations = { 
"interfaceName=org.apache.camel.spi.AuthorizationPolicy" })
+@Configurer(metadataOnly = true)
 public class OpaSecurityPolicy implements AuthorizationPolicy {
 
     private static final Logger LOG = 
LoggerFactory.getLogger(OpaSecurityPolicy.class);
@@ -62,25 +69,91 @@ public class OpaSecurityPolicy implements 
AuthorizationPolicy {
     private static final String REST_MODE = "rest";
     private static final String DEFAULT_SERVER_URL = "http://localhost:8181";;
 
+    @Metadata(defaultValue = DEFAULT_SERVER_URL,
+              description = "The base URL of the OPA server, without the 
/v1/data suffix. Not used in wasm mode.")
     private String serverUrl = DEFAULT_SERVER_URL;
+    @Metadata(required = true,
+              description = "Path of the Rego rule head to evaluate, relative 
to the OPA data document, for example"
+                            + " authz/orders/allow. It is fixed by the policy: 
an inbound message cannot select which"
+                            + " rule judges it.")
     private String policyPath;
+    @Metadata(defaultValue = "allow",
+              description = "The key to read the verdict from when the policy 
returns an object rather than a plain"
+                            + " boolean. A dotted path such as result.allow 
reaches a verdict nested inside the document.")
     private String allowKey = "allow";
+    @Metadata(defaultValue = "*",
+              description = "Comma-separated list of message header names to 
send to OPA in the input document. The"
+                            + " default sends every header except 
Authorization, Proxy-Authorization, Cookie and"
+                            + " Set-Cookie, which carry a caller credential 
and are sent only when named here.")
     private String includeHeaders = "*";
+    @Metadata(description = "Comma-separated list of exchange property names 
to send to OPA in the input document, or *"
+                            + " for all of them. Empty by default. Use it to 
hand the policy an identity an earlier"
+                            + " authentication step stored as an exchange 
property, such as CamelKeycloakTokenSubject or"
+                            + " CamelSpiffePeerId.")
     private String includeProperties;
+    @Metadata(defaultValue = "false",
+              description = "Whether to send the message body to OPA as part 
of the input document.")
     private boolean includeBody;
+    @Metadata(label = "security", security = "secret",
+              description = "Bearer token sent to the OPA server in the 
Authorization header, for an OPA instance that"
+                            + " has its API authentication enabled. Not used 
in wasm mode.")
     private String bearerToken;
+    @Metadata(label = "security", defaultValue = "false", security = 
"insecure:dev",
+              description = "Whether to let the exchange proceed when the 
policy decision point is unavailable: the OPA"
+                            + " server cannot be reached, times out, or a 
gateway in front of it answers 502, 503, 504 or"
+                            + " 429, or in wasm mode no instance frees up 
within borrowTimeout. It never applies to an"
+                            + " answer: an undefined decision, a rejected 
request and an error evaluating the policy fail"
+                            + " closed even when this is set. Do not enable 
this in production.")
     private boolean failOpen;
+    @Metadata(defaultValue = REST_MODE, enums = "rest,wasm",
+              description = "How the policy is evaluated. rest asks a running 
OPA server over its Data API; wasm"
+                            + " evaluates a WebAssembly bundle in-process, 
with no server involved. serverUrl,"
+                            + " bearerToken, connectionTimeout, 
requestTimeout, sslContextParameters and the readiness"
+                            + " check do not apply in wasm mode.")
     private String evaluationMode = REST_MODE;
+    @Metadata(description = "The WebAssembly policy to evaluate in wasm mode, 
as produced by opa build -t wasm: a file:,"
+                            + " classpath: or http: location holding either 
the bundle.tar.gz that opa build emits or a"
+                            + " bare .wasm module. Required when 
evaluationMode is wasm.")
     private String policyBundle;
+    @Metadata(description = "The compiled entrypoint to evaluate in wasm mode, 
fixed when the bundle is built with opa"
+                            + " build -e. Defaults to the policy path, which 
is the name opa build gives it.")
     private String entrypoint;
+    @Metadata(label = "advanced", defaultValue = "8",
+              description = "How many WebAssembly instances to pool in wasm 
mode. An instance is not thread-safe, so this"
+                            + " bounds how many exchanges are authorized at 
once.")
     private int poolSize = 8;
+    @Metadata(label = "advanced", defaultValue = "30000",
+              description = "How long, in milliseconds, an exchange waits for 
a free WebAssembly instance in wasm mode."
+                            + " Running out of time is an evaluation failure 
rather than a deny: it fails closed, or"
+                            + " proceeds when failOpen is set.")
     private long borrowTimeout = 30000;
+    @Metadata(label = "advanced",
+              description = "An existing OPAClient to use instead of building 
one from serverUrl, bearerToken and"
+                            + " sslContextParameters. No readiness check is 
registered for it.")
     private OPAClient opaClient;
 
+    @Metadata(label = "advanced", defaultValue = "true",
+              description = "Whether to register a readiness check for the OPA 
server this policy queries. The policy"
+                            + " denies every exchange it guards while the 
server is unreachable, so a route that cannot"
+                            + " reach it is not ready.")
     private boolean healthCheckEnabled = true;
+    @Metadata(label = "advanced", defaultValue = "10000",
+              description = "How long to wait, in milliseconds, for the 
connection to the OPA server to be established.")
     private long connectionTimeout = 10000;
+    @Metadata(label = "advanced", defaultValue = "30000",
+              description = "How long to wait, in milliseconds, for the 
decision once connected. A request that times"
+                            + " out is an evaluation failure rather than a 
deny: it fails closed, or proceeds when"
+                            + " failOpen is set.")
     private long requestTimeout = 30000;
+    @Metadata(label = "security",
+              description = "TLS configuration for the connection to the OPA 
server, to trust a server whose"
+                            + " certificate comes from a private CA or to 
present a client certificate to one that"
+                            + " requires mutual TLS. Takes precedence over 
useGlobalSslContextParameters.")
     private SSLContextParameters sslContextParameters;
+    @Metadata(label = "security", defaultValue = "false",
+              description = "Whether to use the global SSL context parameters, 
the context-wide SSLContextParameters"
+                            + " camel-main builds from camel.ssl.*, when 
sslContextParameters is not set. Not used in"
+                            + " wasm mode.")
     private boolean useGlobalSslContextParameters;
 
     private volatile OpaPolicyEvaluator evaluator;
diff --git 
a/components/camel-opa/src/test/java/org/apache/camel/component/opa/security/OpaSecurityPolicyBindingTest.java
 
b/components/camel-opa/src/test/java/org/apache/camel/component/opa/security/OpaSecurityPolicyBindingTest.java
new file mode 100644
index 000000000000..3ea487c8b1f1
--- /dev/null
+++ 
b/components/camel-opa/src/test/java/org/apache/camel/component/opa/security/OpaSecurityPolicyBindingTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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.security;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.support.PropertyBindingSupport;
+import org.apache.camel.support.jsse.SSLContextParameters;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Binds the policy the way a YAML {@code beans:} entry does, where every 
value arrives as a string.
+ */
+class OpaSecurityPolicyBindingTest {
+
+    @Test
+    void bindsTheOptionsAsAYamlBeanDefinitionGivesThem() throws Exception {
+        try (CamelContext context = new DefaultCamelContext()) {
+            context.start();
+            SSLContextParameters tls = new SSLContextParameters();
+            context.getRegistry().bind("tls", tls);
+            Map<String, Object> properties = new LinkedHashMap<>();
+            properties.put("serverUrl", "https://opa:8181";);
+            properties.put("policyPath", "authz/orders/allow");
+            properties.put("allowKey", "result.allow");
+            properties.put("includeHeaders", "X-Tenant,Content-Type");
+            properties.put("includeProperties", "CamelSpiffePeerId");
+            properties.put("includeBody", "true");
+            properties.put("bearerToken", "s3cr3t");
+            properties.put("failOpen", "false");
+            properties.put("connectionTimeout", "2000");
+            properties.put("requestTimeout", "3000");
+            properties.put("healthCheckEnabled", "false");
+            properties.put("sslContextParameters", "#bean:tls");
+
+            OpaSecurityPolicy policy = new OpaSecurityPolicy();
+            PropertyBindingSupport.setPropertiesOnTarget(context, policy, 
properties);
+
+            assertThat(policy.getServerUrl()).isEqualTo("https://opa:8181";);
+            assertThat(policy.getPolicyPath()).isEqualTo("authz/orders/allow");
+            assertThat(policy.getAllowKey()).isEqualTo("result.allow");
+            
assertThat(policy.getIncludeHeaders()).isEqualTo("X-Tenant,Content-Type");
+            
assertThat(policy.getIncludeProperties()).isEqualTo("CamelSpiffePeerId");
+            assertThat(policy.isIncludeBody()).isTrue();
+            assertThat(policy.getBearerToken()).isEqualTo("s3cr3t");
+            assertThat(policy.isFailOpen()).isFalse();
+            assertThat(policy.getConnectionTimeout()).isEqualTo(2000L);
+            assertThat(policy.getRequestTimeout()).isEqualTo(3000L);
+            assertThat(policy.isHealthCheckEnabled()).isFalse();
+            assertThat(policy.getSslContextParameters()).isSameAs(tls);
+        }
+    }
+
+    @Test
+    void bindsTheWasmOptions() throws Exception {
+        try (CamelContext context = new DefaultCamelContext()) {
+            context.start();
+            Map<String, Object> properties = new LinkedHashMap<>();
+            properties.put("policyPath", "authz/orders/allow");
+            properties.put("evaluationMode", "wasm");
+            properties.put("policyBundle", "classpath:orders-bundle.tar.gz");
+            properties.put("entrypoint", "authz/orders/allow");
+            properties.put("poolSize", "4");
+            properties.put("borrowTimeout", "1500");
+
+            OpaSecurityPolicy policy = new OpaSecurityPolicy();
+            PropertyBindingSupport.setPropertiesOnTarget(context, policy, 
properties);
+
+            assertThat(policy.getEvaluationMode()).isEqualTo("wasm");
+            
assertThat(policy.getPolicyBundle()).isEqualTo("classpath:orders-bundle.tar.gz");
+            assertThat(policy.getEntrypoint()).isEqualTo("authz/orders/allow");
+            assertThat(policy.getPoolSize()).isEqualTo(4);
+            assertThat(policy.getBorrowTimeout()).isEqualTo(1500L);
+        }
+    }
+}
diff --git 
a/components/camel-openfga/src/generated/java/org/apache/camel/component/openfga/security/OpenFgaSecurityPolicyConfigurer.java
 
b/components/camel-openfga/src/generated/java/org/apache/camel/component/openfga/security/OpenFgaSecurityPolicyConfigurer.java
new file mode 100644
index 000000000000..d6285069ca38
--- /dev/null
+++ 
b/components/camel-openfga/src/generated/java/org/apache/camel/component/openfga/security/OpenFgaSecurityPolicyConfigurer.java
@@ -0,0 +1,178 @@
+/* Generated by camel build tools - do NOT edit this file! */
+package org.apache.camel.component.openfga.security;
+
+import javax.annotation.processing.Generated;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.spi.ExtendedPropertyConfigurerGetter;
+import org.apache.camel.spi.PropertyConfigurerGetter;
+import org.apache.camel.spi.ConfigurerStrategy;
+import org.apache.camel.spi.GeneratedPropertyConfigurer;
+import org.apache.camel.util.CaseInsensitiveMap;
+import org.apache.camel.component.openfga.security.OpenFgaSecurityPolicy;
+
+/**
+ * Generated by camel build tools - do NOT edit this file!
+ */
+@Generated("org.apache.camel.maven.packaging.GenerateConfigurerMojo")
+@SuppressWarnings("unchecked")
+public class OpenFgaSecurityPolicyConfigurer extends 
org.apache.camel.support.component.PropertyConfigurerSupport implements 
GeneratedPropertyConfigurer, PropertyConfigurerGetter {
+
+    @Override
+    public boolean configure(CamelContext camelContext, Object obj, String 
name, Object value, boolean ignoreCase) {
+        org.apache.camel.component.openfga.security.OpenFgaSecurityPolicy 
target = (org.apache.camel.component.openfga.security.OpenFgaSecurityPolicy) 
obj;
+        switch (ignoreCase ? name.toLowerCase(Locale.ROOT) : name) {
+        case "apiaudience":
+        case "apiAudience": target.setApiAudience(property(camelContext, 
java.lang.String.class, value)); return true;
+        case "apitoken":
+        case "apiToken": target.setApiToken(property(camelContext, 
java.lang.String.class, value)); return true;
+        case "apitokenissuer":
+        case "apiTokenIssuer": target.setApiTokenIssuer(property(camelContext, 
java.lang.String.class, value)); return true;
+        case "apiurl":
+        case "apiUrl": target.setApiUrl(property(camelContext, 
java.lang.String.class, value)); return true;
+        case "authorizationmodelid":
+        case "authorizationModelId": 
target.setAuthorizationModelId(property(camelContext, java.lang.String.class, 
value)); return true;
+        case "clientid":
+        case "clientId": target.setClientId(property(camelContext, 
java.lang.String.class, value)); return true;
+        case "clientsecret":
+        case "clientSecret": target.setClientSecret(property(camelContext, 
java.lang.String.class, value)); return true;
+        case "conditioncontext":
+        case "conditionContext": 
target.setConditionContext(property(camelContext, java.util.Map.class, value)); 
return true;
+        case "connecttimeout":
+        case "connectTimeout": target.setConnectTimeout(property(camelContext, 
long.class, value)); return true;
+        case "consistency": target.setConsistency(property(camelContext, 
java.lang.String.class, value)); return true;
+        case "contextualtuples":
+        case "contextualTuples": 
target.setContextualTuples(property(camelContext, java.lang.String.class, 
value)); return true;
+        case "failopen":
+        case "failOpen": target.setFailOpen(property(camelContext, 
boolean.class, value)); return true;
+        case "healthcheckenabled":
+        case "healthCheckEnabled": 
target.setHealthCheckEnabled(property(camelContext, boolean.class, value)); 
return true;
+        case "maxretries":
+        case "maxRetries": target.setMaxRetries(property(camelContext, 
int.class, value)); return true;
+        case "object": target.setObject(property(camelContext, 
java.lang.String.class, value)); return true;
+        case "openfgaclient":
+        case "openFgaClient": target.setOpenFgaClient(property(camelContext, 
dev.openfga.sdk.api.client.OpenFgaClient.class, value)); return true;
+        case "readtimeout":
+        case "readTimeout": target.setReadTimeout(property(camelContext, 
long.class, value)); return true;
+        case "relation": target.setRelation(property(camelContext, 
java.lang.String.class, value)); return true;
+        case "scopes": target.setScopes(property(camelContext, 
java.lang.String.class, value)); return true;
+        case "sslcontextparameters":
+        case "sslContextParameters": 
target.setSslContextParameters(property(camelContext, 
org.apache.camel.support.jsse.SSLContextParameters.class, value)); return true;
+        case "storeid":
+        case "storeId": target.setStoreId(property(camelContext, 
java.lang.String.class, value)); return true;
+        case "useglobalsslcontextparameters":
+        case "useGlobalSslContextParameters": 
target.setUseGlobalSslContextParameters(property(camelContext, boolean.class, 
value)); return true;
+        case "user": target.setUser(property(camelContext, 
java.lang.String.class, value)); return true;
+        default: return false;
+        }
+    }
+
+    @Override
+    public Class<?> getOptionType(String name, boolean ignoreCase) {
+        switch (ignoreCase ? name.toLowerCase(Locale.ROOT) : name) {
+        case "apiaudience":
+        case "apiAudience": return java.lang.String.class;
+        case "apitoken":
+        case "apiToken": return java.lang.String.class;
+        case "apitokenissuer":
+        case "apiTokenIssuer": return java.lang.String.class;
+        case "apiurl":
+        case "apiUrl": return java.lang.String.class;
+        case "authorizationmodelid":
+        case "authorizationModelId": return java.lang.String.class;
+        case "clientid":
+        case "clientId": return java.lang.String.class;
+        case "clientsecret":
+        case "clientSecret": return java.lang.String.class;
+        case "conditioncontext":
+        case "conditionContext": return java.util.Map.class;
+        case "connecttimeout":
+        case "connectTimeout": return long.class;
+        case "consistency": return java.lang.String.class;
+        case "contextualtuples":
+        case "contextualTuples": return java.lang.String.class;
+        case "failopen":
+        case "failOpen": return boolean.class;
+        case "healthcheckenabled":
+        case "healthCheckEnabled": return boolean.class;
+        case "maxretries":
+        case "maxRetries": return int.class;
+        case "object": return java.lang.String.class;
+        case "openfgaclient":
+        case "openFgaClient": return 
dev.openfga.sdk.api.client.OpenFgaClient.class;
+        case "readtimeout":
+        case "readTimeout": return long.class;
+        case "relation": return java.lang.String.class;
+        case "scopes": return java.lang.String.class;
+        case "sslcontextparameters":
+        case "sslContextParameters": return 
org.apache.camel.support.jsse.SSLContextParameters.class;
+        case "storeid":
+        case "storeId": return java.lang.String.class;
+        case "useglobalsslcontextparameters":
+        case "useGlobalSslContextParameters": return boolean.class;
+        case "user": return java.lang.String.class;
+        default: return null;
+        }
+    }
+
+    @Override
+    public Object getOptionValue(Object obj, String name, boolean ignoreCase) {
+        org.apache.camel.component.openfga.security.OpenFgaSecurityPolicy 
target = (org.apache.camel.component.openfga.security.OpenFgaSecurityPolicy) 
obj;
+        switch (ignoreCase ? name.toLowerCase(Locale.ROOT) : name) {
+        case "apiaudience":
+        case "apiAudience": return target.getApiAudience();
+        case "apitoken":
+        case "apiToken": return target.getApiToken();
+        case "apitokenissuer":
+        case "apiTokenIssuer": return target.getApiTokenIssuer();
+        case "apiurl":
+        case "apiUrl": return target.getApiUrl();
+        case "authorizationmodelid":
+        case "authorizationModelId": return target.getAuthorizationModelId();
+        case "clientid":
+        case "clientId": return target.getClientId();
+        case "clientsecret":
+        case "clientSecret": return target.getClientSecret();
+        case "conditioncontext":
+        case "conditionContext": return target.getConditionContext();
+        case "connecttimeout":
+        case "connectTimeout": return target.getConnectTimeout();
+        case "consistency": return target.getConsistency();
+        case "contextualtuples":
+        case "contextualTuples": return target.getContextualTuples();
+        case "failopen":
+        case "failOpen": return target.isFailOpen();
+        case "healthcheckenabled":
+        case "healthCheckEnabled": return target.isHealthCheckEnabled();
+        case "maxretries":
+        case "maxRetries": return target.getMaxRetries();
+        case "object": return target.getObject();
+        case "openfgaclient":
+        case "openFgaClient": return target.getOpenFgaClient();
+        case "readtimeout":
+        case "readTimeout": return target.getReadTimeout();
+        case "relation": return target.getRelation();
+        case "scopes": return target.getScopes();
+        case "sslcontextparameters":
+        case "sslContextParameters": return target.getSslContextParameters();
+        case "storeid":
+        case "storeId": return target.getStoreId();
+        case "useglobalsslcontextparameters":
+        case "useGlobalSslContextParameters": return 
target.isUseGlobalSslContextParameters();
+        case "user": return target.getUser();
+        default: return null;
+        }
+    }
+
+    @Override
+    public Object getCollectionValueType(Object target, String name, boolean 
ignoreCase) {
+        switch (ignoreCase ? name.toLowerCase(Locale.ROOT) : name) {
+        case "conditioncontext":
+        case "conditionContext": return java.lang.Object.class;
+        default: return null;
+        }
+    }
+}
+
diff --git 
a/components/camel-openfga/src/generated/resources/META-INF/services/org/apache/camel/bean.properties
 
b/components/camel-openfga/src/generated/resources/META-INF/services/org/apache/camel/bean.properties
new file mode 100644
index 000000000000..24541361a204
--- /dev/null
+++ 
b/components/camel-openfga/src/generated/resources/META-INF/services/org/apache/camel/bean.properties
@@ -0,0 +1,7 @@
+# Generated by camel build tools - do NOT edit this file!
+bean=OpenFgaSecurityPolicy
+groupId=org.apache.camel
+artifactId=camel-openfga
+version=4.24.0-SNAPSHOT
+projectName=Camel :: OpenFGA
+projectDescription=Camel OpenFGA Component
diff --git 
a/components/camel-openfga/src/generated/resources/META-INF/services/org/apache/camel/bean/OpenFgaSecurityPolicy.json
 
b/components/camel-openfga/src/generated/resources/META-INF/services/org/apache/camel/bean/OpenFgaSecurityPolicy.json
new file mode 100644
index 000000000000..967a61fc9e11
--- /dev/null
+++ 
b/components/camel-openfga/src/generated/resources/META-INF/services/org/apache/camel/bean/OpenFgaSecurityPolicy.json
@@ -0,0 +1,16 @@
+{
+  "bean": {
+    "kind": "bean",
+    "name": "OpenFgaSecurityPolicy",
+    "javaType": 
"org.apache.camel.component.openfga.security.OpenFgaSecurityPolicy",
+    "interfaceType": "org.apache.camel.spi.AuthorizationPolicy",
+    "title": "Open Fga Security Policy",
+    "description": "An authorization policy that asks OpenFGA whether a 
subject has a relation to an object before the part of the route it guards 
runs. A deny throws CamelAuthorizationException.",
+    "deprecated": false,
+    "groupId": "org.apache.camel",
+    "artifactId": "camel-openfga",
+    "version": "4.24.0-SNAPSHOT",
+    "properties": { "apiUrl": { "index": 0, "kind": "property", "displayName": 
"Api Url", "required": false, "type": "string", "javaType": "java.lang.String", 
"deprecated": false, "autowired": false, "secret": false, "defaultValue": 
"http:\/\/localhost:8080", "description": "The base URL of the OpenFGA HTTP 
API." }, "storeId": { "index": 1, "kind": "property", "displayName": "Store 
Id", "required": true, "type": "string", "javaType": "java.lang.String", 
"deprecated": false, "autowired":  [...]
+  }
+}
+
diff --git 
a/components/camel-openfga/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.component.openfga.security.OpenFgaSecurityPolicy
 
b/components/camel-openfga/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.component.openfga.security.OpenFgaSecurityPolicy
new file mode 100644
index 000000000000..c7001ab84487
--- /dev/null
+++ 
b/components/camel-openfga/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.component.openfga.security.OpenFgaSecurityPolicy
@@ -0,0 +1,2 @@
+# Generated by camel build tools - do NOT edit this file!
+class=org.apache.camel.component.openfga.security.OpenFgaSecurityPolicyConfigurer
diff --git 
a/components/camel-openfga/src/main/java/org/apache/camel/component/openfga/security/OpenFgaSecurityPolicy.java
 
b/components/camel-openfga/src/main/java/org/apache/camel/component/openfga/security/OpenFgaSecurityPolicy.java
index e2d0a5f102b6..be331da8d2d5 100644
--- 
a/components/camel-openfga/src/main/java/org/apache/camel/component/openfga/security/OpenFgaSecurityPolicy.java
+++ 
b/components/camel-openfga/src/main/java/org/apache/camel/component/openfga/security/OpenFgaSecurityPolicy.java
@@ -33,6 +33,8 @@ import 
org.apache.camel.component.openfga.OpenFgaClientFactory;
 import org.apache.camel.component.openfga.OpenFgaConfiguration;
 import org.apache.camel.health.HealthCheckRegistry;
 import org.apache.camel.spi.AuthorizationPolicy;
+import org.apache.camel.spi.Configurer;
+import org.apache.camel.spi.Metadata;
 import org.apache.camel.support.jsse.SSLContextParameters;
 import org.apache.camel.util.ObjectHelper;
 import org.apache.camel.util.StringHelper;
@@ -61,14 +63,110 @@ import org.slf4j.LoggerFactory;
  * {@code onException} machinery applies. The decision is also recorded on the 
exchange in the {@code CamelOpenFga}
  * headers.
  */
+@Metadata(label = "bean",
+          description = "An authorization policy that asks OpenFGA whether a 
subject has a relation to an object before the"
+                        + " part of the route it guards runs. A deny throws 
CamelAuthorizationException.",
+          annotations = { 
"interfaceName=org.apache.camel.spi.AuthorizationPolicy" })
+@Configurer(metadataOnly = true)
 public class OpenFgaSecurityPolicy implements AuthorizationPolicy {
 
     private static final Logger LOG = 
LoggerFactory.getLogger(OpenFgaSecurityPolicy.class);
 
-    private final OpenFgaConfiguration configuration = new 
OpenFgaConfiguration();
-
-    private boolean healthCheckEnabled = true;
+    // the defaults of the endpoint options this policy shares, so that the 
two cannot drift apart
+    private static final OpenFgaConfiguration DEFAULTS = new 
OpenFgaConfiguration();
+
+    @Metadata(defaultValue = "http://localhost:8080";, description = "The base 
URL of the OpenFGA HTTP API.")
+    private String apiUrl = DEFAULTS.getApiUrl();
+    @Metadata(required = true,
+              description = "The identifier of the OpenFGA store holding the 
relationship tuples and the authorization"
+                            + " model.")
+    private String storeId;
+    @Metadata(description = "The authorization model revision to evaluate 
against. Pin it in production: left empty, the"
+                            + " store's latest model is used, and that moves 
whenever a new model is written.")
+    private String authorizationModelId;
+    @Metadata(required = true,
+              description = "The subject to authorize, as a Simple expression 
resolving to an OpenFGA user identifier"
+                            + " such as 
user:${exchangeProperty.CamelKeycloakTokenSubject}. Read it from an exchange"
+                            + " property the step that verified the caller 
set, rather than from a header the caller sent."
+                            + " An exchange whose subject resolves to blank is 
denied.")
+    private String user;
+    @Metadata(required = true,
+              description = "The object being accessed, as a Simple expression 
resolving to an OpenFGA object"
+                            + " identifier such as 
document:${header.documentId}. Taking it from a header is normal: the"
+                            + " caller may say which resource it wants, and 
the check decides whether it may have it.")
+    private String object;
+    @Metadata(required = true,
+              description = "The relation to demand, such as reader. Keep it 
literal: it is the permission being"
+                            + " demanded, so resolving it from an inbound 
header would let the caller pick the weakest one"
+                            + " the model defines.")
+    private String relation;
+    @Metadata(description = "Relationship tuples supplied for the duration of 
one check and never stored, as"
+                            + " semicolon-separated user,relation,object 
triples, each part a Simple expression. A"
+                            + " contextual tuple grants exactly like a stored 
one, so derive it from something the route"
+                            + " established, never from caller input.")
+    private String contextualTuples;
+    @Metadata(description = "Context the CEL expression of any conditioned 
relation the check touches is evaluated with,"
+                            + " as a map of parameter values. A value written 
in YAML or a properties file is a string,"
+                            + " which OpenFGA rejects for a bool parameter: 
give that one as #valueAs(boolean):true.")
+    private Map<String, Object> conditionContext;
+    @Metadata(enums = "UNSPECIFIED,MINIMIZE_LATENCY,HIGHER_CONSISTENCY",
+              description = "The consistency the check is answered with. Set 
HIGHER_CONSISTENCY where the window in which"
+                            + " a just-revoked tuple can still grant access 
matters.")
+    private String consistency;
+    @Metadata(label = "security", security = "secret",
+              description = "Pre-shared token sent to OpenFGA in the 
Authorization header, for a server started with"
+                            + " --authn-method preshared.")
+    private String apiToken;
+    @Metadata(label = "security",
+              description = "Client identifier for the OAuth 2.0 
client-credentials flow. Setting it selects that flow,"
+                            + " so clientSecret, apiTokenIssuer and 
apiAudience are then required too.")
+    private String clientId;
+    @Metadata(label = "security", security = "secret",
+              description = "Client secret for the OAuth 2.0 
client-credentials flow.")
+    private String clientSecret;
+    @Metadata(label = "security",
+              description = "The token endpoint the client-credentials flow 
exchanges its credentials at.")
+    private String apiTokenIssuer;
+    @Metadata(label = "security",
+              description = "The audience to request the access token for in 
the client-credentials flow.")
+    private String apiAudience;
+    @Metadata(label = "security", description = "Space-separated scopes to 
request in the client-credentials flow.")
+    private String scopes;
+    @Metadata(label = "security", defaultValue = "false", security = 
"insecure:dev",
+              description = "Whether to let the exchange proceed when OpenFGA 
could not be asked at all, for example"
+                            + " because the server is unreachable. It never 
applies to a deny, nor to an exchange whose"
+                            + " subject or object did not resolve. Do not 
enable this in production.")
+    private boolean failOpen;
+    @Metadata(label = "advanced", defaultValue = "10000",
+              description = "How long to wait, in milliseconds, for the 
connection to OpenFGA to be established.")
+    private long connectTimeout = DEFAULTS.getConnectTimeout();
+    @Metadata(label = "advanced", defaultValue = "10000",
+              description = "How long to wait, in milliseconds, for the check 
to complete once connected. A request that"
+                            + " times out is a failure to obtain a verdict 
rather than a deny, so the exchange is denied"
+                            + " unless failOpen is set.")
+    private long readTimeout = DEFAULTS.getReadTimeout();
+    @Metadata(label = "advanced", defaultValue = "3",
+              description = "How many times to retry a request that failed in 
a way worth retrying, such as a rate limit"
+                            + " or a 5xx.")
+    private int maxRetries = DEFAULTS.getMaxRetries();
+    @Metadata(label = "security",
+              description = "TLS configuration for the connection to OpenFGA, 
to trust a server whose certificate comes"
+                            + " from a private CA or to present a client 
certificate to one that requires mutual TLS."
+                            + " Takes precedence over 
useGlobalSslContextParameters.")
+    private SSLContextParameters sslContextParameters;
+    @Metadata(label = "security", defaultValue = "false",
+              description = "Whether to use the global SSL context parameters, 
the context-wide SSLContextParameters"
+                            + " camel-main builds from camel.ssl.*, when 
sslContextParameters is not set.")
     private boolean useGlobalSslContextParameters;
+    @Metadata(label = "advanced", defaultValue = "true",
+              description = "Whether to register a readiness check for the 
OpenFGA server this policy queries. The policy"
+                            + " denies every exchange it guards while the 
server is unreachable, so a route that cannot"
+                            + " reach it is not ready.")
+    private boolean healthCheckEnabled = true;
+    @Metadata(label = "advanced",
+              description = "An existing OpenFgaClient to use instead of 
building one from apiUrl, the credentials and"
+                            + " sslContextParameters. No readiness check is 
registered for it.")
+    private OpenFgaClient openFgaClient;
 
     private volatile OpenFgaAuthorizer authorizer;
     private volatile OpenFgaSecurityPolicyHealthCheck healthCheck;
@@ -81,25 +179,26 @@ public class OpenFgaSecurityPolicy implements 
AuthorizationPolicy {
     }
 
     public OpenFgaSecurityPolicy(String apiUrl, String storeId) {
-        configuration.setApiUrl(apiUrl);
-        configuration.setStoreId(storeId);
+        this.apiUrl = apiUrl;
+        this.storeId = storeId;
     }
 
     @Override
     public void beforeWrap(Route route, NamedNode definition) {
         this.camelContext = route.getCamelContext();
         if (authorizer == null) {
-            StringHelper.notEmpty(configuration.getStoreId(), "storeId", this);
-            StringHelper.notEmpty(configuration.getRelation(), "relation", 
this);
-            StringHelper.notEmpty(configuration.getUser(), "user", this);
-            StringHelper.notEmpty(configuration.getObject(), "object", this);
-            if (ObjectHelper.isEmpty(configuration.getAuthorizationModelId())) 
{
+            StringHelper.notEmpty(storeId, "storeId", this);
+            StringHelper.notEmpty(relation, "relation", this);
+            StringHelper.notEmpty(user, "user", this);
+            StringHelper.notEmpty(object, "object", this);
+            if (ObjectHelper.isEmpty(authorizationModelId)) {
                 LOG.warn("authorizationModelId is not set on the OpenFGA 
policy guarding route {}: it will evaluate"
                          + " against store {}'s latest authorization model, 
which changes as soon as a new model is"
                          + " written. Pin the model id in production.",
-                        route.getRouteId(), configuration.getStoreId());
+                        route.getRouteId(), storeId);
             }
-            OpenFgaClient client = configuration.getOpenFgaClient();
+            OpenFgaConfiguration configuration = toConfiguration();
+            OpenFgaClient client = openFgaClient;
             if (client == null) {
                 sslContext = createSslContext(route.getCamelContext());
                 try {
@@ -108,7 +207,7 @@ public class OpenFgaSecurityPolicy implements 
AuthorizationPolicy {
                     // beforeWrap cannot throw a checked exception, and a 
policy that could not build its client must
                     // not start a route it would then fail to guard
                     throw new RuntimeCamelException(
-                            "Could not build the OpenFGA client for store " + 
configuration.getStoreId(), e);
+                            "Could not build the OpenFGA client for store " + 
storeId, e);
                 }
                 ownsClient = true;
             }
@@ -128,16 +227,14 @@ public class OpenFgaSecurityPolicy implements 
AuthorizationPolicy {
      */
     private synchronized void registerHealthCheck() {
         if (!healthCheckEnabled || healthCheck != null || !ownsClient
-                || ObjectHelper.isEmpty(configuration.getApiUrl()) || 
camelContext == null) {
+                || ObjectHelper.isEmpty(apiUrl) || camelContext == null) {
             return;
         }
         HealthCheckRegistry registry = HealthCheckRegistry.get(camelContext);
         if (registry == null) {
             return;
         }
-        healthCheck = new OpenFgaSecurityPolicyHealthCheck(
-                configuration.getApiUrl(), configuration.getApiToken(), 
configuration.getStoreId(),
-                configuration.getRelation(), sslContext);
+        healthCheck = new OpenFgaSecurityPolicyHealthCheck(apiUrl, apiToken, 
storeId, relation, sslContext);
         registry.register(healthCheck);
     }
 
@@ -178,7 +275,7 @@ public class OpenFgaSecurityPolicy implements 
AuthorizationPolicy {
     public Processor wrap(Route route, final Processor processor) {
         if (LOG.isDebugEnabled()) {
             LOG.debug("Securing route {} with OpenFGA relation {} in store 
{}", route.getRouteId(),
-                    configuration.getRelation(), configuration.getStoreId());
+                    relation, storeId);
         }
         return new OpenFgaSecurityProcessor(processor, this);
     }
@@ -193,7 +290,7 @@ public class OpenFgaSecurityPolicy implements 
AuthorizationPolicy {
      */
     private SSLContext createSslContext(CamelContext context) {
         // the same precedence as the endpoint: an explicit configuration wins 
over the context-wide one
-        SSLContextParameters parameters = 
configuration.getSslContextParameters();
+        SSLContextParameters parameters = sslContextParameters;
         if (parameters == null && useGlobalSslContextParameters) {
             parameters = context.getSSLContextParameters();
         }
@@ -206,34 +303,65 @@ public class OpenFgaSecurityPolicy implements 
AuthorizationPolicy {
             // as above: a policy whose TLS configuration is broken must not 
start a route that would then talk to
             // OpenFGA over the JVM default trust material instead
             throw new RuntimeCamelException(
-                    "Could not build the SSLContext for the OpenFGA policy on 
store " + configuration.getStoreId(), e);
+                    "Could not build the SSLContext for the OpenFGA policy on 
store " + storeId, e);
         }
     }
 
+    /**
+     * The endpoint configuration the client and the authorizer are built 
from, holding this policy's options. An option
+     * this method does not copy would be accepted and then silently ignored, 
which
+     * {@code OpenFgaSecurityPolicyOptionsTest} guards against.
+     */
+    OpenFgaConfiguration toConfiguration() {
+        OpenFgaConfiguration answer = new OpenFgaConfiguration();
+        answer.setApiUrl(apiUrl);
+        answer.setStoreId(storeId);
+        answer.setAuthorizationModelId(authorizationModelId);
+        answer.setUser(user);
+        answer.setObject(object);
+        answer.setRelation(relation);
+        answer.setContextualTuples(contextualTuples);
+        answer.setConditionContext(conditionContext);
+        answer.setConsistency(consistency);
+        answer.setApiToken(apiToken);
+        answer.setClientId(clientId);
+        answer.setClientSecret(clientSecret);
+        answer.setApiTokenIssuer(apiTokenIssuer);
+        answer.setApiAudience(apiAudience);
+        answer.setScopes(scopes);
+        answer.setFailOpen(failOpen);
+        answer.setConnectTimeout(connectTimeout);
+        answer.setReadTimeout(readTimeout);
+        answer.setMaxRetries(maxRetries);
+        answer.setSslContextParameters(sslContextParameters);
+        answer.setOpenFgaClient(openFgaClient);
+        return answer;
+    }
+
     public String getApiUrl() {
-        return configuration.getApiUrl();
+        return apiUrl;
     }
 
     /**
      * The base URL of the OpenFGA HTTP API.
      */
     public void setApiUrl(String apiUrl) {
-        configuration.setApiUrl(apiUrl);
+        this.apiUrl = apiUrl;
     }
 
     public String getStoreId() {
-        return configuration.getStoreId();
+        return storeId;
     }
 
     /**
      * The identifier of the OpenFGA store holding the relationship tuples and 
the authorization model.
      */
     public void setStoreId(String storeId) {
-        configuration.setStoreId(storeId);
+        this.storeId = storeId;
     }
 
     public String getAuthorizationModelId() {
-        return configuration.getAuthorizationModelId();
+        return authorizationModelId;
     }
 
     /**
@@ -241,11 +369,11 @@ public class OpenFgaSecurityPolicy implements 
AuthorizationPolicy {
      * is used and that moves whenever a new model is written.
      */
     public void setAuthorizationModelId(String authorizationModelId) {
-        configuration.setAuthorizationModelId(authorizationModelId);
+        this.authorizationModelId = authorizationModelId;
     }
 
     public String getUser() {
-        return configuration.getUser();
+        return user;
     }
 
     /**
@@ -259,11 +387,11 @@ public class OpenFgaSecurityPolicy implements 
AuthorizationPolicy {
      * to record it. An exchange whose subject resolves to blank is denied.
      */
     public void setUser(String user) {
-        configuration.setUser(user);
+        this.user = user;
     }
 
     public String getObject() {
-        return configuration.getObject();
+        return object;
     }
 
     /**
@@ -272,11 +400,11 @@ public class OpenFgaSecurityPolicy implements 
AuthorizationPolicy {
      * resource it wants, and the check decides whether it may have it.
      */
     public void setObject(String object) {
-        configuration.setObject(object);
+        this.object = object;
     }
 
     public String getRelation() {
-        return configuration.getRelation();
+        return relation;
     }
 
     /**
@@ -284,11 +412,11 @@ public class OpenFgaSecurityPolicy implements 
AuthorizationPolicy {
      * resolving it from an inbound header would let the caller pick the 
weakest one the model defines.
      */
     public void setRelation(String relation) {
-        configuration.setRelation(relation);
+        this.relation = relation;
     }
 
     public String getContextualTuples() {
-        return configuration.getContextualTuples();
+        return contextualTuples;
     }
 
     /**
@@ -301,11 +429,11 @@ public class OpenFgaSecurityPolicy implements 
AuthorizationPolicy {
      * route established, such as a claim an earlier authentication step put 
on an exchange property.
      */
     public void setContextualTuples(String contextualTuples) {
-        configuration.setContextualTuples(contextualTuples);
+        this.contextualTuples = contextualTuples;
     }
 
     public Map<String, Object> getConditionContext() {
-        return configuration.getConditionContext();
+        return conditionContext;
     }
 
     /**
@@ -314,11 +442,11 @@ public class OpenFgaSecurityPolicy implements 
AuthorizationPolicy {
      * {@code timestamp}, {@code ipaddress}) instead of strings the server 
would reject.
      */
     public void setConditionContext(Map<String, Object> conditionContext) {
-        configuration.setConditionContext(conditionContext);
+        this.conditionContext = conditionContext;
     }
 
     public String getConsistency() {
-        return configuration.getConsistency();
+        return consistency;
     }
 
     /**
@@ -326,77 +454,77 @@ public class OpenFgaSecurityPolicy implements 
AuthorizationPolicy {
      * just-revoked tuple can still grant access matters.
      */
     public void setConsistency(String consistency) {
-        configuration.setConsistency(consistency);
+        this.consistency = consistency;
     }
 
     public String getApiToken() {
-        return configuration.getApiToken();
+        return apiToken;
     }
 
     /**
      * Pre-shared token for an OpenFGA server started with {@code 
--authn-method preshared}.
      */
     public void setApiToken(String apiToken) {
-        configuration.setApiToken(apiToken);
+        this.apiToken = apiToken;
     }
 
     public String getClientId() {
-        return configuration.getClientId();
+        return clientId;
     }
 
     /**
      * Client identifier for the OAuth 2.0 client-credentials flow.
      */
     public void setClientId(String clientId) {
-        configuration.setClientId(clientId);
+        this.clientId = clientId;
     }
 
     public String getClientSecret() {
-        return configuration.getClientSecret();
+        return clientSecret;
     }
 
     /**
      * Client secret for the OAuth 2.0 client-credentials flow.
      */
     public void setClientSecret(String clientSecret) {
-        configuration.setClientSecret(clientSecret);
+        this.clientSecret = clientSecret;
     }
 
     public String getApiTokenIssuer() {
-        return configuration.getApiTokenIssuer();
+        return apiTokenIssuer;
     }
 
     /**
      * The token endpoint the client-credentials flow exchanges its 
credentials at.
      */
     public void setApiTokenIssuer(String apiTokenIssuer) {
-        configuration.setApiTokenIssuer(apiTokenIssuer);
+        this.apiTokenIssuer = apiTokenIssuer;
     }
 
     public String getApiAudience() {
-        return configuration.getApiAudience();
+        return apiAudience;
     }
 
     /**
      * The audience to request the access token for in the client-credentials 
flow.
      */
     public void setApiAudience(String apiAudience) {
-        configuration.setApiAudience(apiAudience);
+        this.apiAudience = apiAudience;
     }
 
     public String getScopes() {
-        return configuration.getScopes();
+        return scopes;
     }
 
     /**
      * Space-separated scopes to request in the OAuth 2.0 client-credentials 
flow.
      */
     public void setScopes(String scopes) {
-        configuration.setScopes(scopes);
+        this.scopes = scopes;
     }
 
     public boolean isFailOpen() {
-        return configuration.isFailOpen();
+        return failOpen;
     }
 
     /**
@@ -407,22 +535,22 @@ public class OpenFgaSecurityPolicy implements 
AuthorizationPolicy {
      * failures.
      */
     public void setFailOpen(boolean failOpen) {
-        configuration.setFailOpen(failOpen);
+        this.failOpen = failOpen;
     }
 
     public long getConnectTimeout() {
-        return configuration.getConnectTimeout();
+        return connectTimeout;
     }
 
     /**
      * How long to wait for the connection to OpenFGA to be established.
      */
     public void setConnectTimeout(long connectTimeout) {
-        configuration.setConnectTimeout(connectTimeout);
+        this.connectTimeout = connectTimeout;
     }
 
     public long getReadTimeout() {
-        return configuration.getReadTimeout();
+        return readTimeout;
     }
 
     /**
@@ -430,22 +558,22 @@ public class OpenFgaSecurityPolicy implements 
AuthorizationPolicy {
      * verdict rather than a deny, so the policy denies the exchange unless 
{@code failOpen} is set.
      */
     public void setReadTimeout(long readTimeout) {
-        configuration.setReadTimeout(readTimeout);
+        this.readTimeout = readTimeout;
     }
 
     public int getMaxRetries() {
-        return configuration.getMaxRetries();
+        return maxRetries;
     }
 
     /**
      * How many times to retry a request that failed in a way worth retrying.
      */
     public void setMaxRetries(int maxRetries) {
-        configuration.setMaxRetries(maxRetries);
+        this.maxRetries = maxRetries;
     }
 
     public SSLContextParameters getSslContextParameters() {
-        return configuration.getSslContextParameters();
+        return sslContextParameters;
     }
 
     /**
@@ -454,7 +582,7 @@ public class OpenFgaSecurityPolicy implements 
AuthorizationPolicy {
      * {@code useGlobalSslContextParameters}.
      */
     public void setSslContextParameters(SSLContextParameters 
sslContextParameters) {
-        configuration.setSslContextParameters(sslContextParameters);
+        this.sslContextParameters = sslContextParameters;
     }
 
     public boolean isUseGlobalSslContextParameters() {
@@ -486,13 +614,13 @@ public class OpenFgaSecurityPolicy implements 
AuthorizationPolicy {
     }
 
     public OpenFgaClient getOpenFgaClient() {
-        return configuration.getOpenFgaClient();
+        return openFgaClient;
     }
 
     /**
      * An already-configured {@link OpenFgaClient} to use instead of building 
one.
      */
     public void setOpenFgaClient(OpenFgaClient openFgaClient) {
-        configuration.setOpenFgaClient(openFgaClient);
+        this.openFgaClient = openFgaClient;
     }
 }
diff --git 
a/components/camel-openfga/src/test/java/org/apache/camel/component/openfga/security/OpenFgaSecurityPolicyOptionsTest.java
 
b/components/camel-openfga/src/test/java/org/apache/camel/component/openfga/security/OpenFgaSecurityPolicyOptionsTest.java
new file mode 100644
index 000000000000..1ef0a694eac4
--- /dev/null
+++ 
b/components/camel-openfga/src/test/java/org/apache/camel/component/openfga/security/OpenFgaSecurityPolicyOptionsTest.java
@@ -0,0 +1,178 @@
+/*
+ * 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.openfga.security;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+import dev.openfga.sdk.api.client.OpenFgaClient;
+import org.apache.camel.CamelContext;
+import org.apache.camel.component.openfga.OpenFgaConfiguration;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.support.PropertyBindingSupport;
+import org.apache.camel.support.jsse.SSLContextParameters;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+/**
+ * The policy keeps its options in fields of its own, so that the bean catalog 
can describe them, and builds the
+ * endpoint configuration its client and authorizer use from them when it 
wraps a route. An option it forgets to copy
+ * would be accepted and then silently ignored - for a security policy, a 
check that quietly does not apply.
+ */
+class OpenFgaSecurityPolicyOptionsTest {
+
+    // options of the policy itself, which the endpoint configuration has no 
counterpart for
+    // (useGlobalSslContextParameters is a component option there, applied 
where the SSLContext is built)
+    private static final Set<String> POLICY_ONLY = 
Set.of("healthCheckEnabled", "useGlobalSslContextParameters");
+
+    @Test
+    void 
everyOptionReachesTheConfigurationTheClientAndAuthorizerAreBuiltFrom() throws 
Exception {
+        OpenFgaSecurityPolicy policy = new OpenFgaSecurityPolicy();
+        Map<String, Object> values = new TreeMap<>();
+        int n = 0;
+        for (Method setter : setters(OpenFgaSecurityPolicy.class).values()) {
+            String option = optionName(setter);
+            if (POLICY_ONLY.contains(option)) {
+                continue;
+            }
+            Object value = distinctValue(setter.getParameterTypes()[0], 
option, n++);
+            setter.invoke(policy, value);
+            values.put(option, value);
+        }
+
+        // a new option of the policy is either an endpoint option the 
configuration carries, or listed in POLICY_ONLY
+        
assertThat(setters(OpenFgaConfiguration.class).keySet()).containsAll(values.keySet());
+        // the reflection above found the options at all
+        assertThat(values).hasSizeGreaterThanOrEqualTo(21);
+
+        OpenFgaConfiguration configuration = policy.toConfiguration();
+        for (Map.Entry<String, Object> entry : values.entrySet()) {
+            Object copied = getter(OpenFgaConfiguration.class, 
entry.getKey()).invoke(configuration);
+            assertThat(copied).as(entry.getKey()).isEqualTo(entry.getValue());
+            assertThat(getter(OpenFgaSecurityPolicy.class, 
entry.getKey()).invoke(policy)).as(entry.getKey())
+                    .isEqualTo(entry.getValue());
+        }
+    }
+
+    @Test
+    void keepsTheDefaultsOfTheEndpointOptions() {
+        OpenFgaConfiguration defaults = new OpenFgaConfiguration();
+        OpenFgaSecurityPolicy policy = new OpenFgaSecurityPolicy();
+
+        assertThat(policy.getApiUrl()).isEqualTo(defaults.getApiUrl());
+        
assertThat(policy.getConnectTimeout()).isEqualTo(defaults.getConnectTimeout());
+        
assertThat(policy.getReadTimeout()).isEqualTo(defaults.getReadTimeout());
+        assertThat(policy.getMaxRetries()).isEqualTo(defaults.getMaxRetries());
+        assertThat(policy.isFailOpen()).isFalse();
+        assertThat(policy.isHealthCheckEnabled()).isTrue();
+    }
+
+    @Test
+    void bindsTheOptionsAsAYamlBeanDefinitionGivesThem() throws Exception {
+        // the YAML DSL hands every bean property over as a string, and a map 
value as a nested map
+        try (CamelContext context = new DefaultCamelContext()) {
+            context.start();
+            SSLContextParameters tls = new SSLContextParameters();
+            context.getRegistry().bind("tls", tls);
+            Map<String, Object> conditionContext = new LinkedHashMap<>();
+            conditionContext.put("region", "eu");
+            conditionContext.put("trusted", "#valueAs(boolean):true");
+            Map<String, Object> properties = new LinkedHashMap<>();
+            properties.put("apiUrl", "https://openfga:8443";);
+            properties.put("storeId", "01HSTORE");
+            properties.put("authorizationModelId", "01HMODEL");
+            properties.put("relation", "reader");
+            properties.put("user", 
"user:${exchangeProperty.CamelSpiffePeerId}");
+            properties.put("object", "document:${header.documentId}");
+            properties.put("consistency", "HIGHER_CONSISTENCY");
+            properties.put("connectTimeout", "1500");
+            properties.put("maxRetries", "0");
+            properties.put("failOpen", "false");
+            properties.put("healthCheckEnabled", "false");
+            properties.put("sslContextParameters", "#bean:tls");
+            properties.put("conditionContext", conditionContext);
+
+            OpenFgaSecurityPolicy policy = new OpenFgaSecurityPolicy();
+            PropertyBindingSupport.setPropertiesOnTarget(context, policy, 
properties);
+
+            assertThat(policy.getApiUrl()).isEqualTo("https://openfga:8443";);
+            assertThat(policy.getStoreId()).isEqualTo("01HSTORE");
+            assertThat(policy.getAuthorizationModelId()).isEqualTo("01HMODEL");
+            assertThat(policy.getRelation()).isEqualTo("reader");
+            
assertThat(policy.getUser()).isEqualTo("user:${exchangeProperty.CamelSpiffePeerId}");
+            
assertThat(policy.getObject()).isEqualTo("document:${header.documentId}");
+            
assertThat(policy.getConsistency()).isEqualTo("HIGHER_CONSISTENCY");
+            assertThat(policy.getConnectTimeout()).isEqualTo(1500L);
+            assertThat(policy.getMaxRetries()).isZero();
+            assertThat(policy.isFailOpen()).isFalse();
+            assertThat(policy.isHealthCheckEnabled()).isFalse();
+            assertThat(policy.getSslContextParameters()).isSameAs(tls);
+            assertThat(policy.getConditionContext()).containsEntry("region", 
"eu").containsEntry("trusted", true);
+        }
+    }
+
+    private static Map<String, Method> setters(Class<?> type) {
+        Map<String, Method> answer = new TreeMap<>();
+        for (Method m : type.getMethods()) {
+            if (m.getDeclaringClass() == type && m.getName().startsWith("set") 
&& m.getParameterCount() == 1
+                    && !Modifier.isStatic(m.getModifiers())) {
+                answer.put(optionName(m), m);
+            }
+        }
+        return answer;
+    }
+
+    private static String optionName(Method setter) {
+        String name = setter.getName().substring(3);
+        return Character.toLowerCase(name.charAt(0)) + name.substring(1);
+    }
+
+    private static Method getter(Class<?> type, String option) throws 
NoSuchMethodException {
+        String suffix = Character.toUpperCase(option.charAt(0)) + 
option.substring(1);
+        try {
+            return type.getMethod("get" + suffix);
+        } catch (NoSuchMethodException e) {
+            return type.getMethod("is" + suffix);
+        }
+    }
+
+    // a value no option has by default, and no two options share
+    private static Object distinctValue(Class<?> type, String option, int n) {
+        if (type == String.class) {
+            return "value-of-" + option;
+        } else if (type == boolean.class) {
+            return true;
+        } else if (type == long.class) {
+            return 4200L + n;
+        } else if (type == int.class) {
+            return 42 + n;
+        } else if (type == Map.class) {
+            return Map.of("option", option);
+        } else if (type == SSLContextParameters.class) {
+            return new SSLContextParameters();
+        } else if (type == OpenFgaClient.class) {
+            return mock(OpenFgaClient.class);
+        }
+        throw new IllegalStateException("No test value for option " + option + 
" of type " + type.getName());
+    }
+}
diff --git 
a/components/camel-spiffe/src/generated/java/org/apache/camel/component/spiffe/SpiffeSSLContextParametersConfigurer.java
 
b/components/camel-spiffe/src/generated/java/org/apache/camel/component/spiffe/SpiffeSSLContextParametersConfigurer.java
new file mode 100644
index 000000000000..dd711cd681aa
--- /dev/null
+++ 
b/components/camel-spiffe/src/generated/java/org/apache/camel/component/spiffe/SpiffeSSLContextParametersConfigurer.java
@@ -0,0 +1,76 @@
+/* Generated by camel build tools - do NOT edit this file! */
+package org.apache.camel.component.spiffe;
+
+import javax.annotation.processing.Generated;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.spi.ExtendedPropertyConfigurerGetter;
+import org.apache.camel.spi.PropertyConfigurerGetter;
+import org.apache.camel.spi.ConfigurerStrategy;
+import org.apache.camel.spi.GeneratedPropertyConfigurer;
+import org.apache.camel.util.CaseInsensitiveMap;
+import org.apache.camel.component.spiffe.SpiffeSSLContextParameters;
+
+/**
+ * Generated by camel build tools - do NOT edit this file!
+ */
+@Generated("org.apache.camel.maven.packaging.GenerateConfigurerMojo")
+@SuppressWarnings("unchecked")
+public class SpiffeSSLContextParametersConfigurer extends 
org.apache.camel.support.component.PropertyConfigurerSupport implements 
GeneratedPropertyConfigurer, PropertyConfigurerGetter {
+
+    @Override
+    public boolean configure(CamelContext camelContext, Object obj, String 
name, Object value, boolean ignoreCase) {
+        org.apache.camel.component.spiffe.SpiffeSSLContextParameters target = 
(org.apache.camel.component.spiffe.SpiffeSSLContextParameters) obj;
+        switch (ignoreCase ? name.toLowerCase(Locale.ROOT) : name) {
+        case "acceptanyspiffeid":
+        case "acceptAnySpiffeId": 
target.setAcceptAnySpiffeId(property(camelContext, boolean.class, value)); 
return true;
+        case "acceptedspiffeids":
+        case "acceptedSpiffeIds": 
target.setAcceptedSpiffeIds(property(camelContext, java.lang.String.class, 
value)); return true;
+        case "camelcontext":
+        case "camelContext": target.setCamelContext(property(camelContext, 
org.apache.camel.CamelContext.class, value)); return true;
+        case "inittimeout":
+        case "initTimeout": target.setInitTimeout(property(camelContext, 
long.class, value)); return true;
+        case "spiffesocketpath":
+        case "spiffeSocketPath": 
target.setSpiffeSocketPath(property(camelContext, java.lang.String.class, 
value)); return true;
+        default: return false;
+        }
+    }
+
+    @Override
+    public Class<?> getOptionType(String name, boolean ignoreCase) {
+        switch (ignoreCase ? name.toLowerCase(Locale.ROOT) : name) {
+        case "acceptanyspiffeid":
+        case "acceptAnySpiffeId": return boolean.class;
+        case "acceptedspiffeids":
+        case "acceptedSpiffeIds": return java.lang.String.class;
+        case "camelcontext":
+        case "camelContext": return org.apache.camel.CamelContext.class;
+        case "inittimeout":
+        case "initTimeout": return long.class;
+        case "spiffesocketpath":
+        case "spiffeSocketPath": return java.lang.String.class;
+        default: return null;
+        }
+    }
+
+    @Override
+    public Object getOptionValue(Object obj, String name, boolean ignoreCase) {
+        org.apache.camel.component.spiffe.SpiffeSSLContextParameters target = 
(org.apache.camel.component.spiffe.SpiffeSSLContextParameters) obj;
+        switch (ignoreCase ? name.toLowerCase(Locale.ROOT) : name) {
+        case "acceptanyspiffeid":
+        case "acceptAnySpiffeId": return target.isAcceptAnySpiffeId();
+        case "acceptedspiffeids":
+        case "acceptedSpiffeIds": return target.getAcceptedSpiffeIds();
+        case "camelcontext":
+        case "camelContext": return target.getCamelContext();
+        case "inittimeout":
+        case "initTimeout": return target.getInitTimeout();
+        case "spiffesocketpath":
+        case "spiffeSocketPath": return target.getSpiffeSocketPath();
+        default: return null;
+        }
+    }
+}
+
diff --git 
a/components/camel-spiffe/src/generated/java/org/apache/camel/component/spiffe/SpiffeSecurityPolicyConfigurer.java
 
b/components/camel-spiffe/src/generated/java/org/apache/camel/component/spiffe/SpiffeSecurityPolicyConfigurer.java
new file mode 100644
index 000000000000..f25a58f3bf43
--- /dev/null
+++ 
b/components/camel-spiffe/src/generated/java/org/apache/camel/component/spiffe/SpiffeSecurityPolicyConfigurer.java
@@ -0,0 +1,64 @@
+/* Generated by camel build tools - do NOT edit this file! */
+package org.apache.camel.component.spiffe;
+
+import javax.annotation.processing.Generated;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.spi.ExtendedPropertyConfigurerGetter;
+import org.apache.camel.spi.PropertyConfigurerGetter;
+import org.apache.camel.spi.ConfigurerStrategy;
+import org.apache.camel.spi.GeneratedPropertyConfigurer;
+import org.apache.camel.util.CaseInsensitiveMap;
+import org.apache.camel.component.spiffe.SpiffeSecurityPolicy;
+
+/**
+ * Generated by camel build tools - do NOT edit this file!
+ */
+@Generated("org.apache.camel.maven.packaging.GenerateConfigurerMojo")
+@SuppressWarnings("unchecked")
+public class SpiffeSecurityPolicyConfigurer extends 
org.apache.camel.support.component.PropertyConfigurerSupport implements 
GeneratedPropertyConfigurer, PropertyConfigurerGetter {
+
+    @Override
+    public boolean configure(CamelContext camelContext, Object obj, String 
name, Object value, boolean ignoreCase) {
+        org.apache.camel.component.spiffe.SpiffeSecurityPolicy target = 
(org.apache.camel.component.spiffe.SpiffeSecurityPolicy) obj;
+        switch (ignoreCase ? name.toLowerCase(Locale.ROOT) : name) {
+        case "acceptanyspiffeid":
+        case "acceptAnySpiffeId": 
target.setAcceptAnySpiffeId(property(camelContext, boolean.class, value)); 
return true;
+        case "acceptedspiffeids":
+        case "acceptedSpiffeIds": 
target.setAcceptedSpiffeIds(property(camelContext, java.lang.String.class, 
value)); return true;
+        case "sslsessionheader":
+        case "sslSessionHeader": 
target.setSslSessionHeader(property(camelContext, java.lang.String.class, 
value)); return true;
+        default: return false;
+        }
+    }
+
+    @Override
+    public Class<?> getOptionType(String name, boolean ignoreCase) {
+        switch (ignoreCase ? name.toLowerCase(Locale.ROOT) : name) {
+        case "acceptanyspiffeid":
+        case "acceptAnySpiffeId": return boolean.class;
+        case "acceptedspiffeids":
+        case "acceptedSpiffeIds": return java.lang.String.class;
+        case "sslsessionheader":
+        case "sslSessionHeader": return java.lang.String.class;
+        default: return null;
+        }
+    }
+
+    @Override
+    public Object getOptionValue(Object obj, String name, boolean ignoreCase) {
+        org.apache.camel.component.spiffe.SpiffeSecurityPolicy target = 
(org.apache.camel.component.spiffe.SpiffeSecurityPolicy) obj;
+        switch (ignoreCase ? name.toLowerCase(Locale.ROOT) : name) {
+        case "acceptanyspiffeid":
+        case "acceptAnySpiffeId": return target.isAcceptAnySpiffeId();
+        case "acceptedspiffeids":
+        case "acceptedSpiffeIds": return target.getAcceptedSpiffeIds();
+        case "sslsessionheader":
+        case "sslSessionHeader": return target.getSslSessionHeader();
+        default: return null;
+        }
+    }
+}
+
diff --git 
a/components/camel-spiffe/src/generated/resources/META-INF/services/org/apache/camel/bean.properties
 
b/components/camel-spiffe/src/generated/resources/META-INF/services/org/apache/camel/bean.properties
new file mode 100644
index 000000000000..094c2262df2c
--- /dev/null
+++ 
b/components/camel-spiffe/src/generated/resources/META-INF/services/org/apache/camel/bean.properties
@@ -0,0 +1,7 @@
+# Generated by camel build tools - do NOT edit this file!
+bean=SpiffeSSLContextParameters SpiffeSecurityPolicy
+groupId=org.apache.camel
+artifactId=camel-spiffe
+version=4.24.0-SNAPSHOT
+projectName=Camel :: SPIFFE
+projectDescription=Camel SPIFFE Workload Identity Component
diff --git 
a/components/camel-spiffe/src/generated/resources/META-INF/services/org/apache/camel/bean/SpiffeSSLContextParameters.json
 
b/components/camel-spiffe/src/generated/resources/META-INF/services/org/apache/camel/bean/SpiffeSSLContextParameters.json
new file mode 100644
index 000000000000..ca445236ce47
--- /dev/null
+++ 
b/components/camel-spiffe/src/generated/resources/META-INF/services/org/apache/camel/bean/SpiffeSSLContextParameters.json
@@ -0,0 +1,16 @@
+{
+  "bean": {
+    "kind": "bean",
+    "name": "SpiffeSSLContextParameters",
+    "javaType": "org.apache.camel.component.spiffe.SpiffeSSLContextParameters",
+    "interfaceType": "org.apache.camel.support.jsse.SSLContextParameters",
+    "title": "Spiffe SSLContext Parameters",
+    "description": "SSLContextParameters whose SSLContext is backed by the 
SPIFFE Workload API: the X.509-SVID and the trust bundles are fetched live and 
rotated automatically, giving mutual TLS to any component that accepts an 
sslContextParameters reference. Set acceptedSpiffeIds or acceptAnySpiffeId; the 
inherited options, such as serverParameters.clientAuthentication, still apply.",
+    "deprecated": false,
+    "groupId": "org.apache.camel",
+    "artifactId": "camel-spiffe",
+    "version": "4.24.0-SNAPSHOT",
+    "properties": { "spiffeSocketPath": { "index": 0, "kind": "property", 
"displayName": "Spiffe Socket Path", "label": "security", "required": false, 
"type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": false, "description": "Path to the SPIFFE 
Workload API endpoint (for example unix:\/\/\/tmp\/agent.sock). When not set, 
the SPIFFE_ENDPOINT_SOCKET environment variable is used." }, 
"acceptedSpiffeIds": { "index": 1, "kind": "property", "dis [...]
+  }
+}
+
diff --git 
a/components/camel-spiffe/src/generated/resources/META-INF/services/org/apache/camel/bean/SpiffeSecurityPolicy.json
 
b/components/camel-spiffe/src/generated/resources/META-INF/services/org/apache/camel/bean/SpiffeSecurityPolicy.json
new file mode 100644
index 000000000000..f132d90abb38
--- /dev/null
+++ 
b/components/camel-spiffe/src/generated/resources/META-INF/services/org/apache/camel/bean/SpiffeSecurityPolicy.json
@@ -0,0 +1,16 @@
+{
+  "bean": {
+    "kind": "bean",
+    "name": "SpiffeSecurityPolicy",
+    "javaType": "org.apache.camel.component.spiffe.SpiffeSecurityPolicy",
+    "interfaceType": "org.apache.camel.spi.AuthorizationPolicy",
+    "title": "Spiffe Security Policy",
+    "description": "An authorization policy that authorizes a route segment on 
the caller's SPIFFE ID, read from the peer certificate that SPIFFE mutual TLS 
verified. A peer that is not accepted, or presents no verified SVID, is denied 
with CamelAuthorizationException.",
+    "deprecated": false,
+    "groupId": "org.apache.camel",
+    "artifactId": "camel-spiffe",
+    "version": "4.24.0-SNAPSHOT",
+    "properties": { "acceptedSpiffeIds": { "index": 0, "kind": "property", 
"displayName": "Accepted Spiffe Ids", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "description": "Comma-separated allow-list of the peer SPIFFE 
IDs this route segment authorizes, for example 
spiffe:\/\/example.org\/frontend. Set exactly one of acceptedSpiffeIds and 
acceptAnySpiffeId: setting both is rejected, and setting neither fai [...]
+  }
+}
+
diff --git 
a/components/camel-spiffe/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.component.spiffe.SpiffeSSLContextParameters
 
b/components/camel-spiffe/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.component.spiffe.SpiffeSSLContextParameters
new file mode 100644
index 000000000000..2bb4bac8e0f8
--- /dev/null
+++ 
b/components/camel-spiffe/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.component.spiffe.SpiffeSSLContextParameters
@@ -0,0 +1,2 @@
+# Generated by camel build tools - do NOT edit this file!
+class=org.apache.camel.component.spiffe.SpiffeSSLContextParametersConfigurer
diff --git 
a/components/camel-spiffe/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.component.spiffe.SpiffeSecurityPolicy
 
b/components/camel-spiffe/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.component.spiffe.SpiffeSecurityPolicy
new file mode 100644
index 000000000000..2f2e257bd480
--- /dev/null
+++ 
b/components/camel-spiffe/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.component.spiffe.SpiffeSecurityPolicy
@@ -0,0 +1,2 @@
+# Generated by camel build tools - do NOT edit this file!
+class=org.apache.camel.component.spiffe.SpiffeSecurityPolicyConfigurer
diff --git 
a/components/camel-spiffe/src/main/java/org/apache/camel/component/spiffe/SpiffeSSLContextParameters.java
 
b/components/camel-spiffe/src/main/java/org/apache/camel/component/spiffe/SpiffeSSLContextParameters.java
index 42e20ebb5425..873203a951ec 100644
--- 
a/components/camel-spiffe/src/main/java/org/apache/camel/component/spiffe/SpiffeSSLContextParameters.java
+++ 
b/components/camel-spiffe/src/main/java/org/apache/camel/component/spiffe/SpiffeSSLContextParameters.java
@@ -33,6 +33,7 @@ import io.spiffe.workloadapi.X509Source;
 import org.apache.camel.CamelContext;
 import org.apache.camel.RuntimeCamelException;
 import org.apache.camel.Service;
+import org.apache.camel.spi.Configurer;
 import org.apache.camel.spi.Metadata;
 import org.apache.camel.support.jsse.SSLContextParameters;
 import org.apache.camel.util.ObjectHelper;
@@ -57,6 +58,14 @@ import org.apache.camel.util.ObjectHelper;
  * The underlying {@code X509Source} is created lazily on the first call, 
closed when the {@link CamelContext} shuts
  * down, and the cached context is invalidated at the same time so a restarted 
context rebuilds a fresh source.
  */
+@Metadata(label = "bean",
+          description = "SSLContextParameters whose SSLContext is backed by 
the SPIFFE Workload API: the X.509-SVID and"
+                        + " the trust bundles are fetched live and rotated 
automatically, giving mutual TLS to any"
+                        + " component that accepts an sslContextParameters 
reference. Set acceptedSpiffeIds or"
+                        + " acceptAnySpiffeId; the inherited options, such as 
serverParameters.clientAuthentication,"
+                        + " still apply.",
+          annotations = { 
"interfaceName=org.apache.camel.support.jsse.SSLContextParameters" })
+@Configurer(metadataOnly = true)
 public class SpiffeSSLContextParameters extends SSLContextParameters {
 
     private static final String DEFAULT_PROTOCOL = "TLSv1.3";
diff --git 
a/components/camel-spiffe/src/main/java/org/apache/camel/component/spiffe/SpiffeSecurityPolicy.java
 
b/components/camel-spiffe/src/main/java/org/apache/camel/component/spiffe/SpiffeSecurityPolicy.java
index 150ad4fe3449..a5e6b58d4bdb 100644
--- 
a/components/camel-spiffe/src/main/java/org/apache/camel/component/spiffe/SpiffeSecurityPolicy.java
+++ 
b/components/camel-spiffe/src/main/java/org/apache/camel/component/spiffe/SpiffeSecurityPolicy.java
@@ -24,6 +24,8 @@ import org.apache.camel.NamedNode;
 import org.apache.camel.Processor;
 import org.apache.camel.Route;
 import org.apache.camel.spi.AuthorizationPolicy;
+import org.apache.camel.spi.Configurer;
+import org.apache.camel.spi.Metadata;
 import org.apache.camel.util.ObjectHelper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -50,6 +52,12 @@ import org.slf4j.LoggerFactory;
  * {@link SpiffeConstants#PEER_SPIFFE_ID} exchange property, so an OPA policy 
downstream can forward it to a Rego rule
  * through its {@code includeProperties} option (CAMEL-24643).
  */
+@Metadata(label = "bean",
+          description = "An authorization policy that authorizes a route 
segment on the caller's SPIFFE ID, read from the"
+                        + " peer certificate that SPIFFE mutual TLS verified. 
A peer that is not accepted, or presents no"
+                        + " verified SVID, is denied with 
CamelAuthorizationException.",
+          annotations = { 
"interfaceName=org.apache.camel.spi.AuthorizationPolicy" })
+@Configurer(metadataOnly = true)
 public class SpiffeSecurityPolicy implements AuthorizationPolicy {
 
     /**
@@ -59,8 +67,19 @@ public class SpiffeSecurityPolicy implements 
AuthorizationPolicy {
 
     private static final Logger LOG = 
LoggerFactory.getLogger(SpiffeSecurityPolicy.class);
 
+    @Metadata(description = "Comma-separated allow-list of the peer SPIFFE IDs 
this route segment authorizes, for example"
+                            + " spiffe://example.org/frontend. Set exactly one 
of acceptedSpiffeIds and"
+                            + " acceptAnySpiffeId: setting both is rejected, 
and setting neither fails closed.")
     private String acceptedSpiffeIds;
+    @Metadata(defaultValue = "false",
+              description = "Authorize any peer that presents a verified 
SPIFFE SVID, leaving the which-peer decision to"
+                            + " a downstream policy such as OPA. It still 
fails closed when no verified peer certificate"
+                            + " is present. Mutually exclusive with 
acceptedSpiffeIds.")
     private boolean acceptAnySpiffeId;
+    @Metadata(label = "advanced", defaultValue = DEFAULT_SSL_SESSION_HEADER,
+              description = "Name of the message header that carries the 
verified SSLSession the peer certificate is"
+                            + " read from. The default is the header 
camel-netty and camel-netty-http populate; set it"
+                            + " for another TLS consumer that exposes the 
session under a different name.")
     private String sslSessionHeader = DEFAULT_SSL_SESSION_HEADER;
 
     private volatile Set<SpiffeId> acceptedSet = Set.of();
diff --git 
a/components/camel-spiffe/src/test/java/org/apache/camel/component/spiffe/SpiffeBeanBindingTest.java
 
b/components/camel-spiffe/src/test/java/org/apache/camel/component/spiffe/SpiffeBeanBindingTest.java
new file mode 100644
index 000000000000..f58e162127a0
--- /dev/null
+++ 
b/components/camel-spiffe/src/test/java/org/apache/camel/component/spiffe/SpiffeBeanBindingTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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.spiffe;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.support.PropertyBindingSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Binds the SPIFFE beans the way a YAML {@code beans:} entry does: every 
value a string, a nested mapping a nested map.
+ */
+class SpiffeBeanBindingTest {
+
+    @Test
+    void bindsTheSslContextParametersOwnAndInheritedOptions() throws Exception 
{
+        try (CamelContext context = new DefaultCamelContext()) {
+            context.start();
+            Map<String, Object> serverParameters = new LinkedHashMap<>();
+            serverParameters.put("clientAuthentication", "REQUIRE");
+            Map<String, Object> protocols = new LinkedHashMap<>();
+            protocols.put("secureSocketProtocol[0]", "TLSv1.3");
+            Map<String, Object> properties = new LinkedHashMap<>();
+            properties.put("spiffeSocketPath", "unix:///run/spire/agent.sock");
+            properties.put("acceptedSpiffeIds", 
"spiffe://example.org/frontend,spiffe://example.org/batch");
+            properties.put("initTimeout", "5000");
+            // inherited from SSLContextParameters, which the generated 
configurer does not cover
+            properties.put("serverParameters", serverParameters);
+            properties.put("secureSocketProtocols", protocols);
+            properties.put("secureSocketProtocol", "TLSv1.3");
+
+            SpiffeSSLContextParameters ssl = new SpiffeSSLContextParameters();
+            PropertyBindingSupport.setPropertiesOnTarget(context, ssl, 
properties);
+
+            
assertThat(ssl.getSpiffeSocketPath()).isEqualTo("unix:///run/spire/agent.sock");
+            
assertThat(ssl.getAcceptedSpiffeIds()).isEqualTo("spiffe://example.org/frontend,spiffe://example.org/batch");
+            assertThat(ssl.isAcceptAnySpiffeId()).isFalse();
+            assertThat(ssl.getInitTimeout()).isEqualTo(5000L);
+            
assertThat(ssl.getServerParameters().getClientAuthentication()).isEqualTo("REQUIRE");
+            
assertThat(ssl.getSecureSocketProtocols().getSecureSocketProtocol()).containsExactly("TLSv1.3");
+            assertThat(ssl.getSecureSocketProtocol()).isEqualTo("TLSv1.3");
+        }
+    }
+
+    @Test
+    void bindsTheSecurityPolicyOptions() throws Exception {
+        try (CamelContext context = new DefaultCamelContext()) {
+            context.start();
+            Map<String, Object> properties = new LinkedHashMap<>();
+            properties.put("acceptedSpiffeIds", 
"spiffe://example.org/frontend");
+            properties.put("sslSessionHeader", "CamelCustomSSLSession");
+
+            SpiffeSecurityPolicy policy = new SpiffeSecurityPolicy();
+            PropertyBindingSupport.setPropertiesOnTarget(context, policy, 
properties);
+
+            
assertThat(policy.getAcceptedSpiffeIds()).isEqualTo("spiffe://example.org/frontend");
+            assertThat(policy.isAcceptAnySpiffeId()).isFalse();
+            
assertThat(policy.getSslSessionHeader()).isEqualTo("CamelCustomSSLSession");
+
+            SpiffeSecurityPolicy acceptAny = new SpiffeSecurityPolicy();
+            PropertyBindingSupport.setPropertiesOnTarget(context, acceptAny, 
Map.of("acceptAnySpiffeId", "true"));
+
+            assertThat(acceptAny.isAcceptAnySpiffeId()).isTrue();
+            
assertThat(acceptAny.getSslSessionHeader()).isEqualTo(SpiffeSecurityPolicy.DEFAULT_SSL_SESSION_HEADER);
+        }
+    }
+}
diff --git 
a/core/camel-util/src/main/java/org/apache/camel/util/ArtifactUtils.java 
b/core/camel-util/src/main/java/org/apache/camel/util/ArtifactUtils.java
index 6f54ab180d32..c526a64fa166 100644
--- a/core/camel-util/src/main/java/org/apache/camel/util/ArtifactUtils.java
+++ b/core/camel-util/src/main/java/org/apache/camel/util/ArtifactUtils.java
@@ -656,6 +656,10 @@ public final class ArtifactUtils {
                     
"org.apache.camel.support.MemoryKeyValueRepository|org.apache.camel.spi.KeyValueRepository|camel-support"),
             Map.<String, String> entry("MongoDbIdempotentRepository",
                     
"org.apache.camel.component.mongodb.processor.idempotent.MongoDbIdempotentRepository|org.apache.camel.spi.IdempotentRepository|camel-mongodb"),
+            Map.<String, String> entry("OpaSecurityPolicy",
+                    
"org.apache.camel.component.opa.security.OpaSecurityPolicy|org.apache.camel.spi.AuthorizationPolicy|camel-opa"),
+            Map.<String, String> entry("OpenFgaSecurityPolicy",
+                    
"org.apache.camel.component.openfga.security.OpenFgaSecurityPolicy|org.apache.camel.spi.AuthorizationPolicy|camel-openfga"),
             Map.<String, String> 
entry("OpensearchBulkRequestAggregationStrategy",
                     
"org.apache.camel.component.opensearch.aggregation.OpensearchBulkRequestAggregationStrategy|org.apache.camel.AggregationStrategy|camel-opensearch"),
             Map.<String, String> entry("RedisAggregationRepository",
@@ -664,6 +668,10 @@ public final class ArtifactUtils {
                     
"org.apache.camel.component.redis.RedisKeyValueRepository|org.apache.camel.spi.KeyValueRepository|camel-redis"),
             Map.<String, String> entry("SimpleScheduledRoutePolicy",
                     
"org.apache.camel.routepolicy.quartz.SimpleScheduledRoutePolicy|org.apache.camel.spi.RoutePolicy|camel-quartz"),
+            Map.<String, String> entry("SpiffeSSLContextParameters",
+                    
"org.apache.camel.component.spiffe.SpiffeSSLContextParameters|org.apache.camel.support.jsse.SSLContextParameters|camel-spiffe"),
+            Map.<String, String> entry("SpiffeSecurityPolicy",
+                    
"org.apache.camel.component.spiffe.SpiffeSecurityPolicy|org.apache.camel.spi.AuthorizationPolicy|camel-spiffe"),
             Map.<String, String> entry("SpringCacheIdempotentRepository",
                     
"org.apache.camel.spring.processor.idempotent.SpringCacheIdempotentRepository|org.apache.camel.spi.IdempotentRepository|camel-spring"),
             Map.<String, String> entry("SpringRedisIdempotentRepository",

Reply via email to