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 d5160fd41ba6 CAMEL-24578: Configure Avro ClassSecurityValidator at
runtime
d5160fd41ba6 is described below
commit d5160fd41ba67df212cdf8840f8e5b2aa611303a
Author: Omar Atie <[email protected]>
AuthorDate: Wed Sep 2 11:05:11 2026 -0700
CAMEL-24578: Configure Avro ClassSecurityValidator at runtime
Apache Avro 1.12.2 introduced a ClassSecurityValidator that rejects IPC
handshake and application model classes unless the SERIALIZABLE_PACKAGES
JVM property is set manually, breaking camel-avro-rpc and camel-avro out
of the box. This configures the validator automatically: camel-avro-rpc
trusts org.apache.avro.ipc and infers model packages from the protocol
class, namespace and schema types; the camel-avro data format trusts
packages from the schema/instance class and the marshal graph at runtime.
A new serializablePackages option (endpoint and data format) allows
declaring additional trusted packages, marked
security="insecure:serialization".
Includes tests that run without the Surefire SERIALIZABLE_PACKAGES vmargs
and an upgrade-guide entry.
Closes #26026
Co-authored-by: Cursor Agent <[email protected]>
---
.../org/apache/camel/catalog/components/avro.json | 6 +-
.../org/apache/camel/catalog/dataformats/avro.json | 3 +-
.../org/apache/camel/catalog/models/avro.json | 3 +-
.../apache/camel/catalog/schemas/camel-spring.xsd | 10 +
.../apache/camel/catalog/schemas/camel-xml-io.xsd | 10 +
.../camel/catalog/schemas/camelYamlDsl-model.json | 10 +
.../camel-avro-rpc-component/pom.xml | 9 +-
.../component/avro/AvroComponentConfigurer.java | 6 +
.../component/avro/AvroEndpointConfigurer.java | 6 +
.../component/avro/AvroEndpointUriFactory.java | 3 +-
.../org/apache/camel/component/avro/avro.json | 6 +-
.../apache/camel/component/avro/AvroComponent.java | 7 +
.../camel/component/avro/AvroConfiguration.java | 15 +
.../apache/camel/component/avro/AvroEndpoint.java | 11 +
.../avro/AvroClassSecurityWithoutVmArgsTest.java | 102 +++++++
components/camel-avro/pom.xml | 5 -
.../dataformat/avro/AvroDataFormatConfigurer.java | 7 +
.../org/apache/camel/dataformat/avro/avro.json | 3 +-
.../avro/support/AvroClassSecuritySupport.java | 309 +++++++++++++++++++++
.../camel/dataformat/avro/AvroDataFormat.java | 25 ++
.../avro/support/AvroClassSecuritySupportTest.java | 122 ++++++++
.../dataformat/avro/example/nested/NestedFoo.java | 20 ++
.../jackson/avro/JacksonAvroDataFormat.java | 3 +-
.../jackson3/avro/JacksonAvroDataFormat.java | 3 +-
.../org/apache/camel/model/dataformat/avro.json | 3 +-
.../camel/model/dataformat/AvroDataFormat.java | 23 ++
.../reifier/dataformat/AvroDataFormatReifier.java | 1 +
.../apache/camel/java/out/JavaDslModelWriter.java | 3 +-
.../java/org/apache/camel/util/SecurityUtils.java | 1 +
.../java/org/apache/camel/xml/in/ModelParser.java | 1 +
.../java/org/apache/camel/xml/out/ModelWriter.java | 3 +-
.../org/apache/camel/yaml/out/YamlModelWriter.java | 3 +-
.../ROOT/pages/camel-4x-upgrade-guide-4_23.adoc | 32 ++-
.../component/dsl/AvroComponentBuilderFactory.java | 20 ++
.../endpoint/dsl/AvroEndpointBuilderFactory.java | 54 ++++
.../dsl/yaml/deserializers/ModelDeserializers.java | 6 +
.../resources/schema/camelYamlDsl-canonical.json | 5 +
.../resources/schema/camelYamlDsl-model.json | 10 +
.../generated/resources/schema/camelYamlDsl.json | 5 +
39 files changed, 844 insertions(+), 30 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/avro.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/avro.json
index 197c0b609fe9..e7dc315189b5 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/avro.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/avro.json
@@ -33,7 +33,8 @@
"bridgeErrorHandler": { "index": 6, "kind": "property", "displayName":
"Bridge Error Handler", "group": "consumer", "label": "consumer", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": false, "description":
"Allows for bridging the consumer to the Camel routing Error Handler, which
mean any exceptions (if possible) occurred while the Camel consumer is trying
to pickup incoming messages, or the like [...]
"lazyStartProducer": { "index": 7, "kind": "property", "displayName":
"Lazy Start Producer", "group": "producer", "label": "producer", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": false, "description":
"Whether the producer should be started lazy (on the first message). By
starting lazy you can use this to allow CamelContext and routes to startup in
situations where a producer may otherwise fail [...]
"autowiredEnabled": { "index": 8, "kind": "property", "displayName":
"Autowired Enabled", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": true, "description":
"Whether autowiring is enabled. This is used for automatic autowiring options
(the option must be marked as autowired) by looking up in the registry to find
if there is a single instance of matching t [...]
- "configuration": { "index": 9, "kind": "property", "displayName":
"Configuration", "group": "advanced", "label": "advanced", "required": false,
"type": "object", "javaType":
"org.apache.camel.component.avro.AvroConfiguration", "deprecated": false,
"autowired": false, "secret": false, "description": "To use a shared
AvroConfiguration to configure options once" }
+ "configuration": { "index": 9, "kind": "property", "displayName":
"Configuration", "group": "advanced", "label": "advanced", "required": false,
"type": "object", "javaType":
"org.apache.camel.component.avro.AvroConfiguration", "deprecated": false,
"autowired": false, "secret": false, "description": "To use a shared
AvroConfiguration to configure options once" },
+ "serializablePackages": { "index": 10, "kind": "property", "displayName":
"Serializable Packages", "group": "security", "label": "security", "required":
false, "type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": false, "security": "insecure:serialization",
"configurationClass": "org.apache.camel.component.avro.AvroConfiguration",
"configurationField": "configuration", "description": "Comma-separated list of
additional packages that con [...]
},
"headers": {
"CamelAvroMessageName": { "index": 0, "kind": "header", "displayName": "",
"group": "common", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The name of the message to send. In consumer overrides
message name from URI (if any)", "constantName":
"org.apache.camel.component.avro.AvroConstants#AVRO_MESSAGE_NAME" }
@@ -52,6 +53,7 @@
"bridgeErrorHandler": { "index": 10, "kind": "parameter", "displayName":
"Bridge Error Handler", "group": "consumer (advanced)", "label":
"consumer,advanced", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Allows for bridging the consumer to the
Camel routing Error Handler, which mean any exceptions (if possible) occurred
while the Camel consumer is trying to pickup incoming [...]
"exceptionHandler": { "index": 11, "kind": "parameter", "displayName":
"Exception Handler", "group": "consumer (advanced)", "label":
"consumer,advanced", "required": false, "type": "object", "javaType":
"org.apache.camel.spi.ExceptionHandler", "optionalPrefix": "consumer.",
"deprecated": false, "autowired": false, "secret": false, "description": "To
let the consumer use a custom ExceptionHandler. Notice if the option
bridgeErrorHandler is enabled then this option is not in use. By de [...]
"exchangePattern": { "index": 12, "kind": "parameter", "displayName":
"Exchange Pattern", "group": "consumer (advanced)", "label":
"consumer,advanced", "required": false, "type": "enum", "javaType":
"org.apache.camel.ExchangePattern", "enum": [ "InOnly", "InOut" ],
"deprecated": false, "autowired": false, "secret": false, "description": "Sets
the exchange pattern when the consumer creates an exchange." },
- "lazyStartProducer": { "index": 13, "kind": "parameter", "displayName":
"Lazy Start Producer", "group": "producer (advanced)", "label":
"producer,advanced", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Whether the producer should be started
lazy (on the first message). By starting lazy you can use this to allow
CamelContext and routes to startup in situations where a produ [...]
+ "lazyStartProducer": { "index": 13, "kind": "parameter", "displayName":
"Lazy Start Producer", "group": "producer (advanced)", "label":
"producer,advanced", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Whether the producer should be started
lazy (on the first message). By starting lazy you can use this to allow
CamelContext and routes to startup in situations where a produ [...]
+ "serializablePackages": { "index": 14, "kind": "parameter", "displayName":
"Serializable Packages", "group": "security", "label": "security", "required":
false, "type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": false, "security": "insecure:serialization",
"configurationClass": "org.apache.camel.component.avro.AvroConfiguration",
"configurationField": "configuration", "description": "Comma-separated list of
additional packages that co [...]
}
}
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dataformats/avro.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dataformats/avro.json
index d62f9f95bf1e..63a7e01f75db 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dataformats/avro.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dataformats/avro.json
@@ -17,6 +17,7 @@
},
"properties": {
"id": { "index": 0, "kind": "attribute", "displayName": "Id", "group":
"common", "required": false, "type": "string", "javaType": "java.lang.String",
"deprecated": false, "autowired": false, "secret": false, "description": "The
id of this node" },
- "instanceClassName": { "index": 1, "kind": "attribute", "displayName":
"Instance Class Name", "group": "common", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "description": "Class name to use for marshal and
unmarshalling." }
+ "instanceClassName": { "index": 1, "kind": "attribute", "displayName":
"Instance Class Name", "group": "common", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "description": "Class name to use for marshal and
unmarshalling." },
+ "serializablePackages": { "index": 2, "kind": "attribute", "displayName":
"Serializable Packages", "group": "security", "label": "security", "required":
false, "type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": false, "description": "Comma-separated list of
additional packages that contain trusted Avro model classes. Avro 1.12
validates classes resolved from schemas; Camel automatically trusts packages
derived from the configured sche [...]
}
}
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/avro.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/avro.json
index ea01270769b7..3c008806e3ba 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/avro.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/avro.json
@@ -33,6 +33,7 @@
"autoDiscoverObjectMapper": { "index": 17, "kind": "attribute",
"displayName": "Auto Discover Object Mapper", "group": "advanced", "label":
"advanced", "required": false, "type": "boolean", "javaType":
"java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "If set to true then Jackson will lookup
for an objectMapper into the registry." },
"contentTypeHeader": { "index": 18, "kind": "attribute", "displayName":
"Content Type Header", "group": "common", "required": false, "type": "boolean",
"javaType": "java.lang.Boolean", "deprecated": false, "autowired": false,
"secret": false, "defaultValue": true, "description": "Whether the data format
should set the Content-Type header with the type from the data format. For
example application\/xml for data formats marshalling to XML, or
application\/json for data formats marshall [...]
"schemaResolver": { "index": 19, "kind": "attribute", "displayName":
"Schema Resolver", "group": "advanced", "label": "advanced", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": false, "description": "Optional schema resolver
used to lookup schemas for the data in transit." },
- "autoDiscoverSchemaResolver": { "index": 20, "kind": "attribute",
"displayName": "Auto Discover Schema Resolver", "group": "advanced", "label":
"advanced", "required": false, "type": "boolean", "javaType":
"java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": true, "description": "When not disabled, the SchemaResolver
will be looked up into the registry." }
+ "autoDiscoverSchemaResolver": { "index": 20, "kind": "attribute",
"displayName": "Auto Discover Schema Resolver", "group": "advanced", "label":
"advanced", "required": false, "type": "boolean", "javaType":
"java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": true, "description": "When not disabled, the SchemaResolver
will be looked up into the registry." },
+ "serializablePackages": { "index": 21, "kind": "attribute", "displayName":
"Serializable Packages", "group": "security", "label": "security", "required":
false, "type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": false, "description": "Comma-separated list of
additional packages that contain trusted Avro model classes. Avro 1.12
validates classes resolved from schemas; Camel automatically trusts packages
derived from the configured sch [...]
}
}
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 42efcd514b6e..a52e71aa9a3e 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
@@ -7543,6 +7543,16 @@ Optional schema resolver used to lookup schemas for the
data in transit.
<xs:documentation xml:lang="en">
<![CDATA[
When not disabled, the SchemaResolver will be looked up into the registry.
Default value: true
+]]>
+ </xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="serializablePackages" type="xs:string">
+ <xs:annotation>
+ <xs:documentation xml:lang="en">
+<![CDATA[
+Comma-separated list of additional packages that contain trusted Avro model
classes. Avro 1.12 validates classes
+resolved from schemas; Camel automatically trusts packages derived from the
configured schema or instance class.
]]>
</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 8d5e213e2377..2b1f4d80bc75 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
@@ -6665,6 +6665,16 @@ Optional schema resolver used to lookup schemas for the
data in transit.
<xs:documentation xml:lang="en">
<![CDATA[
When not disabled, the SchemaResolver will be looked up into the registry.
Default value: true
+]]>
+ </xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="serializablePackages" type="xs:string">
+ <xs:annotation>
+ <xs:documentation xml:lang="en">
+<![CDATA[
+Comma-separated list of additional packages that contain trusted Avro model
classes. Avro 1.12 validates classes
+resolved from schemas; Camel automatically trusts packages derived from the
configured schema or instance class.
]]>
</xs:documentation>
</xs:annotation>
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camelYamlDsl-model.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camelYamlDsl-model.json
index 801be7f8ff4f..6c451faa7007 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camelYamlDsl-model.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camelYamlDsl-model.json
@@ -10438,6 +10438,16 @@
"index" : 20,
"group" : "advanced",
"label" : "advanced"
+ }, {
+ "name" : "serializablePackages",
+ "type" : "string",
+ "description" : "Comma-separated list of additional packages that
contain trusted Avro model classes. Avro 1.12 validates classes resolved from
schemas; Camel automatically trusts packages derived from the configured schema
or instance class.",
+ "title" : "Serializable Packages",
+ "displayName" : "Serializable Packages",
+ "kind" : "attribute",
+ "index" : 21,
+ "group" : "security",
+ "label" : "security"
} ]
},
"barcode" : {
diff --git a/components/camel-avro-rpc/camel-avro-rpc-component/pom.xml
b/components/camel-avro-rpc/camel-avro-rpc-component/pom.xml
index 1d37f9ef3d89..191d166a287a 100644
--- a/components/camel-avro-rpc/camel-avro-rpc-component/pom.xml
+++ b/components/camel-avro-rpc/camel-avro-rpc-component/pom.xml
@@ -31,16 +31,15 @@
<name>Camel :: Avro RPC</name>
<description>Camel Avro RPC component</description>
- <properties>
- <!-- Avro 1.12.2 requires the IPC and test protocol model packages to
be explicitly trusted. -->
-
<camel.surefire.fork.additional-vmargs>-Dorg.apache.avro.SERIALIZABLE_PACKAGES=org.apache.avro,org.apache.camel.avro</camel.surefire.fork.additional-vmargs>
- </properties>
-
<dependencies>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-support</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-avro</artifactId>
+ </dependency>
<dependency>
<groupId>org.apache.avro</groupId>
<artifactId>avro</artifactId>
diff --git
a/components/camel-avro-rpc/camel-avro-rpc-component/src/generated/java/org/apache/camel/component/avro/AvroComponentConfigurer.java
b/components/camel-avro-rpc/camel-avro-rpc-component/src/generated/java/org/apache/camel/component/avro/AvroComponentConfigurer.java
index 3318990fcc0f..c296eb3122a3 100644
---
a/components/camel-avro-rpc/camel-avro-rpc-component/src/generated/java/org/apache/camel/component/avro/AvroComponentConfigurer.java
+++
b/components/camel-avro-rpc/camel-avro-rpc-component/src/generated/java/org/apache/camel/component/avro/AvroComponentConfigurer.java
@@ -44,6 +44,8 @@ public class AvroComponentConfigurer extends
PropertyConfigurerSupport implement
case "protocolLocation":
getOrCreateConfiguration(target).setProtocolLocation(property(camelContext,
java.lang.String.class, value)); return true;
case "reflectionprotocol":
case "reflectionProtocol":
getOrCreateConfiguration(target).setReflectionProtocol(property(camelContext,
boolean.class, value)); return true;
+ case "serializablepackages":
+ case "serializablePackages":
getOrCreateConfiguration(target).setSerializablePackages(property(camelContext,
java.lang.String.class, value)); return true;
case "singleparameter":
case "singleParameter":
getOrCreateConfiguration(target).setSingleParameter(property(camelContext,
boolean.class, value)); return true;
case "uriauthority":
@@ -69,6 +71,8 @@ public class AvroComponentConfigurer extends
PropertyConfigurerSupport implement
case "protocolLocation": return java.lang.String.class;
case "reflectionprotocol":
case "reflectionProtocol": return boolean.class;
+ case "serializablepackages":
+ case "serializablePackages": return java.lang.String.class;
case "singleparameter":
case "singleParameter": return boolean.class;
case "uriauthority":
@@ -95,6 +99,8 @@ public class AvroComponentConfigurer extends
PropertyConfigurerSupport implement
case "protocolLocation": return
getOrCreateConfiguration(target).getProtocolLocation();
case "reflectionprotocol":
case "reflectionProtocol": return
getOrCreateConfiguration(target).isReflectionProtocol();
+ case "serializablepackages":
+ case "serializablePackages": return
getOrCreateConfiguration(target).getSerializablePackages();
case "singleparameter":
case "singleParameter": return
getOrCreateConfiguration(target).isSingleParameter();
case "uriauthority":
diff --git
a/components/camel-avro-rpc/camel-avro-rpc-component/src/generated/java/org/apache/camel/component/avro/AvroEndpointConfigurer.java
b/components/camel-avro-rpc/camel-avro-rpc-component/src/generated/java/org/apache/camel/component/avro/AvroEndpointConfigurer.java
index 4756c1b96ddd..50745690ccbe 100644
---
a/components/camel-avro-rpc/camel-avro-rpc-component/src/generated/java/org/apache/camel/component/avro/AvroEndpointConfigurer.java
+++
b/components/camel-avro-rpc/camel-avro-rpc-component/src/generated/java/org/apache/camel/component/avro/AvroEndpointConfigurer.java
@@ -38,6 +38,8 @@ public class AvroEndpointConfigurer extends
PropertyConfigurerSupport implements
case "protocolLocation":
target.getConfiguration().setProtocolLocation(property(camelContext,
java.lang.String.class, value)); return true;
case "reflectionprotocol":
case "reflectionProtocol":
target.getConfiguration().setReflectionProtocol(property(camelContext,
boolean.class, value)); return true;
+ case "serializablepackages":
+ case "serializablePackages":
target.getConfiguration().setSerializablePackages(property(camelContext,
java.lang.String.class, value)); return true;
case "singleparameter":
case "singleParameter":
target.getConfiguration().setSingleParameter(property(camelContext,
boolean.class, value)); return true;
case "uriauthority":
@@ -64,6 +66,8 @@ public class AvroEndpointConfigurer extends
PropertyConfigurerSupport implements
case "protocolLocation": return java.lang.String.class;
case "reflectionprotocol":
case "reflectionProtocol": return boolean.class;
+ case "serializablepackages":
+ case "serializablePackages": return java.lang.String.class;
case "singleparameter":
case "singleParameter": return boolean.class;
case "uriauthority":
@@ -91,6 +95,8 @@ public class AvroEndpointConfigurer extends
PropertyConfigurerSupport implements
case "protocolLocation": return
target.getConfiguration().getProtocolLocation();
case "reflectionprotocol":
case "reflectionProtocol": return
target.getConfiguration().isReflectionProtocol();
+ case "serializablepackages":
+ case "serializablePackages": return
target.getConfiguration().getSerializablePackages();
case "singleparameter":
case "singleParameter": return
target.getConfiguration().isSingleParameter();
case "uriauthority":
diff --git
a/components/camel-avro-rpc/camel-avro-rpc-component/src/generated/java/org/apache/camel/component/avro/AvroEndpointUriFactory.java
b/components/camel-avro-rpc/camel-avro-rpc-component/src/generated/java/org/apache/camel/component/avro/AvroEndpointUriFactory.java
index e129d732b990..d035df8e3e26 100644
---
a/components/camel-avro-rpc/camel-avro-rpc-component/src/generated/java/org/apache/camel/component/avro/AvroEndpointUriFactory.java
+++
b/components/camel-avro-rpc/camel-avro-rpc-component/src/generated/java/org/apache/camel/component/avro/AvroEndpointUriFactory.java
@@ -24,7 +24,7 @@ public class AvroEndpointUriFactory extends
org.apache.camel.support.component.E
private static final Set<String> ENDPOINT_IDENTITY_PROPERTY_NAMES;
private static final Map<String, String> MULTI_VALUE_PREFIXES;
static {
- Set<String> props = new HashSet<>(14);
+ Set<String> props = new HashSet<>(15);
props.add("bridgeErrorHandler");
props.add("exceptionHandler");
props.add("exchangePattern");
@@ -36,6 +36,7 @@ public class AvroEndpointUriFactory extends
org.apache.camel.support.component.E
props.add("protocolClassName");
props.add("protocolLocation");
props.add("reflectionProtocol");
+ props.add("serializablePackages");
props.add("singleParameter");
props.add("transport");
props.add("uriAuthority");
diff --git
a/components/camel-avro-rpc/camel-avro-rpc-component/src/generated/resources/META-INF/org/apache/camel/component/avro/avro.json
b/components/camel-avro-rpc/camel-avro-rpc-component/src/generated/resources/META-INF/org/apache/camel/component/avro/avro.json
index 197c0b609fe9..e7dc315189b5 100644
---
a/components/camel-avro-rpc/camel-avro-rpc-component/src/generated/resources/META-INF/org/apache/camel/component/avro/avro.json
+++
b/components/camel-avro-rpc/camel-avro-rpc-component/src/generated/resources/META-INF/org/apache/camel/component/avro/avro.json
@@ -33,7 +33,8 @@
"bridgeErrorHandler": { "index": 6, "kind": "property", "displayName":
"Bridge Error Handler", "group": "consumer", "label": "consumer", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": false, "description":
"Allows for bridging the consumer to the Camel routing Error Handler, which
mean any exceptions (if possible) occurred while the Camel consumer is trying
to pickup incoming messages, or the like [...]
"lazyStartProducer": { "index": 7, "kind": "property", "displayName":
"Lazy Start Producer", "group": "producer", "label": "producer", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": false, "description":
"Whether the producer should be started lazy (on the first message). By
starting lazy you can use this to allow CamelContext and routes to startup in
situations where a producer may otherwise fail [...]
"autowiredEnabled": { "index": 8, "kind": "property", "displayName":
"Autowired Enabled", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": true, "description":
"Whether autowiring is enabled. This is used for automatic autowiring options
(the option must be marked as autowired) by looking up in the registry to find
if there is a single instance of matching t [...]
- "configuration": { "index": 9, "kind": "property", "displayName":
"Configuration", "group": "advanced", "label": "advanced", "required": false,
"type": "object", "javaType":
"org.apache.camel.component.avro.AvroConfiguration", "deprecated": false,
"autowired": false, "secret": false, "description": "To use a shared
AvroConfiguration to configure options once" }
+ "configuration": { "index": 9, "kind": "property", "displayName":
"Configuration", "group": "advanced", "label": "advanced", "required": false,
"type": "object", "javaType":
"org.apache.camel.component.avro.AvroConfiguration", "deprecated": false,
"autowired": false, "secret": false, "description": "To use a shared
AvroConfiguration to configure options once" },
+ "serializablePackages": { "index": 10, "kind": "property", "displayName":
"Serializable Packages", "group": "security", "label": "security", "required":
false, "type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": false, "security": "insecure:serialization",
"configurationClass": "org.apache.camel.component.avro.AvroConfiguration",
"configurationField": "configuration", "description": "Comma-separated list of
additional packages that con [...]
},
"headers": {
"CamelAvroMessageName": { "index": 0, "kind": "header", "displayName": "",
"group": "common", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The name of the message to send. In consumer overrides
message name from URI (if any)", "constantName":
"org.apache.camel.component.avro.AvroConstants#AVRO_MESSAGE_NAME" }
@@ -52,6 +53,7 @@
"bridgeErrorHandler": { "index": 10, "kind": "parameter", "displayName":
"Bridge Error Handler", "group": "consumer (advanced)", "label":
"consumer,advanced", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Allows for bridging the consumer to the
Camel routing Error Handler, which mean any exceptions (if possible) occurred
while the Camel consumer is trying to pickup incoming [...]
"exceptionHandler": { "index": 11, "kind": "parameter", "displayName":
"Exception Handler", "group": "consumer (advanced)", "label":
"consumer,advanced", "required": false, "type": "object", "javaType":
"org.apache.camel.spi.ExceptionHandler", "optionalPrefix": "consumer.",
"deprecated": false, "autowired": false, "secret": false, "description": "To
let the consumer use a custom ExceptionHandler. Notice if the option
bridgeErrorHandler is enabled then this option is not in use. By de [...]
"exchangePattern": { "index": 12, "kind": "parameter", "displayName":
"Exchange Pattern", "group": "consumer (advanced)", "label":
"consumer,advanced", "required": false, "type": "enum", "javaType":
"org.apache.camel.ExchangePattern", "enum": [ "InOnly", "InOut" ],
"deprecated": false, "autowired": false, "secret": false, "description": "Sets
the exchange pattern when the consumer creates an exchange." },
- "lazyStartProducer": { "index": 13, "kind": "parameter", "displayName":
"Lazy Start Producer", "group": "producer (advanced)", "label":
"producer,advanced", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Whether the producer should be started
lazy (on the first message). By starting lazy you can use this to allow
CamelContext and routes to startup in situations where a produ [...]
+ "lazyStartProducer": { "index": 13, "kind": "parameter", "displayName":
"Lazy Start Producer", "group": "producer (advanced)", "label":
"producer,advanced", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Whether the producer should be started
lazy (on the first message). By starting lazy you can use this to allow
CamelContext and routes to startup in situations where a produ [...]
+ "serializablePackages": { "index": 14, "kind": "parameter", "displayName":
"Serializable Packages", "group": "security", "label": "security", "required":
false, "type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": false, "security": "insecure:serialization",
"configurationClass": "org.apache.camel.component.avro.AvroConfiguration",
"configurationField": "configuration", "description": "Comma-separated list of
additional packages that co [...]
}
}
diff --git
a/components/camel-avro-rpc/camel-avro-rpc-component/src/main/java/org/apache/camel/component/avro/AvroComponent.java
b/components/camel-avro-rpc/camel-avro-rpc-component/src/main/java/org/apache/camel/component/avro/AvroComponent.java
index 12364f50202b..16fc7f7230be 100644
---
a/components/camel-avro-rpc/camel-avro-rpc-component/src/main/java/org/apache/camel/component/avro/AvroComponent.java
+++
b/components/camel-avro-rpc/camel-avro-rpc-component/src/main/java/org/apache/camel/component/avro/AvroComponent.java
@@ -23,6 +23,7 @@ import java.util.concurrent.ConcurrentMap;
import org.apache.camel.CamelContext;
import org.apache.camel.Endpoint;
+import org.apache.camel.avro.support.AvroClassSecuritySupport;
import org.apache.camel.spi.Metadata;
import org.apache.camel.spi.annotations.Component;
import org.apache.camel.support.DefaultComponent;
@@ -43,6 +44,12 @@ public class AvroComponent extends DefaultComponent {
super(context);
}
+ @Override
+ protected void doInit() throws Exception {
+ AvroClassSecuritySupport.ensureAvroIpcPackagesTrusted();
+ super.doInit();
+ }
+
/**
* A factory method allowing derived components to create a new endpoint
from the given URI, remaining path and
* optional parameters
diff --git
a/components/camel-avro-rpc/camel-avro-rpc-component/src/main/java/org/apache/camel/component/avro/AvroConfiguration.java
b/components/camel-avro-rpc/camel-avro-rpc-component/src/main/java/org/apache/camel/component/avro/AvroConfiguration.java
index db6a3b1af245..655b707357da 100644
---
a/components/camel-avro-rpc/camel-avro-rpc-component/src/main/java/org/apache/camel/component/avro/AvroConfiguration.java
+++
b/components/camel-avro-rpc/camel-avro-rpc-component/src/main/java/org/apache/camel/component/avro/AvroConfiguration.java
@@ -54,6 +54,8 @@ public class AvroConfiguration implements Cloneable {
private boolean reflectionProtocol;
@UriParam
private boolean singleParameter;
+ @UriParam(label = "security", security = "insecure:serialization")
+ private String serializablePackages;
public AvroConfiguration copy() {
try {
@@ -200,4 +202,17 @@ public class AvroConfiguration implements Cloneable {
public void setSingleParameter(boolean singleParameter) {
this.singleParameter = singleParameter;
}
+
+ public String getSerializablePackages() {
+ return serializablePackages;
+ }
+
+ /**
+ * Comma-separated list of additional packages that contain trusted Avro
model classes. Avro 1.12+ validates classes
+ * resolved from schemas; Camel automatically trusts {@code
org.apache.avro} for IPC and packages derived from the
+ * configured protocol. Use this option for any additional model packages
not inferred from the protocol.
+ */
+ public void setSerializablePackages(String serializablePackages) {
+ this.serializablePackages = serializablePackages;
+ }
}
diff --git
a/components/camel-avro-rpc/camel-avro-rpc-component/src/main/java/org/apache/camel/component/avro/AvroEndpoint.java
b/components/camel-avro-rpc/camel-avro-rpc-component/src/main/java/org/apache/camel/component/avro/AvroEndpoint.java
index 7d97073c105f..d5fde4529303 100644
---
a/components/camel-avro-rpc/camel-avro-rpc-component/src/main/java/org/apache/camel/component/avro/AvroEndpoint.java
+++
b/components/camel-avro-rpc/camel-avro-rpc-component/src/main/java/org/apache/camel/component/avro/AvroEndpoint.java
@@ -27,6 +27,7 @@ import org.apache.camel.Category;
import org.apache.camel.Component;
import org.apache.camel.Consumer;
import org.apache.camel.Processor;
+import org.apache.camel.avro.support.AvroClassSecuritySupport;
import org.apache.camel.spi.UriEndpoint;
import org.apache.camel.spi.UriParam;
import org.apache.camel.support.DefaultEndpoint;
@@ -94,6 +95,8 @@ public abstract class AvroEndpoint extends DefaultEndpoint
implements AsyncEndpo
throw new IllegalArgumentException("Avro configuration does not
contain protocol");
}
+ configureClassSecurity(config);
+
if (config.getMessageName() != null &&
!config.getProtocol().getMessages().containsKey(config.getMessageName())) {
throw new IllegalArgumentException("Message " +
config.getMessageName() + " is not defined in protocol");
}
@@ -114,4 +117,12 @@ public abstract class AvroEndpoint extends DefaultEndpoint
implements AsyncEndpo
}
}
}
+
+ private void configureClassSecurity(AvroConfiguration config) {
+
AvroClassSecuritySupport.trustPackages(config.getSerializablePackages());
+ AvroClassSecuritySupport.trustClassName(config.getProtocolClassName());
+ if (config.getProtocol() != null) {
+ AvroClassSecuritySupport.trustProtocol(config.getProtocol());
+ }
+ }
}
diff --git
a/components/camel-avro-rpc/camel-avro-rpc-component/src/test/java/org/apache/camel/component/avro/AvroClassSecurityWithoutVmArgsTest.java
b/components/camel-avro-rpc/camel-avro-rpc-component/src/test/java/org/apache/camel/component/avro/AvroClassSecurityWithoutVmArgsTest.java
new file mode 100644
index 000000000000..f79b3a01b87e
--- /dev/null
+++
b/components/camel-avro-rpc/camel-avro-rpc-component/src/test/java/org/apache/camel/component/avro/AvroClassSecurityWithoutVmArgsTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.avro;
+
+import org.apache.avro.ipc.HandshakeRequest;
+import org.apache.avro.util.ClassSecurityValidator;
+import org.apache.camel.avro.generated.Key;
+import org.apache.camel.avro.support.AvroClassSecuritySupport;
+import org.apache.camel.avro.test.TestPojo;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class AvroClassSecurityWithoutVmArgsTest extends CamelTestSupport {
+
+ @BeforeEach
+ void resetValidator() {
+ AvroClassSecuritySupport.resetForTesting();
+ }
+
+ @Test
+ void shouldTrustAvroIpcHandshakeClassesFromComponentInit() throws
Exception {
+ context.getEndpoint(
+
"avro:netty:localhost:9999?protocolClassName=org.apache.camel.avro.generated.KeyValueProtocol",
+ AvroEndpoint.class);
+
+ assertDoesNotThrow(() ->
ClassSecurityValidator.validate(HandshakeRequest.class));
+ }
+
+ @Test
+ void shouldTrustProtocolModelPackagesFromEndpointConfiguration() throws
Exception {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("direct:validate")
+
.to("avro:netty:localhost:9999?protocolClassName=org.apache.camel.avro.generated.KeyValueProtocol");
+ }
+ });
+ context.getEndpoint(
+
"avro:netty:localhost:9999?protocolClassName=org.apache.camel.avro.generated.KeyValueProtocol",
+ AvroEndpoint.class);
+
+ assertDoesNotThrow(() -> ClassSecurityValidator.validate(Key.class));
+ }
+
+ @Test
+ void shouldTrustAdditionalPackagesFromSerializablePackagesOption() throws
Exception {
+ context.getEndpoint(
+
"avro:netty:localhost:9999?protocolClassName=org.apache.camel.avro.generated.KeyValueProtocol"
+ +
"&serializablePackages=org.apache.camel.avro.generated",
+ AvroEndpoint.class);
+
+ assertDoesNotThrow(() -> ClassSecurityValidator.validate(Key.class));
+ }
+
+ @Test
+ void shouldTrustParentPackageFromSerializablePackagesOption() throws
Exception {
+ context.getEndpoint(
+
"avro:netty:localhost:9999?protocolClassName=org.apache.camel.avro.generated.KeyValueProtocol"
+ + "&serializablePackages=org.apache.camel.avro",
+ AvroEndpoint.class);
+
+ assertDoesNotThrow(() ->
ClassSecurityValidator.validate(TestPojo.class));
+ }
+
+ @Test
+ void shouldRejectClassesOutsideConfiguredProtocolPackages() throws
Exception {
+ context.getEndpoint(
+
"avro:netty:localhost:9999?protocolClassName=org.apache.camel.avro.generated.KeyValueProtocol",
+ AvroEndpoint.class);
+
+ assertThrows(SecurityException.class, () ->
ClassSecurityValidator.validate(TestPojo.class));
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ // route added in test
+ }
+ };
+ }
+}
diff --git a/components/camel-avro/pom.xml b/components/camel-avro/pom.xml
index d684367e013c..c695ae9cf876 100644
--- a/components/camel-avro/pom.xml
+++ b/components/camel-avro/pom.xml
@@ -31,11 +31,6 @@
<name>Camel :: Avro</name>
<description>Camel Avro data format</description>
- <properties>
- <!-- Avro 1.12.2 requires application model packages to be explicitly
trusted. -->
-
<camel.surefire.fork.additional-vmargs>-Dorg.apache.avro.SERIALIZABLE_PACKAGES=org.apache.camel.dataformat.avro.example</camel.surefire.fork.additional-vmargs>
- </properties>
-
<dependencies>
<dependency>
<groupId>org.apache.camel</groupId>
diff --git
a/components/camel-avro/src/generated/java/org/apache/camel/dataformat/avro/AvroDataFormatConfigurer.java
b/components/camel-avro/src/generated/java/org/apache/camel/dataformat/avro/AvroDataFormatConfigurer.java
index db7b965cf756..97647013685d 100644
---
a/components/camel-avro/src/generated/java/org/apache/camel/dataformat/avro/AvroDataFormatConfigurer.java
+++
b/components/camel-avro/src/generated/java/org/apache/camel/dataformat/avro/AvroDataFormatConfigurer.java
@@ -23,6 +23,7 @@ public class AvroDataFormatConfigurer extends
org.apache.camel.support.component
static {
Map<String, Object> map = new CaseInsensitiveMap();
map.put("InstanceClassName", java.lang.String.class);
+ map.put("SerializablePackages", java.lang.String.class);
ALL_OPTIONS = map;
}
@@ -32,6 +33,8 @@ public class AvroDataFormatConfigurer extends
org.apache.camel.support.component
switch (ignoreCase ? name.toLowerCase() : name) {
case "instanceclassname":
case "instanceClassName":
target.setInstanceClassName(property(camelContext, java.lang.String.class,
value)); return true;
+ case "serializablepackages":
+ case "serializablePackages":
target.setSerializablePackages(property(camelContext, java.lang.String.class,
value)); return true;
default: return false;
}
}
@@ -46,6 +49,8 @@ public class AvroDataFormatConfigurer extends
org.apache.camel.support.component
switch (ignoreCase ? name.toLowerCase() : name) {
case "instanceclassname":
case "instanceClassName": return java.lang.String.class;
+ case "serializablepackages":
+ case "serializablePackages": return java.lang.String.class;
default: return null;
}
}
@@ -56,6 +61,8 @@ public class AvroDataFormatConfigurer extends
org.apache.camel.support.component
switch (ignoreCase ? name.toLowerCase() : name) {
case "instanceclassname":
case "instanceClassName": return target.getInstanceClassName();
+ case "serializablepackages":
+ case "serializablePackages": return target.getSerializablePackages();
default: return null;
}
}
diff --git
a/components/camel-avro/src/generated/resources/META-INF/org/apache/camel/dataformat/avro/avro.json
b/components/camel-avro/src/generated/resources/META-INF/org/apache/camel/dataformat/avro/avro.json
index d62f9f95bf1e..63a7e01f75db 100644
---
a/components/camel-avro/src/generated/resources/META-INF/org/apache/camel/dataformat/avro/avro.json
+++
b/components/camel-avro/src/generated/resources/META-INF/org/apache/camel/dataformat/avro/avro.json
@@ -17,6 +17,7 @@
},
"properties": {
"id": { "index": 0, "kind": "attribute", "displayName": "Id", "group":
"common", "required": false, "type": "string", "javaType": "java.lang.String",
"deprecated": false, "autowired": false, "secret": false, "description": "The
id of this node" },
- "instanceClassName": { "index": 1, "kind": "attribute", "displayName":
"Instance Class Name", "group": "common", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "description": "Class name to use for marshal and
unmarshalling." }
+ "instanceClassName": { "index": 1, "kind": "attribute", "displayName":
"Instance Class Name", "group": "common", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "description": "Class name to use for marshal and
unmarshalling." },
+ "serializablePackages": { "index": 2, "kind": "attribute", "displayName":
"Serializable Packages", "group": "security", "label": "security", "required":
false, "type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": false, "description": "Comma-separated list of
additional packages that contain trusted Avro model classes. Avro 1.12
validates classes resolved from schemas; Camel automatically trusts packages
derived from the configured sche [...]
}
}
diff --git
a/components/camel-avro/src/main/java/org/apache/camel/avro/support/AvroClassSecuritySupport.java
b/components/camel-avro/src/main/java/org/apache/camel/avro/support/AvroClassSecuritySupport.java
new file mode 100644
index 000000000000..dbb5b16150e3
--- /dev/null
+++
b/components/camel-avro/src/main/java/org/apache/camel/avro/support/AvroClassSecuritySupport.java
@@ -0,0 +1,309 @@
+/*
+ * 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.avro.support;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.apache.avro.Protocol;
+import org.apache.avro.Schema;
+import org.apache.avro.util.ClassSecurityValidator;
+import org.apache.avro.util.ClassSecurityValidator.ClassSecurityPredicate;
+
+/**
+ * Configures Apache Avro {@link ClassSecurityValidator} with Camel trusted
packages.
+ * <p>
+ * Avro 1.12+ validates classes resolved from schemas. Camel automatically
trusts packages derived from configured
+ * protocol or schema classes. Additional packages can be configured through
the {@code serializablePackages} endpoint
+ * option.
+ * <p>
+ * Trusted packages are stored in a JVM-wide registry shared by all Camel
contexts in the process. Trust is cumulative
+ * and cannot be revoked in production.
+ */
+public final class AvroClassSecuritySupport {
+
+ private static final Set<String> TRUSTED_PACKAGES =
java.util.concurrent.ConcurrentHashMap.newKeySet();
+
+ private static final Set<String> TRUSTED_CLASSES =
java.util.concurrent.ConcurrentHashMap.newKeySet();
+
+ private static final Object LOCK = new Object();
+
+ private static final ClassSecurityPredicate CAMEL_TRUSTED =
AvroClassSecuritySupport::isCamelTrusted;
+
+ private static volatile ClassSecurityPredicate baseValidator =
ClassSecurityValidator.DEFAULT;
+
+ private static volatile ClassSecurityPredicate installedGlobal;
+
+ private static volatile List<String> normalizedPackagePrefixes = List.of();
+
+ private AvroClassSecuritySupport() {
+ }
+
+ /**
+ * Trusts Avro IPC classes required for camel-avro-rpc handshake.
+ */
+ public static void ensureAvroIpcPackagesTrusted() {
+ trustPackages("org.apache.avro.ipc");
+ }
+
+ /**
+ * Trusts the exact class name and its package for schema resolution.
+ */
+ public static void trustClassName(String className) {
+ if (className == null || className.isBlank()) {
+ return;
+ }
+ synchronized (LOCK) {
+ if (!applyClassNameTrust(className, true)) {
+ return;
+ }
+ commitTrustChanges();
+ }
+ }
+
+ /**
+ * Trusts only the exact class name without trusting its whole package.
+ */
+ public static void trustClassNameOnly(String className) {
+ if (className == null || className.isBlank()) {
+ return;
+ }
+ synchronized (LOCK) {
+ if (!applyClassNameTrust(className, false)) {
+ return;
+ }
+ commitTrustChanges();
+ }
+ }
+
+ /**
+ * Trusts the comma-separated list of packages.
+ */
+ public static void trustPackages(String packages) {
+ if (packages == null || packages.isBlank()) {
+ return;
+ }
+ trustPackages(parsePackages(packages).toArray(String[]::new));
+ }
+
+ /**
+ * Trusts the given packages.
+ */
+ public static void trustPackages(String... packages) {
+ if (packages == null || packages.length == 0) {
+ return;
+ }
+ synchronized (LOCK) {
+ boolean changed = false;
+ for (String pkg : packages) {
+ if (pkg != null && !pkg.isBlank()) {
+ String normalized = normalizePackage(pkg);
+ if (!isSystemPackage(normalized)) {
+ changed |= TRUSTED_PACKAGES.add(normalized);
+ }
+ }
+ }
+ if (!changed) {
+ return;
+ }
+ commitTrustChanges();
+ }
+ }
+
+ /**
+ * Trusts all named types reachable from the given schema graph.
+ */
+ public static void trustSchema(Schema schema) {
+ if (schema == null) {
+ return;
+ }
+ synchronized (LOCK) {
+ Set<Schema> visited = new HashSet<>();
+ boolean changed = collectSchemaTrust(schema, visited);
+ if (!changed) {
+ return;
+ }
+ commitTrustChanges();
+ }
+ }
+
+ /**
+ * Trusts all named types reachable from the given RPC protocol.
+ */
+ public static void trustProtocol(Protocol protocol) {
+ if (protocol == null) {
+ return;
+ }
+ synchronized (LOCK) {
+ boolean changed = false;
+ String namespace = protocol.getNamespace();
+ if (namespace != null && !namespace.isBlank() &&
!isSystemPackage(namespace)) {
+ changed |= TRUSTED_PACKAGES.add(normalizePackage(namespace));
+ }
+ Set<Schema> visited = new HashSet<>();
+ for (Schema type : protocol.getTypes()) {
+ if (!type.isError()) {
+ changed |= collectSchemaTrust(type, visited);
+ }
+ }
+ for (Protocol.Message message : protocol.getMessages().values()) {
+ changed |= collectSchemaTrust(message.getRequest(), visited);
+ changed |= collectSchemaTrust(message.getResponse(), visited);
+ changed |= collectSchemaTrust(message.getErrors(), visited);
+ }
+ if (!changed) {
+ return;
+ }
+ commitTrustChanges();
+ }
+ }
+
+ /**
+ * Clears Camel-managed trusted classes and packages. Intended for tests.
+ */
+ public static void resetForTesting() {
+ synchronized (LOCK) {
+ TRUSTED_PACKAGES.clear();
+ TRUSTED_CLASSES.clear();
+ normalizedPackagePrefixes = List.of();
+ baseValidator = ClassSecurityValidator.DEFAULT;
+ installedGlobal = null;
+ ClassSecurityValidator.setGlobal(ClassSecurityValidator.DEFAULT);
+ }
+ }
+
+ private static boolean applyClassNameTrust(String className, boolean
trustPackage) {
+ boolean changed = TRUSTED_CLASSES.add(className);
+ if (trustPackage) {
+ int lastDot = className.lastIndexOf('.');
+ if (lastDot > 0) {
+ String pkg = normalizePackage(className.substring(0, lastDot));
+ if (!isSystemPackage(pkg)) {
+ changed |= TRUSTED_PACKAGES.add(pkg);
+ }
+ }
+ }
+ return changed;
+ }
+
+ private static boolean collectSchemaTrust(Schema schema, Set<Schema>
visited) {
+ if (schema == null || !visited.add(schema)) {
+ return false;
+ }
+ boolean changed = false;
+ switch (schema.getType()) {
+ case RECORD, ENUM, FIXED -> {
+ String namespace = schema.getNamespace();
+ if (namespace != null && !namespace.isBlank() &&
!isSystemPackage(namespace)) {
+ changed |=
TRUSTED_PACKAGES.add(normalizePackage(namespace));
+ }
+ String fullName = schema.getFullName();
+ if (fullName != null && !fullName.isBlank()) {
+ changed |= TRUSTED_CLASSES.add(fullName);
+ }
+ if (schema.getType() == Schema.Type.RECORD) {
+ for (Schema.Field field : schema.getFields()) {
+ changed |= collectSchemaTrust(field.schema(), visited);
+ }
+ }
+ }
+ case ARRAY -> changed |=
collectSchemaTrust(schema.getElementType(), visited);
+ case MAP -> changed |= collectSchemaTrust(schema.getValueType(),
visited);
+ case UNION -> {
+ for (Schema branch : schema.getTypes()) {
+ changed |= collectSchemaTrust(branch, visited);
+ }
+ }
+ default -> {
+ // primitives and other non-named roots
+ }
+ }
+ return changed;
+ }
+
+ private static void commitTrustChanges() {
+ rebuildNormalizedPackagePrefixes();
+ refreshGlobal();
+ }
+
+ private static void refreshGlobal() {
+ ClassSecurityPredicate current = ClassSecurityValidator.getGlobal();
+ if (installedGlobal == null) {
+ if (current != null && current != ClassSecurityValidator.DEFAULT) {
+ baseValidator = current;
+ }
+ } else if (current != installedGlobal) {
+ baseValidator = current;
+ }
+ installedGlobal = ClassSecurityValidator.composite(baseValidator,
CAMEL_TRUSTED);
+ ClassSecurityValidator.setGlobal(installedGlobal);
+ }
+
+ private static boolean isCamelTrusted(Class<?> clazz) {
+ String className = clazz.getName();
+ if (TRUSTED_CLASSES.contains(className)) {
+ return true;
+ }
+ for (String prefix : normalizedPackagePrefixes) {
+ if (className.startsWith(prefix)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static void rebuildNormalizedPackagePrefixes() {
+ normalizedPackagePrefixes = TRUSTED_PACKAGES.stream()
+ .map(pkg -> normalizePackage(pkg) + ".")
+ .sorted()
+ .toList();
+ }
+
+ private static Set<String> parsePackages(String packages) {
+ return Arrays.stream(packages.split(","))
+ .map(String::trim)
+ .filter(s -> !s.isEmpty())
+ .map(AvroClassSecuritySupport::normalizePackage)
+ .collect(Collectors.toCollection(LinkedHashSet::new));
+ }
+
+ private static String normalizePackage(String pkg) {
+ String normalized = pkg.trim();
+ if ("*".equals(normalized)) {
+ throw new IllegalArgumentException(
+ "Wildcard '*' is not supported in serializablePackages
because it disables Avro class-loading protection");
+ }
+ if (normalized.endsWith(".")) {
+ normalized = normalized.substring(0, normalized.length() - 1);
+ }
+ return normalized;
+ }
+
+ static boolean isSystemPackage(String pkg) {
+ if (pkg == null || pkg.isBlank()) {
+ return true;
+ }
+ return pkg.startsWith("java.")
+ || pkg.startsWith("javax.")
+ || pkg.startsWith("jdk.")
+ || pkg.startsWith("sun.");
+ }
+}
diff --git
a/components/camel-avro/src/main/java/org/apache/camel/dataformat/avro/AvroDataFormat.java
b/components/camel-avro/src/main/java/org/apache/camel/dataformat/avro/AvroDataFormat.java
index ebded0dcd21f..cc1fcc360134 100644
---
a/components/camel-avro/src/main/java/org/apache/camel/dataformat/avro/AvroDataFormat.java
+++
b/components/camel-avro/src/main/java/org/apache/camel/dataformat/avro/AvroDataFormat.java
@@ -36,6 +36,7 @@ import org.apache.camel.CamelContext;
import org.apache.camel.CamelContextAware;
import org.apache.camel.CamelException;
import org.apache.camel.Exchange;
+import org.apache.camel.avro.support.AvroClassSecuritySupport;
import org.apache.camel.spi.DataFormat;
import org.apache.camel.spi.DataFormatName;
import org.apache.camel.spi.Metadata;
@@ -55,6 +56,8 @@ public class AvroDataFormat extends ServiceSupport implements
DataFormat, DataFo
private Object schema;
private transient Schema actualSchema;
private String instanceClassName;
+ @Metadata(label = "security", security = "insecure:serialization")
+ private String serializablePackages;
public AvroDataFormat() {
}
@@ -82,6 +85,9 @@ public class AvroDataFormat extends ServiceSupport implements
DataFormat, DataFo
protected void doInit() throws Exception {
super.doInit();
+ AvroClassSecuritySupport.trustPackages(serializablePackages);
+ AvroClassSecuritySupport.trustClassName(instanceClassName);
+
if (schema != null) {
if (schema instanceof Schema) {
actualSchema = (Schema) schema;
@@ -91,6 +97,10 @@ public class AvroDataFormat extends ServiceSupport
implements DataFormat, DataFo
} else if (instanceClassName != null) {
actualSchema = loadSchema(instanceClassName);
}
+
+ if (actualSchema != null) {
+ AvroClassSecuritySupport.trustSchema(actualSchema);
+ }
}
@Override
@@ -116,6 +126,18 @@ public class AvroDataFormat extends ServiceSupport
implements DataFormat, DataFo
instanceClassName = className;
}
+ public String getSerializablePackages() {
+ return serializablePackages;
+ }
+
+ /**
+ * Comma-separated list of additional packages that contain trusted Avro
model classes. Avro 1.12+ validates classes
+ * resolved from schemas; Camel automatically trusts packages derived from
the configured schema or instance class.
+ */
+ public void setSerializablePackages(String serializablePackages) {
+ this.serializablePackages = serializablePackages;
+ }
+
protected Schema loadSchema(String className) throws CamelException,
ClassNotFoundException {
// must use same class loading procedure to ensure working in OSGi
Class<?> instanceClass =
camelContext.getClassResolver().resolveMandatoryClass(className);
@@ -137,6 +159,9 @@ public class AvroDataFormat extends ServiceSupport
implements DataFormat, DataFo
public void marshal(Exchange exchange, Object graph, OutputStream
outputStream) throws Exception {
// the schema should be from the graph class name
Schema useSchema = actualSchema != null ? actualSchema :
loadSchema(graph.getClass().getName());
+ if (actualSchema == null) {
+
AvroClassSecuritySupport.trustClassNameOnly(graph.getClass().getName());
+ }
SpecificData specificData = getSpecificData(useSchema);
diff --git
a/components/camel-avro/src/test/java/org/apache/camel/avro/support/AvroClassSecuritySupportTest.java
b/components/camel-avro/src/test/java/org/apache/camel/avro/support/AvroClassSecuritySupportTest.java
new file mode 100644
index 000000000000..8924f7fb06f1
--- /dev/null
+++
b/components/camel-avro/src/test/java/org/apache/camel/avro/support/AvroClassSecuritySupportTest.java
@@ -0,0 +1,122 @@
+/*
+ * 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.avro.support;
+
+import java.io.IOException;
+import java.util.UUID;
+
+import org.apache.avro.Schema;
+import org.apache.avro.util.ClassSecurityValidator;
+import org.apache.camel.dataformat.avro.example.Value;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class AvroClassSecuritySupportTest {
+
+ @BeforeEach
+ void resetValidator() {
+ AvroClassSecuritySupport.resetForTesting();
+ }
+
+ @Test
+ void shouldRejectUntrustedApplicationClassesByDefault() {
+ SecurityException exception = assertThrows(SecurityException.class,
+ () -> ClassSecurityValidator.validate(Value.class));
+
assertTrue(exception.getMessage().contains("org.apache.camel.dataformat.avro.example.Value"));
+ }
+
+ @Test
+ void shouldTrustConfiguredPackages() {
+
AvroClassSecuritySupport.trustPackages("org.apache.camel.dataformat.avro.example");
+
+ assertDoesNotThrow(() -> ClassSecurityValidator.validate(Value.class));
+ }
+
+ @Test
+ void shouldTrustClassNamePackage() {
+ AvroClassSecuritySupport.trustClassName(Value.class.getName());
+
+ assertDoesNotThrow(() -> ClassSecurityValidator.validate(Value.class));
+ }
+
+ @Test
+ void shouldTrustParentPackageWhenChildPackageIsAlsoTrusted() {
+
AvroClassSecuritySupport.trustPackages("org.apache.camel.dataformat.avro.example");
+
AvroClassSecuritySupport.trustPackages("org.apache.camel.dataformat.avro.example.nested");
+
+ assertDoesNotThrow(() -> ClassSecurityValidator.validate(
+
org.apache.camel.dataformat.avro.example.nested.NestedFoo.class));
+ assertDoesNotThrow(() -> ClassSecurityValidator.validate(Value.class));
+ }
+
+ @Test
+ void shouldRejectWildcardPackages() {
+ IllegalArgumentException exception =
assertThrows(IllegalArgumentException.class,
+ () -> AvroClassSecuritySupport.trustPackages("*"));
+ assertTrue(exception.getMessage().contains("Wildcard"));
+ }
+
+ @Test
+ void shouldPreserveExistingGlobalValidator() {
+ ClassSecurityValidator.ClassSecurityPredicate custom = clazz -> clazz
== String.class;
+ ClassSecurityValidator.setGlobal(custom);
+
+
AvroClassSecuritySupport.trustPackages("org.apache.camel.dataformat.avro.example");
+
+ assertDoesNotThrow(() ->
ClassSecurityValidator.validate(String.class));
+ assertDoesNotThrow(() -> ClassSecurityValidator.validate(Value.class));
+ }
+
+ @Test
+ void shouldPreserveValidatorInstalledAfterFirstTrustCall() {
+ AvroClassSecuritySupport.trustPackages("a.b");
+ ClassSecurityValidator.setGlobal(
+
ClassSecurityValidator.composite(ClassSecurityValidator.getGlobal(), c -> c ==
UUID.class));
+
+ AvroClassSecuritySupport.trustPackages("c.d");
+
+ assertDoesNotThrow(() -> ClassSecurityValidator.validate(UUID.class));
+ }
+
+ @Test
+ void shouldTrustNonNamedRootSchemaGraph() {
+ Schema arraySchema = Schema.createArray(Value.SCHEMA$);
+
+ assertDoesNotThrow(() ->
AvroClassSecuritySupport.trustSchema(arraySchema));
+ assertDoesNotThrow(() -> ClassSecurityValidator.validate(Value.class));
+ }
+
+ @Test
+ void shouldNotTrustSystemPackagesFromClassName() {
+ AvroClassSecuritySupport.trustClassName(IOException.class.getName());
+
+ assertThrows(SecurityException.class, () ->
ClassSecurityValidator.validate(java.io.ObjectInputStream.class));
+ }
+
+ @Test
+ void shouldTrustExactClassWithoutPackageWhenUsingClassNameOnly() {
+ AvroClassSecuritySupport.trustClassNameOnly(Value.class.getName());
+
+ assertDoesNotThrow(() -> ClassSecurityValidator.validate(Value.class));
+ assertThrows(SecurityException.class,
+ () ->
ClassSecurityValidator.validate(org.apache.camel.dataformat.avro.example.DateRecord.class));
+ }
+}
diff --git
a/components/camel-avro/src/test/java/org/apache/camel/dataformat/avro/example/nested/NestedFoo.java
b/components/camel-avro/src/test/java/org/apache/camel/dataformat/avro/example/nested/NestedFoo.java
new file mode 100644
index 000000000000..96e41191627d
--- /dev/null
+++
b/components/camel-avro/src/test/java/org/apache/camel/dataformat/avro/example/nested/NestedFoo.java
@@ -0,0 +1,20 @@
+/*
+ * 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.dataformat.avro.example.nested;
+
+public class NestedFoo {
+}
diff --git
a/components/camel-jackson-avro/src/main/java/org/apache/camel/component/jackson/avro/JacksonAvroDataFormat.java
b/components/camel-jackson-avro/src/main/java/org/apache/camel/component/jackson/avro/JacksonAvroDataFormat.java
index 4270f1d935a8..e27a577c0901 100644
---
a/components/camel-jackson-avro/src/main/java/org/apache/camel/component/jackson/avro/JacksonAvroDataFormat.java
+++
b/components/camel-jackson-avro/src/main/java/org/apache/camel/component/jackson/avro/JacksonAvroDataFormat.java
@@ -27,7 +27,8 @@ import org.apache.camel.spi.annotations.Dataformat;
* Marshal POJOs to Avro and back using Jackson.
*/
@Dataformat("avroJackson")
-@Metadata(firstVersion = "3.10.0", title = "Avro Jackson", excludeProperties =
"library,instanceClassName,schema")
+@Metadata(firstVersion = "3.10.0", title = "Avro Jackson",
+ excludeProperties =
"library,instanceClassName,schema,serializablePackages")
public class JacksonAvroDataFormat extends AbstractJacksonDataFormat {
/**
diff --git
a/components/camel-jackson3-avro/src/main/java/org/apache/camel/component/jackson3/avro/JacksonAvroDataFormat.java
b/components/camel-jackson3-avro/src/main/java/org/apache/camel/component/jackson3/avro/JacksonAvroDataFormat.java
index 50fe2b2ac902..62ec22930dd2 100644
---
a/components/camel-jackson3-avro/src/main/java/org/apache/camel/component/jackson3/avro/JacksonAvroDataFormat.java
+++
b/components/camel-jackson3-avro/src/main/java/org/apache/camel/component/jackson3/avro/JacksonAvroDataFormat.java
@@ -26,7 +26,8 @@ import tools.jackson.dataformat.avro.AvroMapper;
* Marshal POJOs to Avro and back using Jackson.
*/
@Dataformat("avroJackson")
-@Metadata(firstVersion = "3.10.0", title = "Avro Jackson", excludeProperties =
"library,instanceClassName,schema")
+@Metadata(firstVersion = "3.10.0", title = "Avro Jackson",
+ excludeProperties =
"library,instanceClassName,schema,serializablePackages")
public class JacksonAvroDataFormat extends AbstractJacksonDataFormat {
/**
diff --git
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/dataformat/avro.json
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/dataformat/avro.json
index ea01270769b7..3c008806e3ba 100644
---
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/dataformat/avro.json
+++
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/dataformat/avro.json
@@ -33,6 +33,7 @@
"autoDiscoverObjectMapper": { "index": 17, "kind": "attribute",
"displayName": "Auto Discover Object Mapper", "group": "advanced", "label":
"advanced", "required": false, "type": "boolean", "javaType":
"java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "If set to true then Jackson will lookup
for an objectMapper into the registry." },
"contentTypeHeader": { "index": 18, "kind": "attribute", "displayName":
"Content Type Header", "group": "common", "required": false, "type": "boolean",
"javaType": "java.lang.Boolean", "deprecated": false, "autowired": false,
"secret": false, "defaultValue": true, "description": "Whether the data format
should set the Content-Type header with the type from the data format. For
example application\/xml for data formats marshalling to XML, or
application\/json for data formats marshall [...]
"schemaResolver": { "index": 19, "kind": "attribute", "displayName":
"Schema Resolver", "group": "advanced", "label": "advanced", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": false, "description": "Optional schema resolver
used to lookup schemas for the data in transit." },
- "autoDiscoverSchemaResolver": { "index": 20, "kind": "attribute",
"displayName": "Auto Discover Schema Resolver", "group": "advanced", "label":
"advanced", "required": false, "type": "boolean", "javaType":
"java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": true, "description": "When not disabled, the SchemaResolver
will be looked up into the registry." }
+ "autoDiscoverSchemaResolver": { "index": 20, "kind": "attribute",
"displayName": "Auto Discover Schema Resolver", "group": "advanced", "label":
"advanced", "required": false, "type": "boolean", "javaType":
"java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": true, "description": "When not disabled, the SchemaResolver
will be looked up into the registry." },
+ "serializablePackages": { "index": 21, "kind": "attribute", "displayName":
"Serializable Packages", "group": "security", "label": "security", "required":
false, "type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": false, "description": "Comma-separated list of
additional packages that contain trusted Avro model classes. Avro 1.12
validates classes resolved from schemas; Camel automatically trusts packages
derived from the configured sch [...]
}
}
diff --git
a/core/camel-core-model/src/main/java/org/apache/camel/model/dataformat/AvroDataFormat.java
b/core/camel-core-model/src/main/java/org/apache/camel/model/dataformat/AvroDataFormat.java
index 67e07e89db37..8fcf474a5ab7 100644
---
a/core/camel-core-model/src/main/java/org/apache/camel/model/dataformat/AvroDataFormat.java
+++
b/core/camel-core-model/src/main/java/org/apache/camel/model/dataformat/AvroDataFormat.java
@@ -120,6 +120,10 @@ public class AvroDataFormat extends DataFormatDefinition
implements ContentTypeH
@Metadata(label = "advanced", javaType = "java.lang.Boolean", defaultValue
= "true",
description = "When not disabled, the SchemaResolver will be
looked up into the registry.")
private String autoDiscoverSchemaResolver;
+ @XmlAttribute
+ @Metadata(label = "security", security = "insecure:serialization",
+ description = "Comma-separated list of additional packages that
contain trusted Avro model classes. Avro 1.12+ validates classes resolved from
schemas; Camel automatically trusts packages derived from the configured schema
or instance class.")
+ private String serializablePackages;
public AvroDataFormat() {
super("avro");
@@ -151,6 +155,7 @@ public class AvroDataFormat extends DataFormatDefinition
implements ContentTypeH
this.contentTypeHeader = source.contentTypeHeader;
this.schemaResolver = source.schemaResolver;
this.autoDiscoverSchemaResolver = source.autoDiscoverSchemaResolver;
+ this.serializablePackages = source.serializablePackages;
}
public AvroDataFormat(AvroLibrary library) {
@@ -189,6 +194,7 @@ public class AvroDataFormat extends DataFormatDefinition
implements ContentTypeH
this.contentTypeHeader = builder.contentTypeHeader;
this.schemaResolver = builder.schemaResolver;
this.autoDiscoverSchemaResolver = builder.autoDiscoverSchemaResolver;
+ this.serializablePackages = builder.serializablePackages;
}
@Override
@@ -394,6 +400,14 @@ public class AvroDataFormat extends DataFormatDefinition
implements ContentTypeH
this.autoDiscoverSchemaResolver = autoDiscoverSchemaResolver;
}
+ public String getSerializablePackages() {
+ return serializablePackages;
+ }
+
+ public void setSerializablePackages(String serializablePackages) {
+ this.serializablePackages = serializablePackages;
+ }
+
//
// Fluent builders
//
@@ -524,6 +538,7 @@ public class AvroDataFormat extends DataFormatDefinition
implements ContentTypeH
private String contentTypeHeader;
private String schemaResolver;
private String autoDiscoverSchemaResolver;
+ private String serializablePackages;
/**
* Class name to use for marshal and unmarshalling
@@ -790,6 +805,14 @@ public class AvroDataFormat extends DataFormatDefinition
implements ContentTypeH
return this;
}
+ /**
+ * Comma-separated list of additional packages that contain trusted
Avro model classes.
+ */
+ public Builder serializablePackages(String serializablePackages) {
+ this.serializablePackages = serializablePackages;
+ return this;
+ }
+
@Override
public AvroDataFormat end() {
return new AvroDataFormat(this);
diff --git
a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/dataformat/AvroDataFormatReifier.java
b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/dataformat/AvroDataFormatReifier.java
index 71513adcf2fd..afaa16d15536 100644
---
a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/dataformat/AvroDataFormatReifier.java
+++
b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/dataformat/AvroDataFormatReifier.java
@@ -42,6 +42,7 @@ public class AvroDataFormatReifier extends
DataFormatReifier<AvroDataFormat> {
properties.put("instanceClassName",
definition.getInstanceClassName());
}
properties.put("schema", definition.getSchema());
+ properties.put("serializablePackages",
definition.getSerializablePackages());
} else if (definition.getLibrary() == AvroLibrary.Jackson) {
properties.put("objectMapper",
asRef(definition.getObjectMapper()));
if (definition.getUseDefaultObjectMapper() == null) {
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 d433c8cb9a8a..ff8d6a542575 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
@@ -2863,9 +2863,9 @@ public class JavaDslModelWriter extends
JavaDslModelWriterSupport {
}
protected void doWriteAvroDataFormat(StringBuilder sb, AvroDataFormat def)
{
doWriteIdentifiedTypeAttributes(sb, def);
- doWriteAttribute(sb, "unmarshalType", def.getUnmarshalTypeName(),
null);
doWriteAttribute(sb, "collectionType", def.getCollectionTypeName(),
null);
doWriteAttribute(sb, "jsonView", def.getJsonViewTypeName(), null);
+ doWriteAttribute(sb, "unmarshalType", def.getUnmarshalTypeName(),
null);
doWriteAttribute(sb, "instanceClassName", def.getInstanceClassName(),
null);
doWriteAttribute(sb, "library", toString(def.getLibrary()),
"avroJackson");
doWriteAttribute(sb, "objectMapper", def.getObjectMapper(), null);
@@ -2883,6 +2883,7 @@ public class JavaDslModelWriter extends
JavaDslModelWriterSupport {
doWriteAttribute(sb, "contentTypeHeader", def.getContentTypeHeader(),
"true");
doWriteAttribute(sb, "schemaResolver", def.getSchemaResolver(), null);
doWriteAttribute(sb, "autoDiscoverSchemaResolver",
def.getAutoDiscoverSchemaResolver(), "true");
+ doWriteAttribute(sb, "serializablePackages",
def.getSerializablePackages(), null);
}
protected void doWriteBarcodeDataFormat(StringBuilder sb,
BarcodeDataFormat def) {
doWriteIdentifiedTypeAttributes(sb, def);
diff --git
a/core/camel-util/src/main/java/org/apache/camel/util/SecurityUtils.java
b/core/camel-util/src/main/java/org/apache/camel/util/SecurityUtils.java
index 035b5e1892ac..eff478cdd04c 100644
--- a/core/camel-util/src/main/java/org/apache/camel/util/SecurityUtils.java
+++ b/core/camel-util/src/main/java/org/apache/camel/util/SecurityUtils.java
@@ -76,6 +76,7 @@ public final class SecurityUtils {
map.put("objectcodecpattern", new
SecurityOption(INSECURE_SERIALIZATION, ""));
map.put("objectmessageenabled", new
SecurityOption(INSECURE_SERIALIZATION, "true"));
map.put("sendenabled", new SecurityOption(INSECURE_DEV, "true"));
+ map.put("serializablepackages", new
SecurityOption(INSECURE_SERIALIZATION, ""));
map.put("skiptlsverify", new SecurityOption(INSECURE_SSL, "true"));
map.put("sslendpointalgorithm", new SecurityOption(INSECURE_SSL,
"none"));
map.put("stricthostkeychecking", new SecurityOption(INSECURE_SSL, ""));
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 42b5466a9224..67d703d473f6 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
@@ -1590,6 +1590,7 @@ public class ModelParser extends BaseParser {
case "moduleRefs": def.setModuleRefs(val); yield true;
case "objectMapper": def.setObjectMapper(val); yield true;
case "schemaResolver": def.setSchemaResolver(val); yield true;
+ case "serializablePackages": def.setSerializablePackages(val);
yield true;
case "timezone": def.setTimezone(val); yield true;
case "unmarshalType": def.setUnmarshalTypeName(val); yield
true;
case "useDefaultObjectMapper":
def.setUseDefaultObjectMapper(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 febeba750a22..49eaecc6606f 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
@@ -2168,9 +2168,9 @@ public class ModelWriter extends BaseWriter {
protected void doWriteAvroDataFormat(String name, AvroDataFormat def)
throws IOException {
startElement(name);
doWriteIdentifiedTypeAttributes(def);
- doWriteAttribute("unmarshalType", def.getUnmarshalTypeName(), null);
doWriteAttribute("collectionType", def.getCollectionTypeName(), null);
doWriteAttribute("jsonView", def.getJsonViewTypeName(), null);
+ doWriteAttribute("unmarshalType", def.getUnmarshalTypeName(), null);
doWriteAttribute("instanceClassName", def.getInstanceClassName(),
null);
doWriteAttribute("library", toString(def.getLibrary()), "avroJackson");
doWriteAttribute("objectMapper", def.getObjectMapper(), null);
@@ -2188,6 +2188,7 @@ public class ModelWriter extends BaseWriter {
doWriteAttribute("contentTypeHeader", def.getContentTypeHeader(),
"true");
doWriteAttribute("schemaResolver", def.getSchemaResolver(), null);
doWriteAttribute("autoDiscoverSchemaResolver",
def.getAutoDiscoverSchemaResolver(), "true");
+ doWriteAttribute("serializablePackages",
def.getSerializablePackages(), null);
endElement(name);
}
protected void doWriteBarcodeDataFormat(String name, BarcodeDataFormat
def) throws IOException {
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 f88fa119364b..736ccd04ca76 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
@@ -2164,9 +2164,9 @@ public class YamlModelWriter extends
YamlModelWriterSupport {
protected JsonObject doWriteAvroDataFormat(AvroDataFormat def) {
JsonObject jo = new JsonObject();
doWriteIdentifiedTypeAttributes(jo, def);
- doWriteAttribute(jo, "unmarshalType", def.getUnmarshalTypeName(),
null);
doWriteAttribute(jo, "collectionType", def.getCollectionTypeName(),
null);
doWriteAttribute(jo, "jsonView", def.getJsonViewTypeName(), null);
+ doWriteAttribute(jo, "unmarshalType", def.getUnmarshalTypeName(),
null);
doWriteAttribute(jo, "instanceClassName", def.getInstanceClassName(),
null);
doWriteAttribute(jo, "library", toString(def.getLibrary()),
"avroJackson");
doWriteAttribute(jo, "objectMapper", def.getObjectMapper(), null);
@@ -2184,6 +2184,7 @@ public class YamlModelWriter extends
YamlModelWriterSupport {
doWriteAttribute(jo, "contentTypeHeader", def.getContentTypeHeader(),
"true");
doWriteAttribute(jo, "schemaResolver", def.getSchemaResolver(), null);
doWriteAttribute(jo, "autoDiscoverSchemaResolver",
def.getAutoDiscoverSchemaResolver(), "true");
+ doWriteAttribute(jo, "serializablePackages",
def.getSerializablePackages(), null);
return jo;
}
protected JsonObject doWriteBarcodeDataFormat(BarcodeDataFormat def) {
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
index e1cf2d5025e5..3783a2812de4 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
@@ -16,27 +16,47 @@ See the xref:camel-upgrade-recipes-tool.adoc[documentation]
page for details.
=== Apache Avro trusted packages
Camel now uses Apache Avro 1.12.2. Avro validates classes resolved from schemas
-and no longer trusts application packages by default. If your routes use Avro
-specific records, reflected POJOs, or schemas with Java class names, configure
+and no longer trusts application packages by default. The `camel-avro` data
format
+and `camel-avro-rpc` component automatically trust the Avro IPC packages and
packages
+derived from the configured schema or protocol. For any additional model
packages,
+use the `serializablePackages` option on the endpoint or data format, or
configure
the `org.apache.avro.SERIALIZABLE_PACKAGES` JVM system property with the
comma-separated packages that contain only your trusted Avro model classes.
For example, if generated records are in `com.example.orders.avro`, add the
-following JVM option:
+following JVM option when not using Camel's automatic package detection:
[source]
----
-Dorg.apache.avro.SERIALIZABLE_PACKAGES=com.example.orders.avro
----
-If you use `camel-avro-rpc`, also include `org.apache.avro`, which contains the
-Avro IPC handshake classes:
+Alternatively, configure the endpoint or data format:
[source]
----
--Dorg.apache.avro.SERIALIZABLE_PACKAGES=org.apache.avro,com.example.orders.avro
+avro:netty:localhost:8080?protocolClassName=com.example.orders.avro.OrderProtocol&serializablePackages=com.example.orders.avro
----
+[source]
+----
+from("kafka:orders")
+ .marshal().avro(AvroLibrary.ApacheAvro, Value.getClassSchema())
+ .to("kafka:orders-avro");
+----
+
+For additional packages not inferred from the schema, configure the data
format in Java:
+
+[source,java]
+----
+AvroDataFormat avro = new AvroDataFormat(Value.getClassSchema());
+avro.setSerializablePackages("com.example.orders.avro");
+----
+
+If you use `camel-avro-rpc`, Camel automatically trusts `org.apache.avro`,
which contains the
+Avro IPC handshake classes. You only need `serializablePackages` or the JVM
property for
+packages not inferred from the configured protocol.
+
Do not use `*`, as it disables Avro's class-loading protection.
=== camel-dynamic-router
diff --git
a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/AvroComponentBuilderFactory.java
b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/AvroComponentBuilderFactory.java
index 2e16449040e3..de071aa6bbba 100644
---
a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/AvroComponentBuilderFactory.java
+++
b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/AvroComponentBuilderFactory.java
@@ -238,6 +238,25 @@ public interface AvroComponentBuilderFactory {
doSetProperty("configuration", configuration);
return this;
}
+
+ /**
+ * Comma-separated list of additional packages that contain trusted
Avro
+ * model classes. Avro 1.12 validates classes resolved from schemas;
+ * Camel automatically trusts org.apache.avro for IPC and packages
+ * derived from the configured protocol. Use this option for any
+ * additional model packages not inferred from the protocol.
+ *
+ * The option is a: <code>java.lang.String</code> type.
+ *
+ * Group: security
+ *
+ * @param serializablePackages the value to set
+ * @return the dsl builder
+ */
+ default AvroComponentBuilder serializablePackages(java.lang.String
serializablePackages) {
+ doSetProperty("serializablePackages", serializablePackages);
+ return this;
+ }
}
class AvroComponentBuilderImpl
@@ -269,6 +288,7 @@ public interface AvroComponentBuilderFactory {
case "lazyStartProducer": ((AvroComponent)
component).setLazyStartProducer((boolean) value); return true;
case "autowiredEnabled": ((AvroComponent)
component).setAutowiredEnabled((boolean) value); return true;
case "configuration": ((AvroComponent)
component).setConfiguration((org.apache.camel.component.avro.AvroConfiguration)
value); return true;
+ case "serializablePackages":
getOrCreateConfiguration((AvroComponent)
component).setSerializablePackages((java.lang.String) value); return true;
default: return false;
}
}
diff --git
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/AvroEndpointBuilderFactory.java
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/AvroEndpointBuilderFactory.java
index 75ca4ffd6090..f9d7638e52b3 100644
---
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/AvroEndpointBuilderFactory.java
+++
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/AvroEndpointBuilderFactory.java
@@ -180,6 +180,24 @@ public interface AvroEndpointBuilderFactory {
doSetProperty("uriAuthority", uriAuthority);
return this;
}
+ /**
+ * Comma-separated list of additional packages that contain trusted
Avro
+ * model classes. Avro 1.12 validates classes resolved from schemas;
+ * Camel automatically trusts org.apache.avro for IPC and packages
+ * derived from the configured protocol. Use this option for any
+ * additional model packages not inferred from the protocol.
+ *
+ * The option is a: <code>java.lang.String</code> type.
+ *
+ * Group: security
+ *
+ * @param serializablePackages the value to set
+ * @return the dsl builder
+ */
+ default AvroEndpointConsumerBuilder serializablePackages(String
serializablePackages) {
+ doSetProperty("serializablePackages", serializablePackages);
+ return this;
+ }
}
/**
@@ -457,6 +475,24 @@ public interface AvroEndpointBuilderFactory {
doSetProperty("uriAuthority", uriAuthority);
return this;
}
+ /**
+ * Comma-separated list of additional packages that contain trusted
Avro
+ * model classes. Avro 1.12 validates classes resolved from schemas;
+ * Camel automatically trusts org.apache.avro for IPC and packages
+ * derived from the configured protocol. Use this option for any
+ * additional model packages not inferred from the protocol.
+ *
+ * The option is a: <code>java.lang.String</code> type.
+ *
+ * Group: security
+ *
+ * @param serializablePackages the value to set
+ * @return the dsl builder
+ */
+ default AvroEndpointProducerBuilder serializablePackages(String
serializablePackages) {
+ doSetProperty("serializablePackages", serializablePackages);
+ return this;
+ }
}
/**
@@ -663,6 +699,24 @@ public interface AvroEndpointBuilderFactory {
doSetProperty("uriAuthority", uriAuthority);
return this;
}
+ /**
+ * Comma-separated list of additional packages that contain trusted
Avro
+ * model classes. Avro 1.12 validates classes resolved from schemas;
+ * Camel automatically trusts org.apache.avro for IPC and packages
+ * derived from the configured protocol. Use this option for any
+ * additional model packages not inferred from the protocol.
+ *
+ * The option is a: <code>java.lang.String</code> type.
+ *
+ * Group: security
+ *
+ * @param serializablePackages the value to set
+ * @return the dsl builder
+ */
+ default AvroEndpointBuilder serializablePackages(String
serializablePackages) {
+ doSetProperty("serializablePackages", serializablePackages);
+ return this;
+ }
}
/**
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 474e081479ae..8d713156f97a 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
@@ -741,6 +741,7 @@ public final class ModelDeserializers extends
YamlDeserializerSupport {
@YamlProperty(name = "moduleRefs", type = "string",
description = "To use custom Jackson modules referred from the Camel registry.
Multiple modules can be separated by comma.", displayName = "Module Refs"),
@YamlProperty(name = "objectMapper", type = "string",
description = "Lookup and use the existing ObjectMapper with the given id when
using Jackson.", displayName = "Object Mapper"),
@YamlProperty(name = "schemaResolver", type = "string",
description = "Optional schema resolver used to lookup schemas for the data in
transit.", displayName = "Schema Resolver"),
+ @YamlProperty(name = "serializablePackages", type =
"string", description = "Comma-separated list of additional packages that
contain trusted Avro model classes. Avro 1.12 validates classes resolved from
schemas; Camel automatically trusts packages derived from the configured schema
or instance class.", displayName = "Serializable Packages"),
@YamlProperty(name = "timezone", type = "string",
description = "If set then Jackson will use the Timezone when
marshalling/unmarshalling.", displayName = "Timezone"),
@YamlProperty(name = "unmarshalType", type = "string",
description = "Class name of the java type to use when unmarshalling.",
displayName = "Unmarshal Type"),
@YamlProperty(name = "useDefaultObjectMapper", type =
"boolean", defaultValue = "true", description = "Whether to lookup and use
default Jackson ObjectMapper from the registry.", displayName = "Use Default
Object Mapper"),
@@ -851,6 +852,11 @@ public final class ModelDeserializers extends
YamlDeserializerSupport {
target.setSchemaResolver(val);
break;
}
+ case "serializablePackages": {
+ String val = asText(node);
+ target.setSerializablePackages(val);
+ break;
+ }
case "timezone": {
String val = asText(node);
target.setTimezone(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 f954cfa82d39..1b439cba21ff 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
@@ -6364,6 +6364,11 @@
"title" : "Schema Resolver",
"description" : "Optional schema resolver used to lookup schemas
for the data in transit."
},
+ "serializablePackages" : {
+ "type" : "string",
+ "title" : "Serializable Packages",
+ "description" : "Comma-separated list of additional packages that
contain trusted Avro model classes. Avro 1.12 validates classes resolved from
schemas; Camel automatically trusts packages derived from the configured schema
or instance class."
+ },
"timezone" : {
"type" : "string",
"title" : "Timezone",
diff --git
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-model.json
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-model.json
index 801be7f8ff4f..6c451faa7007 100644
---
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-model.json
+++
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-model.json
@@ -10438,6 +10438,16 @@
"index" : 20,
"group" : "advanced",
"label" : "advanced"
+ }, {
+ "name" : "serializablePackages",
+ "type" : "string",
+ "description" : "Comma-separated list of additional packages that
contain trusted Avro model classes. Avro 1.12 validates classes resolved from
schemas; Camel automatically trusts packages derived from the configured schema
or instance class.",
+ "title" : "Serializable Packages",
+ "displayName" : "Serializable Packages",
+ "kind" : "attribute",
+ "index" : 21,
+ "group" : "security",
+ "label" : "security"
} ]
},
"barcode" : {
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 e29ab12882ee..7fc68a0cbc83 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
@@ -9963,6 +9963,11 @@
"title" : "Schema Resolver",
"description" : "Optional schema resolver used to lookup schemas
for the data in transit."
},
+ "serializablePackages" : {
+ "type" : "string",
+ "title" : "Serializable Packages",
+ "description" : "Comma-separated list of additional packages
that contain trusted Avro model classes. Avro 1.12 validates classes resolved
from schemas; Camel automatically trusts packages derived from the configured
schema or instance class."
+ },
"timezone" : {
"type" : "string",
"title" : "Timezone",