This is an automated email from the ASF dual-hosted git repository.
oscerd pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 0fe928c51062 CAMEL-24298: add an optional allowedSchemes allow-list to
the toD dynamic-URI EIP (#25315)
0fe928c51062 is described below
commit 0fe928c5106277357dedc947335631b21372401c
Author: Andrea Cosentino <[email protected]>
AuthorDate: Wed Aug 5 08:29:51 2026 +0200
CAMEL-24298: add an optional allowedSchemes allow-list to the toD
dynamic-URI EIP (#25315)
Co-authored-by: Claude Opus 4.8 <[email protected]>
---
.../org/apache/camel/catalog/docs/toD-eip.adoc | 39 ++++++++++
.../org/apache/camel/catalog/models/toD.json | 3 +-
.../org/apache/camel/catalog/models/wireTap.json | 3 +-
.../apache/camel/catalog/schemas/camel-spring.xsd | 11 +++
.../apache/camel/catalog/schemas/camel-xml-io.xsd | 11 +++
.../src/main/docs/modules/eips/pages/toD-eip.adoc | 39 ++++++++++
.../META-INF/org/apache/camel/model/toD.json | 3 +-
.../META-INF/org/apache/camel/model/wireTap.json | 3 +-
.../apache/camel/model/ToDynamicDefinition.java | 28 +++++++
.../camel/processor/SendDynamicProcessor.java | 38 +++++++++
.../org/apache/camel/reifier/ToDynamicReifier.java | 1 +
.../processor/ToDynamicAllowedSchemesTest.java | 91 ++++++++++++++++++++++
.../apache/camel/java/out/JavaDslModelWriter.java | 1 +
.../java/org/apache/camel/xml/in/ModelParser.java | 1 +
.../java/org/apache/camel/xml/out/ModelWriter.java | 1 +
.../org/apache/camel/yaml/out/YamlModelWriter.java | 1 +
.../dsl/yaml/deserializers/ModelDeserializers.java | 12 +++
.../resources/schema/camelYamlDsl-canonical.json | 10 +++
.../generated/resources/schema/camelYamlDsl.json | 10 +++
19 files changed, 302 insertions(+), 4 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/toD-eip.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/toD-eip.adoc
index fd2cc2e040d6..1f9fb853484e 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/toD-eip.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/toD-eip.adoc
@@ -417,3 +417,42 @@ during startup if `toD` was optimized, or if there was a
failure loading the opt
----
Detected SendDynamicAware component: http optimising toD:
http:myloginserver:8080/login?userid=${header.userName}
----
+
+== Restricting the allowed component schemes
+
+Because `toD` computes its endpoint uri at runtime, a route that interpolates
message content into the uri
+(for example `toD("${header.target}")`) can end up resolving to any component
on the classpath. In low-code or
+Kamelet-style deployments you may want to constrain this to a fixed set of
components. The optional `allowedSchemes`
+option takes a comma-separated allow-list of component schemes; a resolved
recipient whose scheme is not in the list
+is rejected (independently of `ignoreInvalidEndpoint`). By default the option
is unset and any scheme is allowed.
+
+[source,java]
+----
+from("direct:start")
+ // only http and https recipients are permitted
+ .toD().allowedSchemes("http,https").uri("${header.target}");
+----
+
+And in XML:
+
+[source,xml]
+----
+<route>
+ <from uri="direct:start"/>
+ <toD uri="${header.target}" allowedSchemes="http,https"/>
+</route>
+----
+
+And in YAML:
+
+[source,yaml]
+----
+- from:
+ uri: direct:start
+ steps:
+ - toD:
+ uri: "${header.target}"
+ allowedSchemes: "http,https"
+----
+
+NOTE: `wireTap` extends `toD` and therefore honours the same `allowedSchemes`
option.
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/toD.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/toD.json
index dc02feb1013d..14c5e2df2047 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/toD.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/toD.json
@@ -24,7 +24,8 @@
"cacheSize": { "index": 8, "kind": "attribute", "displayName": "Cache
Size", "group": "advanced", "label": "advanced", "required": false, "type":
"integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired":
false, "secret": false, "description": "Sets the maximum size used by the
ProducerCache which is used to cache and reuse producers when uris are reused.
Use 0 for default cache size, or -1 to turn cache off." },
"ignoreInvalidEndpoint": { "index": 9, "kind": "attribute", "displayName":
"Ignore Invalid Endpoint", "group": "advanced", "label": "advanced",
"required": false, "type": "boolean", "javaType": "java.lang.Boolean",
"deprecated": false, "autowired": false, "secret": false, "defaultValue":
false, "description": "Whether to ignore invalid endpoint URIs and skip sending
the message." },
"allowOptimisedComponents": { "index": 10, "kind": "attribute",
"displayName": "Allow Optimised Components", "group": "advanced", "label":
"advanced", "required": false, "type": "boolean", "javaType":
"java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": true, "description": "Whether to allow components to optimise
toD if they are SendDynamicAware." },
- "autoStartComponents": { "index": 11, "kind": "attribute", "displayName":
"Auto Start Components", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": true, "description":
"Whether to auto startup components when toD is starting up." }
+ "autoStartComponents": { "index": 11, "kind": "attribute", "displayName":
"Auto Start Components", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": true, "description":
"Whether to auto startup components when toD is starting up." },
+ "allowedSchemes": { "index": 12, "kind": "attribute", "displayName":
"Allowed Schemes", "group": "security", "label": "advanced,security",
"required": false, "type": "string", "javaType": "java.lang.String",
"deprecated": false, "autowired": false, "secret": false, "description": "Sets
an optional comma-separated allow-list of component schemes that the dynamic
recipient may resolve to (e.g. http,https). When set, a dynamic endpoint whose
scheme is not in the list is rejected. This i [...]
},
"exchangeProperties": {
"CamelToEndpoint": { "index": 0, "kind": "exchangeProperty",
"displayName": "To Endpoint", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Endpoint URI where this Exchange is being sent to" }
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/wireTap.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/wireTap.json
index ffbf852b5537..e4ec0901987c 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/wireTap.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/wireTap.json
@@ -27,7 +27,8 @@
"cacheSize": { "index": 11, "kind": "attribute", "displayName": "Cache
Size", "group": "advanced", "label": "advanced", "required": false, "type":
"integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired":
false, "secret": false, "description": "Sets the maximum size used by the
ProducerCache which is used to cache and reuse producers when uris are reused.
Use 0 for default cache size, or -1 to turn cache off." },
"ignoreInvalidEndpoint": { "index": 12, "kind": "attribute",
"displayName": "Ignore Invalid Endpoint", "group": "advanced", "label":
"advanced", "required": false, "type": "boolean", "javaType":
"java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Whether to ignore invalid endpoint URIs
and skip sending the message." },
"allowOptimisedComponents": { "index": 13, "kind": "attribute",
"displayName": "Allow Optimised Components", "group": "advanced", "label":
"advanced", "required": false, "type": "boolean", "javaType":
"java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": true, "description": "Whether to allow components to optimise
toD if they are SendDynamicAware." },
- "autoStartComponents": { "index": 14, "kind": "attribute", "displayName":
"Auto Start Components", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": true, "description":
"Whether to auto startup components when toD is starting up." }
+ "autoStartComponents": { "index": 14, "kind": "attribute", "displayName":
"Auto Start Components", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": true, "description":
"Whether to auto startup components when toD is starting up." },
+ "allowedSchemes": { "index": 15, "kind": "attribute", "displayName":
"Allowed Schemes", "group": "security", "label": "advanced,security",
"required": false, "type": "string", "javaType": "java.lang.String",
"deprecated": false, "autowired": false, "secret": false, "description": "Sets
an optional comma-separated allow-list of component schemes that the dynamic
recipient may resolve to (e.g. http,https). When set, a dynamic endpoint whose
scheme is not in the list is rejected. This i [...]
},
"exchangeProperties": {
"CamelToEndpoint": { "index": 0, "kind": "exchangeProperty",
"displayName": "To Endpoint", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Endpoint URI where this Exchange is being sent to" }
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd
index 4b463bb4cf2a..ee5fb9097f7d 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd
@@ -14006,6 +14006,17 @@ Whether to allow components to optimise toD if they
are SendDynamicAware. Defaul
<xs:documentation xml:lang="en">
<![CDATA[
Whether to auto startup components when toD is starting up. Default value: true
+]]>
+ </xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="allowedSchemes" type="xs:string">
+ <xs:annotation>
+ <xs:documentation xml:lang="en">
+<![CDATA[
+Sets an optional comma-separated allow-list of component schemes that the
dynamic recipient may resolve to (e.g.
+http,https). When set, a dynamic endpoint whose scheme is not in the list is
rejected. This is a defence-in-depth
+restriction, useful for low-code / Kamelet deployments; by default (unset) any
scheme is allowed.
]]>
</xs:documentation>
</xs:annotation>
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-xml-io.xsd
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-xml-io.xsd
index 10b2c2dea867..f7eeccaf7b22 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-xml-io.xsd
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-xml-io.xsd
@@ -13149,6 +13149,17 @@ Whether to allow components to optimise toD if they
are SendDynamicAware. Defaul
<xs:documentation xml:lang="en">
<![CDATA[
Whether to auto startup components when toD is starting up. Default value: true
+]]>
+ </xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="allowedSchemes" type="xs:string">
+ <xs:annotation>
+ <xs:documentation xml:lang="en">
+<![CDATA[
+Sets an optional comma-separated allow-list of component schemes that the
dynamic recipient may resolve to (e.g.
+http,https). When set, a dynamic endpoint whose scheme is not in the list is
rejected. This is a defence-in-depth
+restriction, useful for low-code / Kamelet deployments; by default (unset) any
scheme is allowed.
]]>
</xs:documentation>
</xs:annotation>
diff --git
a/core/camel-core-engine/src/main/docs/modules/eips/pages/toD-eip.adoc
b/core/camel-core-engine/src/main/docs/modules/eips/pages/toD-eip.adoc
index fd2cc2e040d6..1f9fb853484e 100644
--- a/core/camel-core-engine/src/main/docs/modules/eips/pages/toD-eip.adoc
+++ b/core/camel-core-engine/src/main/docs/modules/eips/pages/toD-eip.adoc
@@ -417,3 +417,42 @@ during startup if `toD` was optimized, or if there was a
failure loading the opt
----
Detected SendDynamicAware component: http optimising toD:
http:myloginserver:8080/login?userid=${header.userName}
----
+
+== Restricting the allowed component schemes
+
+Because `toD` computes its endpoint uri at runtime, a route that interpolates
message content into the uri
+(for example `toD("${header.target}")`) can end up resolving to any component
on the classpath. In low-code or
+Kamelet-style deployments you may want to constrain this to a fixed set of
components. The optional `allowedSchemes`
+option takes a comma-separated allow-list of component schemes; a resolved
recipient whose scheme is not in the list
+is rejected (independently of `ignoreInvalidEndpoint`). By default the option
is unset and any scheme is allowed.
+
+[source,java]
+----
+from("direct:start")
+ // only http and https recipients are permitted
+ .toD().allowedSchemes("http,https").uri("${header.target}");
+----
+
+And in XML:
+
+[source,xml]
+----
+<route>
+ <from uri="direct:start"/>
+ <toD uri="${header.target}" allowedSchemes="http,https"/>
+</route>
+----
+
+And in YAML:
+
+[source,yaml]
+----
+- from:
+ uri: direct:start
+ steps:
+ - toD:
+ uri: "${header.target}"
+ allowedSchemes: "http,https"
+----
+
+NOTE: `wireTap` extends `toD` and therefore honours the same `allowedSchemes`
option.
diff --git
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/toD.json
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/toD.json
index dc02feb1013d..14c5e2df2047 100644
---
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/toD.json
+++
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/toD.json
@@ -24,7 +24,8 @@
"cacheSize": { "index": 8, "kind": "attribute", "displayName": "Cache
Size", "group": "advanced", "label": "advanced", "required": false, "type":
"integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired":
false, "secret": false, "description": "Sets the maximum size used by the
ProducerCache which is used to cache and reuse producers when uris are reused.
Use 0 for default cache size, or -1 to turn cache off." },
"ignoreInvalidEndpoint": { "index": 9, "kind": "attribute", "displayName":
"Ignore Invalid Endpoint", "group": "advanced", "label": "advanced",
"required": false, "type": "boolean", "javaType": "java.lang.Boolean",
"deprecated": false, "autowired": false, "secret": false, "defaultValue":
false, "description": "Whether to ignore invalid endpoint URIs and skip sending
the message." },
"allowOptimisedComponents": { "index": 10, "kind": "attribute",
"displayName": "Allow Optimised Components", "group": "advanced", "label":
"advanced", "required": false, "type": "boolean", "javaType":
"java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": true, "description": "Whether to allow components to optimise
toD if they are SendDynamicAware." },
- "autoStartComponents": { "index": 11, "kind": "attribute", "displayName":
"Auto Start Components", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": true, "description":
"Whether to auto startup components when toD is starting up." }
+ "autoStartComponents": { "index": 11, "kind": "attribute", "displayName":
"Auto Start Components", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": true, "description":
"Whether to auto startup components when toD is starting up." },
+ "allowedSchemes": { "index": 12, "kind": "attribute", "displayName":
"Allowed Schemes", "group": "security", "label": "advanced,security",
"required": false, "type": "string", "javaType": "java.lang.String",
"deprecated": false, "autowired": false, "secret": false, "description": "Sets
an optional comma-separated allow-list of component schemes that the dynamic
recipient may resolve to (e.g. http,https). When set, a dynamic endpoint whose
scheme is not in the list is rejected. This i [...]
},
"exchangeProperties": {
"CamelToEndpoint": { "index": 0, "kind": "exchangeProperty",
"displayName": "To Endpoint", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Endpoint URI where this Exchange is being sent to" }
diff --git
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/wireTap.json
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/wireTap.json
index ffbf852b5537..e4ec0901987c 100644
---
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/wireTap.json
+++
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/wireTap.json
@@ -27,7 +27,8 @@
"cacheSize": { "index": 11, "kind": "attribute", "displayName": "Cache
Size", "group": "advanced", "label": "advanced", "required": false, "type":
"integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired":
false, "secret": false, "description": "Sets the maximum size used by the
ProducerCache which is used to cache and reuse producers when uris are reused.
Use 0 for default cache size, or -1 to turn cache off." },
"ignoreInvalidEndpoint": { "index": 12, "kind": "attribute",
"displayName": "Ignore Invalid Endpoint", "group": "advanced", "label":
"advanced", "required": false, "type": "boolean", "javaType":
"java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Whether to ignore invalid endpoint URIs
and skip sending the message." },
"allowOptimisedComponents": { "index": 13, "kind": "attribute",
"displayName": "Allow Optimised Components", "group": "advanced", "label":
"advanced", "required": false, "type": "boolean", "javaType":
"java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": true, "description": "Whether to allow components to optimise
toD if they are SendDynamicAware." },
- "autoStartComponents": { "index": 14, "kind": "attribute", "displayName":
"Auto Start Components", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": true, "description":
"Whether to auto startup components when toD is starting up." }
+ "autoStartComponents": { "index": 14, "kind": "attribute", "displayName":
"Auto Start Components", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": true, "description":
"Whether to auto startup components when toD is starting up." },
+ "allowedSchemes": { "index": 15, "kind": "attribute", "displayName":
"Allowed Schemes", "group": "security", "label": "advanced,security",
"required": false, "type": "string", "javaType": "java.lang.String",
"deprecated": false, "autowired": false, "secret": false, "description": "Sets
an optional comma-separated allow-list of component schemes that the dynamic
recipient may resolve to (e.g. http,https). When set, a dynamic endpoint whose
scheme is not in the list is rejected. This i [...]
},
"exchangeProperties": {
"CamelToEndpoint": { "index": 0, "kind": "exchangeProperty",
"displayName": "To Endpoint", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Endpoint URI where this Exchange is being sent to" }
diff --git
a/core/camel-core-model/src/main/java/org/apache/camel/model/ToDynamicDefinition.java
b/core/camel-core-model/src/main/java/org/apache/camel/model/ToDynamicDefinition.java
index 076280d6380c..1d0b0a9f3e56 100644
---
a/core/camel-core-model/src/main/java/org/apache/camel/model/ToDynamicDefinition.java
+++
b/core/camel-core-model/src/main/java/org/apache/camel/model/ToDynamicDefinition.java
@@ -74,6 +74,13 @@ public class ToDynamicDefinition extends
NoOutputDefinition<ToDynamicDefinition>
@Metadata(label = "advanced", defaultValue = "true", javaType =
"java.lang.Boolean",
description = "Whether to auto startup components when toD is
starting up.")
private String autoStartComponents;
+ @XmlAttribute
+ @Metadata(label = "advanced,security",
+ description = "Sets an optional comma-separated allow-list of
component schemes that the dynamic recipient"
+ + " may resolve to (e.g. http,https). When set, a
dynamic endpoint whose scheme is not in the"
+ + " list is rejected. This is a defence-in-depth
restriction, useful for low-code / Kamelet"
+ + " deployments; by default (unset) any scheme is
allowed.")
+ private String allowedSchemes;
public ToDynamicDefinition() {
}
@@ -93,6 +100,7 @@ public class ToDynamicDefinition extends
NoOutputDefinition<ToDynamicDefinition>
this.ignoreInvalidEndpoint = source.ignoreInvalidEndpoint;
this.allowOptimisedComponents = source.allowOptimisedComponents;
this.autoStartComponents = source.autoStartComponents;
+ this.allowedSchemes = source.allowedSchemes;
}
@Override
@@ -264,6 +272,18 @@ public class ToDynamicDefinition extends
NoOutputDefinition<ToDynamicDefinition>
return this;
}
+ /**
+ * Sets an optional comma-separated allow-list of component schemes that
the dynamic recipient may resolve to (e.g.
+ * http,https). When set, a dynamic endpoint whose scheme is not in the
list is rejected. By default (unset) any
+ * scheme is allowed.
+ *
+ * @return the builder
+ */
+ public ToDynamicDefinition allowedSchemes(String allowedSchemes) {
+ setAllowedSchemes(allowedSchemes);
+ return this;
+ }
+
// Properties
//
-------------------------------------------------------------------------
@@ -339,6 +359,14 @@ public class ToDynamicDefinition extends
NoOutputDefinition<ToDynamicDefinition>
this.autoStartComponents = autoStartComponents;
}
+ public String getAllowedSchemes() {
+ return allowedSchemes;
+ }
+
+ public void setAllowedSchemes(String allowedSchemes) {
+ this.allowedSchemes = allowedSchemes;
+ }
+
public ToDynamicDefinition copyDefinition() {
return new ToDynamicDefinition(this);
}
diff --git
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/SendDynamicProcessor.java
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/SendDynamicProcessor.java
index 2104806dff08..b1e48e7f78c3 100644
---
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/SendDynamicProcessor.java
+++
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/SendDynamicProcessor.java
@@ -16,7 +16,9 @@
*/
package org.apache.camel.processor;
+import java.util.HashSet;
import java.util.Map;
+import java.util.Set;
import org.apache.camel.AsyncCallback;
import org.apache.camel.CamelContext;
@@ -77,6 +79,8 @@ public class SendDynamicProcessor extends BaseProcessorSupport
protected int cacheSize;
protected boolean allowOptimisedComponents = true;
protected boolean autoStartupComponents = true;
+ protected String allowedSchemes;
+ private Set<String> allowedSchemesSet;
public SendDynamicProcessor(String uri, Expression expression) {
this.uri = uri;
@@ -172,6 +176,18 @@ public class SendDynamicProcessor extends
BaseProcessorSupport
callback.done(true);
return true;
}
+ // enforce the optional allowed-schemes allow-list on the resolved
dynamic recipient (CAMEL-24298);
+ // a disallowed scheme is always rejected, independently of
ignoreInvalidEndpoint
+ if (allowedSchemes != null) {
+ String targetUri = targetRecipient.toString();
+ String targetScheme = resolveScheme(exchange, targetUri);
+ if (targetScheme != null && !isSchemeAllowed(targetScheme)) {
+ exchange.setException(new ResolveEndpointFailedException(
+ targetUri, "Scheme " + targetScheme + " is not in
the allowed schemes: " + allowedSchemes));
+ callback.done(true);
+ return true;
+ }
+ }
Endpoint existing = getExistingEndpoint(exchange, targetRecipient);
if (existing == null) {
endpoint = resolveEndpoint(exchange, targetRecipient,
prototype);
@@ -494,4 +510,26 @@ public class SendDynamicProcessor extends
BaseProcessorSupport
public void setAutoStartupComponents(boolean autoStartupComponents) {
this.autoStartupComponents = autoStartupComponents;
}
+
+ public String getAllowedSchemes() {
+ return allowedSchemes;
+ }
+
+ public void setAllowedSchemes(String allowedSchemes) {
+ this.allowedSchemes = allowedSchemes;
+ if (allowedSchemes != null) {
+ // pre-parse once into a set, so the per-exchange check does not
allocate/split on every message
+ Set<String> set = new HashSet<>();
+ for (String allowed : allowedSchemes.split(",")) {
+ set.add(allowed.trim());
+ }
+ this.allowedSchemesSet = set;
+ } else {
+ this.allowedSchemesSet = null;
+ }
+ }
+
+ private boolean isSchemeAllowed(String scheme) {
+ return allowedSchemesSet == null || allowedSchemesSet.contains(scheme);
+ }
}
diff --git
a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/ToDynamicReifier.java
b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/ToDynamicReifier.java
index 291c0936430d..47b50e8f5a32 100644
---
a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/ToDynamicReifier.java
+++
b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/ToDynamicReifier.java
@@ -77,6 +77,7 @@ public class ToDynamicReifier<T extends ToDynamicDefinition>
extends ProcessorRe
if (definition.getAutoStartComponents() != null) {
processor.setAutoStartupComponents(parseBoolean(definition.getAutoStartComponents(),
true));
}
+
processor.setAllowedSchemes(parseString(definition.getAllowedSchemes()));
return processor;
}
diff --git
a/core/camel-core/src/test/java/org/apache/camel/processor/ToDynamicAllowedSchemesTest.java
b/core/camel-core/src/test/java/org/apache/camel/processor/ToDynamicAllowedSchemesTest.java
new file mode 100644
index 000000000000..14d6be372249
--- /dev/null
+++
b/core/camel-core/src/test/java/org/apache/camel/processor/ToDynamicAllowedSchemesTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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.processor;
+
+import org.apache.camel.CamelExecutionException;
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.ResolveEndpointFailedException;
+import org.apache.camel.builder.RouteBuilder;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * The optional {@code allowedSchemes} allow-list on {@code toD} restricts
which component schemes a dynamic recipient
+ * may resolve to. A recipient whose scheme is not in the list is rejected,
independently of
+ * {@code ignoreInvalidEndpoint}. See CAMEL-24298.
+ */
+class ToDynamicAllowedSchemesTest extends ContextTestSupport {
+
+ @Test
+ void allowedSchemeIsSent() throws Exception {
+ getMockEndpoint("mock:allowed").expectedMessageCount(1);
+
+ template.sendBodyAndHeader("direct:start", "Hello", "target",
"mock:allowed");
+
+ assertMockEndpointsSatisfied();
+ }
+
+ @Test
+ void disallowedSchemeIsRejected() {
+ assertThatThrownBy(() -> template.sendBodyAndHeader("direct:start",
"Hello", "target", "seda:blocked"))
+ .isInstanceOf(CamelExecutionException.class)
+ .cause()
+ .isInstanceOf(ResolveEndpointFailedException.class)
+ .hasMessageContaining("not in the allowed schemes");
+ }
+
+ @Test
+ void multipleAllowedSchemesHonored() throws Exception {
+ getMockEndpoint("mock:ok").expectedMessageCount(1);
+
+ // both mock and seda are in the allow-list
+ template.sendBodyAndHeader("direct:multi", "Hello", "target",
"mock:ok");
+ template.sendBodyAndHeader("direct:multi", "Hello", "target",
"seda:ok");
+
+ assertMockEndpointsSatisfied();
+
+ // a scheme outside the allow-list is still rejected
+ assertThatThrownBy(() -> template.sendBodyAndHeader("direct:multi",
"Hello", "target", "log:blocked"))
+ .isInstanceOf(CamelExecutionException.class)
+ .cause()
+ .isInstanceOf(ResolveEndpointFailedException.class);
+ }
+
+ @Test
+ void disallowedSchemeRejectedEvenWithIgnoreInvalidEndpoint() {
+ // a disallowed scheme hard-fails even when
ignoreInvalidEndpoint=true: the allow-list check sets the
+ // exception directly instead of throwing, so it bypasses the
ignoreInvalidEndpoint handling
+ assertThatThrownBy(() -> template.sendBodyAndHeader("direct:ignore",
"Hello", "target", "seda:blocked"))
+ .isInstanceOf(CamelExecutionException.class)
+ .cause()
+ .isInstanceOf(ResolveEndpointFailedException.class)
+ .hasMessageContaining("not in the allowed schemes");
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+
from("direct:start").toD().allowedSchemes("mock").uri("${header.target}");
+
from("direct:multi").toD().allowedSchemes("mock,seda").uri("${header.target}");
+
from("direct:ignore").toD().ignoreInvalidEndpoint(true).allowedSchemes("mock").uri("${header.target}");
+ }
+ };
+ }
+}
diff --git
a/core/camel-java-io/src/generated/java/org/apache/camel/java/out/JavaDslModelWriter.java
b/core/camel-java-io/src/generated/java/org/apache/camel/java/out/JavaDslModelWriter.java
index dc001845851a..cae1868f093d 100644
---
a/core/camel-java-io/src/generated/java/org/apache/camel/java/out/JavaDslModelWriter.java
+++
b/core/camel-java-io/src/generated/java/org/apache/camel/java/out/JavaDslModelWriter.java
@@ -2614,6 +2614,7 @@ public class JavaDslModelWriter extends
JavaDslModelWriterSupport {
doWriteAttribute(sb, "ignoreInvalidEndpoint",
def.getIgnoreInvalidEndpoint(), null);
doWriteAttribute(sb, "allowOptimisedComponents",
def.getAllowOptimisedComponents(), "true");
doWriteAttribute(sb, "autoStartComponents",
def.getAutoStartComponents(), "true");
+ doWriteAttribute(sb, "allowedSchemes", def.getAllowedSchemes(), null);
}
protected void doWriteToDynamicDefinition(StringBuilder sb,
ToDynamicDefinition def) {
doWriteToDynamicDefinitionAttributes(sb, def);
diff --git
a/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java
b/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java
index e8213adb2cac..19c5adcb7258 100644
---
a/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java
+++
b/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java
@@ -1281,6 +1281,7 @@ public class ModelParser extends BaseParser {
protected <T extends ToDynamicDefinition> AttributeHandler<T>
toDynamicDefinitionAttributeHandler() {
return (def, key, val) -> switch (key) {
case "allowOptimisedComponents":
def.setAllowOptimisedComponents(val); yield true;
+ case "allowedSchemes": def.setAllowedSchemes(val); yield true;
case "autoStartComponents": def.setAutoStartComponents(val); yield
true;
case "cacheSize": def.setCacheSize(val); yield true;
case "ignoreInvalidEndpoint": def.setIgnoreInvalidEndpoint(val);
yield true;
diff --git
a/core/camel-xml-io/src/generated/java/org/apache/camel/xml/out/ModelWriter.java
b/core/camel-xml-io/src/generated/java/org/apache/camel/xml/out/ModelWriter.java
index 00128f8a4b65..1b949e1a840c 100644
---
a/core/camel-xml-io/src/generated/java/org/apache/camel/xml/out/ModelWriter.java
+++
b/core/camel-xml-io/src/generated/java/org/apache/camel/xml/out/ModelWriter.java
@@ -1884,6 +1884,7 @@ public class ModelWriter extends BaseWriter {
doWriteAttribute("ignoreInvalidEndpoint",
def.getIgnoreInvalidEndpoint(), null);
doWriteAttribute("allowOptimisedComponents",
def.getAllowOptimisedComponents(), "true");
doWriteAttribute("autoStartComponents", def.getAutoStartComponents(),
"true");
+ doWriteAttribute("allowedSchemes", def.getAllowedSchemes(), null);
}
protected void doWriteToDynamicDefinition(String name, ToDynamicDefinition
def) throws IOException {
startElement(name);
diff --git
a/core/camel-yaml-io/src/generated/java/org/apache/camel/yaml/out/YamlModelWriter.java
b/core/camel-yaml-io/src/generated/java/org/apache/camel/yaml/out/YamlModelWriter.java
index 16c4a74440de..460305fcbcba 100644
---
a/core/camel-yaml-io/src/generated/java/org/apache/camel/yaml/out/YamlModelWriter.java
+++
b/core/camel-yaml-io/src/generated/java/org/apache/camel/yaml/out/YamlModelWriter.java
@@ -1880,6 +1880,7 @@ public class YamlModelWriter extends
YamlModelWriterSupport {
doWriteAttribute(jo, "ignoreInvalidEndpoint",
def.getIgnoreInvalidEndpoint(), null);
doWriteAttribute(jo, "allowOptimisedComponents",
def.getAllowOptimisedComponents(), "true");
doWriteAttribute(jo, "autoStartComponents",
def.getAutoStartComponents(), "true");
+ doWriteAttribute(jo, "allowedSchemes", def.getAllowedSchemes(), null);
}
protected JsonObject doWriteToDynamicDefinition(ToDynamicDefinition def) {
JsonObject jo = new JsonObject();
diff --git
a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java
b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java
index 01e0cd561c18..50b7c6e090a6 100644
---
a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java
+++
b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java
@@ -18416,6 +18416,7 @@ public final class ModelDeserializers extends
YamlDeserializerSupport {
deprecated = false,
properties = {
@YamlProperty(name = "allowOptimisedComponents", type =
"boolean", defaultValue = "true", description = "Whether to allow components to
optimise toD if they are SendDynamicAware.", displayName = "Allow Optimised
Components"),
+ @YamlProperty(name = "allowedSchemes", type = "string",
description = "Sets an optional comma-separated allow-list of component schemes
that the dynamic recipient may resolve to (e.g. http,https). When set, a
dynamic endpoint whose scheme is not in the list is rejected. This is a
defence-in-depth restriction, useful for low-code / Kamelet deployments; by
default (unset) any scheme is allowed.", displayName = "Allowed Schemes"),
@YamlProperty(name = "autoStartComponents", type =
"boolean", defaultValue = "true", description = "Whether to auto startup
components when toD is starting up.", displayName = "Auto Start Components"),
@YamlProperty(name = "cacheSize", type = "number",
description = "Sets the maximum size used by the ProducerCache which is used to
cache and reuse producers when uris are reused. Use 0 for default cache size,
or -1 to turn cache off.", displayName = "Cache Size"),
@YamlProperty(name = "description", type = "string",
description = "The description for this node", displayName = "Description"),
@@ -18461,6 +18462,11 @@ public final class ModelDeserializers extends
YamlDeserializerSupport {
target.setAllowOptimisedComponents(val);
break;
}
+ case "allowedSchemes": {
+ String val = asText(node);
+ target.setAllowedSchemes(val);
+ break;
+ }
case "autoStartComponents": {
String val = asText(node);
target.setAutoStartComponents(val);
@@ -20491,6 +20497,7 @@ public final class ModelDeserializers extends
YamlDeserializerSupport {
deprecated = false,
properties = {
@YamlProperty(name = "allowOptimisedComponents", type =
"boolean", defaultValue = "true", description = "Whether to allow components to
optimise toD if they are SendDynamicAware.", displayName = "Allow Optimised
Components"),
+ @YamlProperty(name = "allowedSchemes", type = "string",
description = "Sets an optional comma-separated allow-list of component schemes
that the dynamic recipient may resolve to (e.g. http,https). When set, a
dynamic endpoint whose scheme is not in the list is rejected. This is a
defence-in-depth restriction, useful for low-code / Kamelet deployments; by
default (unset) any scheme is allowed.", displayName = "Allowed Schemes"),
@YamlProperty(name = "autoStartComponents", type =
"boolean", defaultValue = "true", description = "Whether to auto startup
components when toD is starting up.", displayName = "Auto Start Components"),
@YamlProperty(name = "cacheSize", type = "number",
description = "Sets the maximum size used by the ProducerCache which is used to
cache and reuse producers when uris are reused. Use 0 for default cache size,
or -1 to turn cache off.", displayName = "Cache Size"),
@YamlProperty(name = "copy", type = "boolean",
defaultValue = "true", description = "Whether to use a copy of the original
exchange.", displayName = "Copy"),
@@ -20534,6 +20541,11 @@ public final class ModelDeserializers extends
YamlDeserializerSupport {
target.setAllowOptimisedComponents(val);
break;
}
+ case "allowedSchemes": {
+ String val = asText(node);
+ target.setAllowedSchemes(val);
+ break;
+ }
case "autoStartComponents": {
String val = asText(node);
target.setAutoStartComponents(val);
diff --git
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-canonical.json
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-canonical.json
index f80e09111ed4..3f82b7efc540 100644
---
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-canonical.json
+++
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-canonical.json
@@ -5252,6 +5252,11 @@
"description" : "Whether to allow components to optimise toD if
they are SendDynamicAware.",
"default" : true
},
+ "allowedSchemes" : {
+ "type" : "string",
+ "title" : "Allowed Schemes",
+ "description" : "Sets an optional comma-separated allow-list of
component schemes that the dynamic recipient may resolve to (e.g. http,https).
When set, a dynamic endpoint whose scheme is not in the list is rejected. This
is a defence-in-depth restriction, useful for low-code / Kamelet deployments;
by default (unset) any scheme is allowed."
+ },
"autoStartComponents" : {
"type" : "boolean",
"title" : "Auto Start Components",
@@ -5826,6 +5831,11 @@
"description" : "Whether to allow components to optimise toD if
they are SendDynamicAware.",
"default" : true
},
+ "allowedSchemes" : {
+ "type" : "string",
+ "title" : "Allowed Schemes",
+ "description" : "Sets an optional comma-separated allow-list of
component schemes that the dynamic recipient may resolve to (e.g. http,https).
When set, a dynamic endpoint whose scheme is not in the list is rejected. This
is a defence-in-depth restriction, useful for low-code / Kamelet deployments;
by default (unset) any scheme is allowed."
+ },
"autoStartComponents" : {
"type" : "boolean",
"title" : "Auto Start Components",
diff --git
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json
index 03bf74cf5316..72766d7318fa 100644
---
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json
+++
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json
@@ -8103,6 +8103,11 @@
"description" : "Whether to allow components to optimise toD if
they are SendDynamicAware.",
"default" : true
},
+ "allowedSchemes" : {
+ "type" : "string",
+ "title" : "Allowed Schemes",
+ "description" : "Sets an optional comma-separated allow-list of
component schemes that the dynamic recipient may resolve to (e.g. http,https).
When set, a dynamic endpoint whose scheme is not in the list is rejected. This
is a defence-in-depth restriction, useful for low-code / Kamelet deployments;
by default (unset) any scheme is allowed."
+ },
"autoStartComponents" : {
"type" : "boolean",
"title" : "Auto Start Components",
@@ -9313,6 +9318,11 @@
"description" : "Whether to allow components to optimise toD if
they are SendDynamicAware.",
"default" : true
},
+ "allowedSchemes" : {
+ "type" : "string",
+ "title" : "Allowed Schemes",
+ "description" : "Sets an optional comma-separated allow-list of
component schemes that the dynamic recipient may resolve to (e.g. http,https).
When set, a dynamic endpoint whose scheme is not in the list is rejected. This
is a defence-in-depth restriction, useful for low-code / Kamelet deployments;
by default (unset) any scheme is allowed."
+ },
"autoStartComponents" : {
"type" : "boolean",
"title" : "Auto Start Components",