This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch camel-4.22.x
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/camel-4.22.x by this push:
new 1d5dea04488a CAMEL-24464: Clarify exec control headers require
allowControlHeaders
1d5dea04488a is described below
commit 1d5dea04488ae61ab0b165aaec47c2bd67adcdde
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Sep 1 07:24:52 2026 +0200
CAMEL-24464: Clarify exec control headers require allowControlHeaders
Backport of #25846 to camel-4.22.x. Since 4.20 (CAMEL-23315),
CamelExecCommand* in-headers only override URI options when
allowControlHeaders=true, but the docs still read as if they always
worked. Documents the requirement, logs a once-per-endpoint WARN when
control headers are ignored, and marks allowControlHeaders as
security = "insecure:dev" so camel.main.profile=prod blocks it by
default.
The upgrade-guide note was placed in the existing "Upgrading from
4.22.0 to 4.22.1" section of camel-4x-upgrade-guide-4_22.adoc, since
camel-4x-upgrade-guide-4_23.adoc doesn't exist on this branch. A
follow-up will sync that entry into camel-4x-upgrade-guide-4_22.adoc
on main per project convention.
Co-authored-by: Claude <[email protected]>
Closes #25982
---
.../org/apache/camel/catalog/components/exec.json | 20 +--
.../apache/camel/catalog/docs/exec-component.adoc | 41 +++++++
.../org/apache/camel/component/exec/exec.json | 20 +--
.../camel-exec/src/main/docs/exec-component.adoc | 41 +++++++
.../apache/camel/component/exec/ExecBinding.java | 28 +++--
.../apache/camel/component/exec/ExecComponent.java | 10 +-
.../apache/camel/component/exec/ExecEndpoint.java | 10 +-
.../component/exec/impl/DefaultExecBinding.java | 36 ++++++
.../component/exec/DefaultExecBindingTest.java | 136 ++++++++++++++++++---
.../camel/component/exec/ExecProducerTest.java | 19 +++
.../java/org/apache/camel/util/SecurityUtils.java | 1 +
.../ROOT/pages/camel-4x-upgrade-guide-4_22.adoc | 12 ++
.../component/dsl/ExecComponentBuilderFactory.java | 12 +-
.../endpoint/dsl/ExecEndpointBuilderFactory.java | 50 +++++---
14 files changed, 364 insertions(+), 72 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/exec.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/exec.json
index ee2f679c33ac..79d5f450c64e 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/exec.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/exec.json
@@ -27,22 +27,22 @@
"lazyStartProducer": { "index": 0, "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 [...]
"timeout": { "index": 1, "kind": "property", "displayName": "Timeout",
"group": "producer", "label": "", "required": false, "type": "integer",
"javaType": "long", "deprecated": false, "autowired": false, "secret": false,
"description": "The timeout, in milliseconds, after which the executable should
be terminated. If execution has not completed within the timeout, the component
will send a termination request." },
"workingDir": { "index": 2, "kind": "property", "displayName": "Working
Dir", "group": "producer", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "description": "The directory in which the command should be
executed. If null, the working directory of the current process will be used."
},
- "allowControlHeaders": { "index": 3, "kind": "property", "displayName":
"Allow Control Headers", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": false, "description":
"Whether to allow to use Camel headers or not (default false). Enabling this
allows to specify dynamic command line arguments via message header. However
this can be seen as a potential securi [...]
+ "allowControlHeaders": { "index": 3, "kind": "property", "displayName":
"Allow Control Headers", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "security": "insecure:dev",
"defaultValue": false, "description": "Whether {code CamelExec} in-headers may
override URI options (default false since Camel 4.20). When false,
CamelExecCommandExecutable, CamelExecCommandArgs, CamelE [...]
"autowiredEnabled": { "index": 4, "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 [...]
"binding": { "index": 5, "kind": "property", "displayName": "Binding",
"group": "advanced", "label": "advanced", "required": false, "type": "object",
"javaType": "org.apache.camel.component.exec.ExecBinding", "deprecated": false,
"autowired": false, "secret": false, "description": "To use a custom
org.apache.commons.exec.ExecBinding for advanced use-cases." },
"commandExecutor": { "index": 6, "kind": "property", "displayName":
"Command Executor", "group": "advanced", "label": "advanced", "required":
false, "type": "object", "javaType":
"org.apache.camel.component.exec.ExecCommandExecutor", "deprecated": false,
"autowired": false, "secret": false, "description": "To use a custom
org.apache.commons.exec.ExecCommandExecutor that customizes the command
execution. The default command executor utilizes the commons-exec library,
which adds a shut [...]
},
"headers": {
- "CamelExecCommandExecutable": { "index": 0, "kind": "header",
"displayName": "", "group": "in", "label": "in", "required": false, "javaType":
"String", "deprecated": false, "deprecationNote": "", "autowired": false,
"secret": false, "description": "The name of the system command that will be
executed. Overrides executable in the URI.", "constantName":
"org.apache.camel.component.exec.ExecBinding#EXEC_COMMAND_EXECUTABLE" },
- "CamelExecCommandArgs": { "index": 1, "kind": "header", "displayName": "",
"group": "in", "label": "in", "required": false, "javaType":
"java.util.List<String> or String", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Command-line argument(s)
to pass to the executed process. The argument(s) is\/are used literally - no
quoting is applied. Overrides any existing args in the URI.", "constantName":
"org.apache.camel.component.exec.ExecBi [...]
- "CamelExecCommandOutFile": { "index": 2, "kind": "header", "displayName":
"", "group": "in", "label": "in", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The name of a file, created by the executable, that
should be considered as its output. Overrides any existing outFile in the
URI.", "constantName":
"org.apache.camel.component.exec.ExecBinding#EXEC_COMMAND_OUT_FILE" },
- "CamelExecCommandWorkingDir": { "index": 3, "kind": "header",
"displayName": "", "group": "in", "label": "in", "required": false, "javaType":
"String", "deprecated": false, "deprecationNote": "", "autowired": false,
"secret": false, "description": "The directory in which the command should be
executed. Overrides any existing workingDir in the URI.", "constantName":
"org.apache.camel.component.exec.ExecBinding#EXEC_COMMAND_WORKING_DIR" },
- "CamelExecCommandTimeout": { "index": 4, "kind": "header", "displayName":
"", "group": "in", "label": "in", "required": false, "javaType": "long",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The timeout, in milliseconds, after which the executable
should be terminated. Overrides any existing timeout in the URI.",
"constantName":
"org.apache.camel.component.exec.ExecBinding#EXEC_COMMAND_TIMEOUT" },
- "CamelExecExitValues": { "index": 5, "kind": "header", "displayName": "",
"group": "in", "label": "in", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The exit values for successful execution of the process.
Overrides any existing exitValues in the URI.", "constantName":
"org.apache.camel.component.exec.ExecBinding#EXEC_COMMAND_EXIT_VALUES" },
+ "CamelExecCommandExecutable": { "index": 0, "kind": "header",
"displayName": "", "group": "in", "label": "in", "required": false, "javaType":
"String", "deprecated": false, "deprecationNote": "", "autowired": false,
"secret": false, "description": "The name of the system command that will be
executed. Overrides executable in the URI. Requires allowControlHeaders=true on
the exec endpoint or component (default is false since Camel 4.20).",
"constantName": "org.apache.camel.component.e [...]
+ "CamelExecCommandArgs": { "index": 1, "kind": "header", "displayName": "",
"group": "in", "label": "in", "required": false, "javaType":
"java.util.List<String> or String", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Command-line argument(s)
to pass to the executed process. The argument(s) is\/are used literally - no
quoting is applied. Overrides any existing args in the URI. Requires
allowControlHeaders=true on the exec endpoint or [...]
+ "CamelExecCommandOutFile": { "index": 2, "kind": "header", "displayName":
"", "group": "in", "label": "in", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The name of a file, created by the executable, that
should be considered as its output. Overrides any existing outFile in the URI.
Requires allowControlHeaders=true on the exec endpoint or component (default is
false since Camel 4.20).", "con [...]
+ "CamelExecCommandWorkingDir": { "index": 3, "kind": "header",
"displayName": "", "group": "in", "label": "in", "required": false, "javaType":
"String", "deprecated": false, "deprecationNote": "", "autowired": false,
"secret": false, "description": "The directory in which the command should be
executed. Overrides any existing workingDir in the URI. Requires
allowControlHeaders=true on the exec endpoint or component (default is false
since Camel 4.20).", "constantName": "org.apache.cam [...]
+ "CamelExecCommandTimeout": { "index": 4, "kind": "header", "displayName":
"", "group": "in", "label": "in", "required": false, "javaType": "long",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The timeout, in milliseconds, after which the executable
should be terminated. Overrides any existing timeout in the URI. Requires
allowControlHeaders=true on the exec endpoint or component (default is false
since Camel 4.20).", "constantName": [...]
+ "CamelExecExitValues": { "index": 5, "kind": "header", "displayName": "",
"group": "in", "label": "in", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The exit values for successful execution of the process.
Overrides any existing exitValues in the URI. Requires allowControlHeaders=true
on the exec endpoint or component (default is false since Camel 4.20).",
"constantName": "org.apache.camel.co [...]
"CamelExecStderr": { "index": 6, "kind": "header", "displayName": "",
"group": "out", "label": "out", "required": false, "javaType":
"java.io.InputStream", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The value of this header points to the
standard error stream (stderr) of the executable. If no stderr is written, the
value is null.", "constantName":
"org.apache.camel.component.exec.ExecBinding#EXEC_STDERR" },
"CamelExecExitValue": { "index": 7, "kind": "header", "displayName": "",
"group": "out", "label": "out", "required": false, "javaType": "int",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "important": true, "description": "The value of this header is the _exit
value_ of the executable. Non-zero exit values typically indicate abnormal
termination. Note that the exit value is OS-dependent.", "constantName":
"org.apache.camel.component.exec.ExecBindin [...]
- "CamelExecUseStderrOnEmptyStdout": { "index": 8, "kind": "header",
"displayName": "", "group": "in", "label": "in", "required": false, "javaType":
"boolean", "deprecated": false, "deprecationNote": "", "autowired": false,
"secret": false, "description": "Indicates that when stdout is empty, this
component will populate the Camel Message Body with stderr. This behavior is
disabled (false) by default.", "constantName":
"org.apache.camel.component.exec.ExecBinding#EXEC_USE_STDERR_ON_EMP [...]
- "CamelExecCommandLogLevel": { "index": 9, "kind": "header", "displayName":
"", "group": "in", "label": "in", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "Logging level to be used for commands during execution.
The default value is DEBUG. Possible values are TRACE, DEBUG, INFO, WARN, ERROR
or OFF (Values of LoggingLevel enum)", "constantName":
"org.apache.camel.component.exec.ExecBinding#EXEC_ [...]
+ "CamelExecUseStderrOnEmptyStdout": { "index": 8, "kind": "header",
"displayName": "", "group": "in", "label": "in", "required": false, "javaType":
"boolean", "deprecated": false, "deprecationNote": "", "autowired": false,
"secret": false, "description": "Indicates that when stdout is empty, this
component will populate the Camel Message Body with stderr. This behavior is
disabled (false) by default. Requires allowControlHeaders=true on the exec
endpoint or component (default is false [...]
+ "CamelExecCommandLogLevel": { "index": 9, "kind": "header", "displayName":
"", "group": "in", "label": "in", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "Logging level to be used for commands during execution.
The default value is DEBUG. Possible values are TRACE, DEBUG, INFO, WARN, ERROR
or OFF (Values of LoggingLevel enum). Requires allowControlHeaders=true on the
exec endpoint or component [...]
},
"properties": {
"executable": { "index": 0, "kind": "path", "displayName": "Executable",
"group": "producer", "label": "", "required": true, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Sets the executable to be
executed. The executable must not be empty or null." },
@@ -54,7 +54,7 @@
"useStderrOnEmptyStdout": { "index": 6, "kind": "parameter",
"displayName": "Use Stderr On Empty Stdout", "group": "producer", "label": "",
"required": false, "type": "boolean", "javaType": "boolean", "deprecated":
false, "autowired": false, "secret": false, "defaultValue": false,
"description": "A boolean indicating that when stdout is empty, this component
will populate the Camel Message Body with stderr. This behavior is disabled
(false) by default." },
"workingDir": { "index": 7, "kind": "parameter", "displayName": "Working
Dir", "group": "producer", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "description": "The directory in which the command should be
executed. If null, the working directory of the current process will be used."
},
"lazyStartProducer": { "index": 8, "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 produc [...]
- "allowControlHeaders": { "index": 9, "kind": "parameter", "displayName":
"Allow Control Headers", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": false, "description":
"Whether to allow to use Camel headers or not (default false). Enabling this
allows to specify dynamic command line arguments via message header. However
this can be seen as a potential secur [...]
+ "allowControlHeaders": { "index": 9, "kind": "parameter", "displayName":
"Allow Control Headers", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "security": "insecure:dev",
"defaultValue": false, "description": "Whether {code CamelExec} in-headers may
override URI options (default false since Camel 4.20). When false,
CamelExecCommandExecutable, CamelExecCommandArgs, Camel [...]
"binding": { "index": 10, "kind": "parameter", "displayName": "Binding",
"group": "advanced", "label": "advanced", "required": false, "type": "object",
"javaType": "org.apache.camel.component.exec.ExecBinding", "deprecated": false,
"autowired": false, "secret": false, "description": "To use a custom
org.apache.commons.exec.ExecBinding for advanced use-cases." },
"commandExecutor": { "index": 11, "kind": "parameter", "displayName":
"Command Executor", "group": "advanced", "label": "advanced", "required":
false, "type": "object", "javaType":
"org.apache.camel.component.exec.ExecCommandExecutor", "deprecated": false,
"autowired": false, "secret": false, "description": "To use a custom
org.apache.commons.exec.ExecCommandExecutor that customizes the command
execution. The default command executor utilizes the commons-exec library,
which adds a sh [...]
}
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/exec-component.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/exec-component.adoc
index ad82f90cec84..64ca34aae1f8 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/exec-component.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/exec-component.adoc
@@ -48,6 +48,47 @@ include::partial$component-endpoint-headers.adoc[]
== Usage
+=== Control headers
+
+Since Camel 4.20, the exec in-headers that override URI options
(`CamelExecCommand*`,
+`CamelExecExitValues`, and `CamelExecUseStderrOnEmptyStdout`) are disabled by
default. This prevents untrusted message sources from redirecting
+command execution. To use dynamic executable, arguments, working directory, or
+other command settings from headers, enable the opt-in flag on the endpoint or
+component:
+
+[tabs]
+====
+Java::
++
+[source,java]
+----
+from("direct:run")
+ .setHeader("CamelExecCommandArgs", constant("ARGS-WORK"))
+ .to("exec:echo?allowControlHeaders=true");
+----
+
+YAML::
++
+[source,yaml]
+----
+- route:
+ from:
+ uri: direct:run
+ steps:
+ - setHeader:
+ name: CamelExecCommandArgs
+ constant: "ARGS-WORK"
+ - to:
+ uri: exec:echo
+ parameters:
+ allowControlHeaders: true
+----
+====
+
+When `allowControlHeaders` is `false` (the default), those in-headers on the
exchange
+are ignored (they are not removed), and a WARN is logged once per exec
endpoint.
+URI parameters such as `args` continue to work as before.
+
=== Message body
If the component receives an `in` message body that is
diff --git
a/components/camel-exec/src/generated/resources/META-INF/org/apache/camel/component/exec/exec.json
b/components/camel-exec/src/generated/resources/META-INF/org/apache/camel/component/exec/exec.json
index ee2f679c33ac..79d5f450c64e 100644
---
a/components/camel-exec/src/generated/resources/META-INF/org/apache/camel/component/exec/exec.json
+++
b/components/camel-exec/src/generated/resources/META-INF/org/apache/camel/component/exec/exec.json
@@ -27,22 +27,22 @@
"lazyStartProducer": { "index": 0, "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 [...]
"timeout": { "index": 1, "kind": "property", "displayName": "Timeout",
"group": "producer", "label": "", "required": false, "type": "integer",
"javaType": "long", "deprecated": false, "autowired": false, "secret": false,
"description": "The timeout, in milliseconds, after which the executable should
be terminated. If execution has not completed within the timeout, the component
will send a termination request." },
"workingDir": { "index": 2, "kind": "property", "displayName": "Working
Dir", "group": "producer", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "description": "The directory in which the command should be
executed. If null, the working directory of the current process will be used."
},
- "allowControlHeaders": { "index": 3, "kind": "property", "displayName":
"Allow Control Headers", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": false, "description":
"Whether to allow to use Camel headers or not (default false). Enabling this
allows to specify dynamic command line arguments via message header. However
this can be seen as a potential securi [...]
+ "allowControlHeaders": { "index": 3, "kind": "property", "displayName":
"Allow Control Headers", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "security": "insecure:dev",
"defaultValue": false, "description": "Whether {code CamelExec} in-headers may
override URI options (default false since Camel 4.20). When false,
CamelExecCommandExecutable, CamelExecCommandArgs, CamelE [...]
"autowiredEnabled": { "index": 4, "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 [...]
"binding": { "index": 5, "kind": "property", "displayName": "Binding",
"group": "advanced", "label": "advanced", "required": false, "type": "object",
"javaType": "org.apache.camel.component.exec.ExecBinding", "deprecated": false,
"autowired": false, "secret": false, "description": "To use a custom
org.apache.commons.exec.ExecBinding for advanced use-cases." },
"commandExecutor": { "index": 6, "kind": "property", "displayName":
"Command Executor", "group": "advanced", "label": "advanced", "required":
false, "type": "object", "javaType":
"org.apache.camel.component.exec.ExecCommandExecutor", "deprecated": false,
"autowired": false, "secret": false, "description": "To use a custom
org.apache.commons.exec.ExecCommandExecutor that customizes the command
execution. The default command executor utilizes the commons-exec library,
which adds a shut [...]
},
"headers": {
- "CamelExecCommandExecutable": { "index": 0, "kind": "header",
"displayName": "", "group": "in", "label": "in", "required": false, "javaType":
"String", "deprecated": false, "deprecationNote": "", "autowired": false,
"secret": false, "description": "The name of the system command that will be
executed. Overrides executable in the URI.", "constantName":
"org.apache.camel.component.exec.ExecBinding#EXEC_COMMAND_EXECUTABLE" },
- "CamelExecCommandArgs": { "index": 1, "kind": "header", "displayName": "",
"group": "in", "label": "in", "required": false, "javaType":
"java.util.List<String> or String", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Command-line argument(s)
to pass to the executed process. The argument(s) is\/are used literally - no
quoting is applied. Overrides any existing args in the URI.", "constantName":
"org.apache.camel.component.exec.ExecBi [...]
- "CamelExecCommandOutFile": { "index": 2, "kind": "header", "displayName":
"", "group": "in", "label": "in", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The name of a file, created by the executable, that
should be considered as its output. Overrides any existing outFile in the
URI.", "constantName":
"org.apache.camel.component.exec.ExecBinding#EXEC_COMMAND_OUT_FILE" },
- "CamelExecCommandWorkingDir": { "index": 3, "kind": "header",
"displayName": "", "group": "in", "label": "in", "required": false, "javaType":
"String", "deprecated": false, "deprecationNote": "", "autowired": false,
"secret": false, "description": "The directory in which the command should be
executed. Overrides any existing workingDir in the URI.", "constantName":
"org.apache.camel.component.exec.ExecBinding#EXEC_COMMAND_WORKING_DIR" },
- "CamelExecCommandTimeout": { "index": 4, "kind": "header", "displayName":
"", "group": "in", "label": "in", "required": false, "javaType": "long",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The timeout, in milliseconds, after which the executable
should be terminated. Overrides any existing timeout in the URI.",
"constantName":
"org.apache.camel.component.exec.ExecBinding#EXEC_COMMAND_TIMEOUT" },
- "CamelExecExitValues": { "index": 5, "kind": "header", "displayName": "",
"group": "in", "label": "in", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The exit values for successful execution of the process.
Overrides any existing exitValues in the URI.", "constantName":
"org.apache.camel.component.exec.ExecBinding#EXEC_COMMAND_EXIT_VALUES" },
+ "CamelExecCommandExecutable": { "index": 0, "kind": "header",
"displayName": "", "group": "in", "label": "in", "required": false, "javaType":
"String", "deprecated": false, "deprecationNote": "", "autowired": false,
"secret": false, "description": "The name of the system command that will be
executed. Overrides executable in the URI. Requires allowControlHeaders=true on
the exec endpoint or component (default is false since Camel 4.20).",
"constantName": "org.apache.camel.component.e [...]
+ "CamelExecCommandArgs": { "index": 1, "kind": "header", "displayName": "",
"group": "in", "label": "in", "required": false, "javaType":
"java.util.List<String> or String", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Command-line argument(s)
to pass to the executed process. The argument(s) is\/are used literally - no
quoting is applied. Overrides any existing args in the URI. Requires
allowControlHeaders=true on the exec endpoint or [...]
+ "CamelExecCommandOutFile": { "index": 2, "kind": "header", "displayName":
"", "group": "in", "label": "in", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The name of a file, created by the executable, that
should be considered as its output. Overrides any existing outFile in the URI.
Requires allowControlHeaders=true on the exec endpoint or component (default is
false since Camel 4.20).", "con [...]
+ "CamelExecCommandWorkingDir": { "index": 3, "kind": "header",
"displayName": "", "group": "in", "label": "in", "required": false, "javaType":
"String", "deprecated": false, "deprecationNote": "", "autowired": false,
"secret": false, "description": "The directory in which the command should be
executed. Overrides any existing workingDir in the URI. Requires
allowControlHeaders=true on the exec endpoint or component (default is false
since Camel 4.20).", "constantName": "org.apache.cam [...]
+ "CamelExecCommandTimeout": { "index": 4, "kind": "header", "displayName":
"", "group": "in", "label": "in", "required": false, "javaType": "long",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The timeout, in milliseconds, after which the executable
should be terminated. Overrides any existing timeout in the URI. Requires
allowControlHeaders=true on the exec endpoint or component (default is false
since Camel 4.20).", "constantName": [...]
+ "CamelExecExitValues": { "index": 5, "kind": "header", "displayName": "",
"group": "in", "label": "in", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The exit values for successful execution of the process.
Overrides any existing exitValues in the URI. Requires allowControlHeaders=true
on the exec endpoint or component (default is false since Camel 4.20).",
"constantName": "org.apache.camel.co [...]
"CamelExecStderr": { "index": 6, "kind": "header", "displayName": "",
"group": "out", "label": "out", "required": false, "javaType":
"java.io.InputStream", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The value of this header points to the
standard error stream (stderr) of the executable. If no stderr is written, the
value is null.", "constantName":
"org.apache.camel.component.exec.ExecBinding#EXEC_STDERR" },
"CamelExecExitValue": { "index": 7, "kind": "header", "displayName": "",
"group": "out", "label": "out", "required": false, "javaType": "int",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "important": true, "description": "The value of this header is the _exit
value_ of the executable. Non-zero exit values typically indicate abnormal
termination. Note that the exit value is OS-dependent.", "constantName":
"org.apache.camel.component.exec.ExecBindin [...]
- "CamelExecUseStderrOnEmptyStdout": { "index": 8, "kind": "header",
"displayName": "", "group": "in", "label": "in", "required": false, "javaType":
"boolean", "deprecated": false, "deprecationNote": "", "autowired": false,
"secret": false, "description": "Indicates that when stdout is empty, this
component will populate the Camel Message Body with stderr. This behavior is
disabled (false) by default.", "constantName":
"org.apache.camel.component.exec.ExecBinding#EXEC_USE_STDERR_ON_EMP [...]
- "CamelExecCommandLogLevel": { "index": 9, "kind": "header", "displayName":
"", "group": "in", "label": "in", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "Logging level to be used for commands during execution.
The default value is DEBUG. Possible values are TRACE, DEBUG, INFO, WARN, ERROR
or OFF (Values of LoggingLevel enum)", "constantName":
"org.apache.camel.component.exec.ExecBinding#EXEC_ [...]
+ "CamelExecUseStderrOnEmptyStdout": { "index": 8, "kind": "header",
"displayName": "", "group": "in", "label": "in", "required": false, "javaType":
"boolean", "deprecated": false, "deprecationNote": "", "autowired": false,
"secret": false, "description": "Indicates that when stdout is empty, this
component will populate the Camel Message Body with stderr. This behavior is
disabled (false) by default. Requires allowControlHeaders=true on the exec
endpoint or component (default is false [...]
+ "CamelExecCommandLogLevel": { "index": 9, "kind": "header", "displayName":
"", "group": "in", "label": "in", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "Logging level to be used for commands during execution.
The default value is DEBUG. Possible values are TRACE, DEBUG, INFO, WARN, ERROR
or OFF (Values of LoggingLevel enum). Requires allowControlHeaders=true on the
exec endpoint or component [...]
},
"properties": {
"executable": { "index": 0, "kind": "path", "displayName": "Executable",
"group": "producer", "label": "", "required": true, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Sets the executable to be
executed. The executable must not be empty or null." },
@@ -54,7 +54,7 @@
"useStderrOnEmptyStdout": { "index": 6, "kind": "parameter",
"displayName": "Use Stderr On Empty Stdout", "group": "producer", "label": "",
"required": false, "type": "boolean", "javaType": "boolean", "deprecated":
false, "autowired": false, "secret": false, "defaultValue": false,
"description": "A boolean indicating that when stdout is empty, this component
will populate the Camel Message Body with stderr. This behavior is disabled
(false) by default." },
"workingDir": { "index": 7, "kind": "parameter", "displayName": "Working
Dir", "group": "producer", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "description": "The directory in which the command should be
executed. If null, the working directory of the current process will be used."
},
"lazyStartProducer": { "index": 8, "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 produc [...]
- "allowControlHeaders": { "index": 9, "kind": "parameter", "displayName":
"Allow Control Headers", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": false, "description":
"Whether to allow to use Camel headers or not (default false). Enabling this
allows to specify dynamic command line arguments via message header. However
this can be seen as a potential secur [...]
+ "allowControlHeaders": { "index": 9, "kind": "parameter", "displayName":
"Allow Control Headers", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "security": "insecure:dev",
"defaultValue": false, "description": "Whether {code CamelExec} in-headers may
override URI options (default false since Camel 4.20). When false,
CamelExecCommandExecutable, CamelExecCommandArgs, Camel [...]
"binding": { "index": 10, "kind": "parameter", "displayName": "Binding",
"group": "advanced", "label": "advanced", "required": false, "type": "object",
"javaType": "org.apache.camel.component.exec.ExecBinding", "deprecated": false,
"autowired": false, "secret": false, "description": "To use a custom
org.apache.commons.exec.ExecBinding for advanced use-cases." },
"commandExecutor": { "index": 11, "kind": "parameter", "displayName":
"Command Executor", "group": "advanced", "label": "advanced", "required":
false, "type": "object", "javaType":
"org.apache.camel.component.exec.ExecCommandExecutor", "deprecated": false,
"autowired": false, "secret": false, "description": "To use a custom
org.apache.commons.exec.ExecCommandExecutor that customizes the command
execution. The default command executor utilizes the commons-exec library,
which adds a sh [...]
}
diff --git a/components/camel-exec/src/main/docs/exec-component.adoc
b/components/camel-exec/src/main/docs/exec-component.adoc
index ad82f90cec84..64ca34aae1f8 100644
--- a/components/camel-exec/src/main/docs/exec-component.adoc
+++ b/components/camel-exec/src/main/docs/exec-component.adoc
@@ -48,6 +48,47 @@ include::partial$component-endpoint-headers.adoc[]
== Usage
+=== Control headers
+
+Since Camel 4.20, the exec in-headers that override URI options
(`CamelExecCommand*`,
+`CamelExecExitValues`, and `CamelExecUseStderrOnEmptyStdout`) are disabled by
default. This prevents untrusted message sources from redirecting
+command execution. To use dynamic executable, arguments, working directory, or
+other command settings from headers, enable the opt-in flag on the endpoint or
+component:
+
+[tabs]
+====
+Java::
++
+[source,java]
+----
+from("direct:run")
+ .setHeader("CamelExecCommandArgs", constant("ARGS-WORK"))
+ .to("exec:echo?allowControlHeaders=true");
+----
+
+YAML::
++
+[source,yaml]
+----
+- route:
+ from:
+ uri: direct:run
+ steps:
+ - setHeader:
+ name: CamelExecCommandArgs
+ constant: "ARGS-WORK"
+ - to:
+ uri: exec:echo
+ parameters:
+ allowControlHeaders: true
+----
+====
+
+When `allowControlHeaders` is `false` (the default), those in-headers on the
exchange
+are ignored (they are not removed), and a WARN is logged once per exec
endpoint.
+URI parameters such as `args` continue to work as before.
+
=== Message body
If the component receives an `in` message body that is
diff --git
a/components/camel-exec/src/main/java/org/apache/camel/component/exec/ExecBinding.java
b/components/camel-exec/src/main/java/org/apache/camel/component/exec/ExecBinding.java
index 449a3bf4272a..8801cf4cbe83 100644
---
a/components/camel-exec/src/main/java/org/apache/camel/component/exec/ExecBinding.java
+++
b/components/camel-exec/src/main/java/org/apache/camel/component/exec/ExecBinding.java
@@ -36,7 +36,8 @@ public interface ExecBinding {
*/
@Metadata(label = "in", description = """
The name of the system command that will be executed. Overrides
- `executable` in the URI.""",
+ `executable` in the URI. Requires `allowControlHeaders=true` on
the exec
+ endpoint or component (default is `false` since Camel 4.20).""",
javaType = "String")
String EXEC_COMMAND_EXECUTABLE = "CamelExecCommandExecutable";
@@ -49,7 +50,8 @@ public interface ExecBinding {
@Metadata(label = "in", description = """
Command-line argument(s) to pass to the executed process. The
argument(s)
is/are used literally - no quoting is applied. Overrides any
existing
- `args` in the URI.""",
+ `args` in the URI. Requires `allowControlHeaders=true` on the exec
endpoint
+ or component (default is `false` since Camel 4.20).""",
javaType = "java.util.List<String> or String")
String EXEC_COMMAND_ARGS = "CamelExecCommandArgs";
@@ -61,7 +63,9 @@ public interface ExecBinding {
*/
@Metadata(label = "in", description = """
The name of a file, created by the executable, that should be
considered
- as its output. Overrides any existing `outFile` in the URI.""",
+ as its output. Overrides any existing `outFile` in the URI.
Requires
+ `allowControlHeaders=true` on the exec endpoint or component
(default is
+ `false` since Camel 4.20).""",
javaType = "String")
String EXEC_COMMAND_OUT_FILE = "CamelExecCommandOutFile";
@@ -72,7 +76,8 @@ public interface ExecBinding {
*/
@Metadata(label = "in", description = """
The directory in which the command should be executed. Overrides
any
- existing `workingDir` in the URI.""",
+ existing `workingDir` in the URI. Requires
`allowControlHeaders=true` on
+ the exec endpoint or component (default is `false` since Camel
4.20).""",
javaType = "String")
String EXEC_COMMAND_WORKING_DIR = "CamelExecCommandWorkingDir";
@@ -82,7 +87,9 @@ public interface ExecBinding {
*/
@Metadata(label = "in", description = """
The timeout, in milliseconds, after which the executable should be
- terminated. Overrides any existing `timeout` in the URI.""",
+ terminated. Overrides any existing `timeout` in the URI. Requires
+ `allowControlHeaders=true` on the exec endpoint or component
(default is
+ `false` since Camel 4.20).""",
javaType = "long")
String EXEC_COMMAND_TIMEOUT = "CamelExecCommandTimeout";
@@ -93,7 +100,9 @@ public interface ExecBinding {
*/
@Metadata(label = "in", description = """
The exit values for successful execution of the process.
- Overrides any existing `exitValues` in the URI.""",
+ Overrides any existing `exitValues` in the URI. Requires
+ `allowControlHeaders=true` on the exec endpoint or component
(default is
+ `false` since Camel 4.20).""",
javaType = "String")
String EXEC_COMMAND_EXIT_VALUES = "CamelExecExitValues";
@@ -124,7 +133,8 @@ public interface ExecBinding {
@Metadata(label = "in", description = """
Indicates that when `stdout` is empty, this component will
populate the
Camel Message Body with `stderr`. This behavior is disabled
(`false`) by
- default.""",
+ default. Requires `allowControlHeaders=true` on the exec endpoint
or
+ component (default is `false` since Camel 4.20).""",
javaType = "boolean")
String EXEC_USE_STDERR_ON_EMPTY_STDOUT = "CamelExecUseStderrOnEmptyStdout";
@@ -135,7 +145,9 @@ public interface ExecBinding {
@Metadata(label = "in",
description = """
Logging level to be used for commands during execution.
The default value is DEBUG.
- Possible values are TRACE, DEBUG, INFO, WARN, ERROR or
OFF (Values of LoggingLevel enum)""",
+ Possible values are TRACE, DEBUG, INFO, WARN, ERROR or
OFF (Values of LoggingLevel enum).
+ Requires `allowControlHeaders=true` on the exec endpoint
or component (default is `false`
+ since Camel 4.20).""",
javaType = "String")
String EXEC_COMMAND_LOG_LEVEL = "CamelExecCommandLogLevel";
diff --git
a/components/camel-exec/src/main/java/org/apache/camel/component/exec/ExecComponent.java
b/components/camel-exec/src/main/java/org/apache/camel/component/exec/ExecComponent.java
index dc1fb8058ec6..11bc8519c0e1 100644
---
a/components/camel-exec/src/main/java/org/apache/camel/component/exec/ExecComponent.java
+++
b/components/camel-exec/src/main/java/org/apache/camel/component/exec/ExecComponent.java
@@ -37,7 +37,7 @@ public class ExecComponent extends DefaultComponent {
@Metadata
private String workingDir;
- @Metadata(label = "advanced")
+ @Metadata(label = "advanced", security = "insecure:dev")
private boolean allowControlHeaders;
@Metadata(label = "advanced")
private ExecCommandExecutor commandExecutor;
@@ -102,9 +102,11 @@ public class ExecComponent extends DefaultComponent {
}
/**
- * Whether to allow to use Camel headers or not (default false). Enabling
this allows to specify dynamic command
- * line arguments via message header. However this can be seen as a
potential security vulnerability if the header
- * is coming from a malicious user, so use this with care.
+ * Whether {@code CamelExec*} in-headers may override URI options (default
{@code false} since Camel 4.20). When
+ * {@code false}, {@code CamelExecCommandExecutable}, {@code
CamelExecCommandArgs}, {@code CamelExecCommandOutFile},
+ * {@code CamelExecCommandWorkingDir}, {@code CamelExecCommandTimeout},
{@code CamelExecExitValues},
+ * {@code CamelExecUseStderrOnEmptyStdout}, and {@code
CamelExecCommandLogLevel} are ignored. Enable only when those
+ * headers come from a trusted route, not from an untrusted consumer.
*/
public void setAllowControlHeaders(boolean allowControlHeaders) {
this.allowControlHeaders = allowControlHeaders;
diff --git
a/components/camel-exec/src/main/java/org/apache/camel/component/exec/ExecEndpoint.java
b/components/camel-exec/src/main/java/org/apache/camel/component/exec/ExecEndpoint.java
index dbe3c6edc276..4339c2304e5b 100644
---
a/components/camel-exec/src/main/java/org/apache/camel/component/exec/ExecEndpoint.java
+++
b/components/camel-exec/src/main/java/org/apache/camel/component/exec/ExecEndpoint.java
@@ -61,7 +61,7 @@ public class ExecEndpoint extends DefaultEndpoint {
private boolean useStderrOnEmptyStdout;
@UriParam(defaultValue = "DEBUG")
private LoggingLevel commandLogLevel = LoggingLevel.DEBUG;
- @UriParam(label = "advanced")
+ @UriParam(label = "advanced", security = "insecure:dev")
private boolean allowControlHeaders;
public ExecEndpoint(String uri, ExecComponent component) {
@@ -211,9 +211,11 @@ public class ExecEndpoint extends DefaultEndpoint {
}
/**
- * Whether to allow to use Camel headers or not (default false). Enabling
this allows to specify dynamic command
- * line arguments via message header. However this can be seen as a
potential security vulnerability if the header
- * is coming from a malicious user, so use this with care.
+ * Whether {@code CamelExec*} in-headers may override URI options (default
{@code false} since Camel 4.20). When
+ * {@code false}, {@code CamelExecCommandExecutable}, {@code
CamelExecCommandArgs}, {@code CamelExecCommandOutFile},
+ * {@code CamelExecCommandWorkingDir}, {@code CamelExecCommandTimeout},
{@code CamelExecExitValues},
+ * {@code CamelExecUseStderrOnEmptyStdout}, and {@code
CamelExecCommandLogLevel} are ignored. Enable only when those
+ * headers come from a trusted route, not from an untrusted consumer.
*/
public void setAllowControlHeaders(boolean allowControlHeaders) {
this.allowControlHeaders = allowControlHeaders;
diff --git
a/components/camel-exec/src/main/java/org/apache/camel/component/exec/impl/DefaultExecBinding.java
b/components/camel-exec/src/main/java/org/apache/camel/component/exec/impl/DefaultExecBinding.java
index b8905e073536..df10f7889406 100644
---
a/components/camel-exec/src/main/java/org/apache/camel/component/exec/impl/DefaultExecBinding.java
+++
b/components/camel-exec/src/main/java/org/apache/camel/component/exec/impl/DefaultExecBinding.java
@@ -18,9 +18,11 @@ package org.apache.camel.component.exec.impl;
import java.io.File;
import java.io.InputStream;
+import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import org.apache.camel.Exchange;
import org.apache.camel.LoggingLevel;
@@ -45,12 +47,26 @@ public class DefaultExecBinding implements ExecBinding {
private static final Logger LOG =
LoggerFactory.getLogger(DefaultExecBinding.class);
+ private static final String[] CONTROL_HEADERS = {
+ EXEC_COMMAND_EXECUTABLE,
+ EXEC_COMMAND_ARGS,
+ EXEC_COMMAND_OUT_FILE,
+ EXEC_COMMAND_WORKING_DIR,
+ EXEC_COMMAND_TIMEOUT,
+ EXEC_COMMAND_EXIT_VALUES,
+ EXEC_USE_STDERR_ON_EMPTY_STDOUT,
+ EXEC_COMMAND_LOG_LEVEL
+ };
+
+ private final Set<String> ignoredControlHeadersWarnedEndpoints =
ConcurrentHashMap.newKeySet();
+
@Override
@SuppressWarnings("unchecked")
public ExecCommand readInput(Exchange exchange, ExecEndpoint endpoint) {
ObjectHelper.notNull(exchange, "exchange");
ObjectHelper.notNull(endpoint, "endpoint");
+ warnIgnoredControlHeaders(exchange, endpoint);
// do not convert args as we do that manually later
Object args = endpoint.isAllowControlHeaders() ?
exchange.getIn().removeHeader(EXEC_COMMAND_ARGS) : null;
String cmd = getAndRemoveHeader(endpoint, exchange.getIn(),
EXEC_COMMAND_EXECUTABLE, endpoint.getExecutable(),
@@ -153,4 +169,24 @@ public class DefaultExecBinding implements ExecBinding {
}
return h;
}
+
+ private void warnIgnoredControlHeaders(Exchange exchange, ExecEndpoint
endpoint) {
+ if (endpoint.isAllowControlHeaders()) {
+ return;
+ }
+ Message in = exchange.getIn();
+ List<String> ignored = new ArrayList<>();
+ for (String headerName : CONTROL_HEADERS) {
+ if (in.getHeader(headerName) != null) {
+ ignored.add(headerName);
+ }
+ }
+ if (!ignored.isEmpty() &&
ignoredControlHeadersWarnedEndpoints.add(endpoint.getEndpointUri())) {
+ LOG.warn(
+ "Control header(s) {} are set but ignored because
allowControlHeaders=false on {}. "
+ + "Set allowControlHeaders=true on the exec endpoint or
component to enable dynamic command configuration. "
+ + "This warning is logged only once per exec endpoint.",
+ ignored, endpoint);
+ }
+ }
}
diff --git
a/components/camel-exec/src/test/java/org/apache/camel/component/exec/DefaultExecBindingTest.java
b/components/camel-exec/src/test/java/org/apache/camel/component/exec/DefaultExecBindingTest.java
index 6d85a2d4d446..83ea91058968 100644
---
a/components/camel-exec/src/test/java/org/apache/camel/component/exec/DefaultExecBindingTest.java
+++
b/components/camel-exec/src/test/java/org/apache/camel/component/exec/DefaultExecBindingTest.java
@@ -19,42 +19,150 @@ package org.apache.camel.component.exec;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
import org.apache.camel.Exchange;
import org.apache.camel.component.exec.impl.DefaultExecBinding;
import org.apache.camel.test.junit6.CamelTestSupport;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.Logger;
+import org.apache.logging.log4j.core.appender.AbstractAppender;
+import org.apache.logging.log4j.core.config.Property;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
-public class DefaultExecBindingTest extends CamelTestSupport {
+class DefaultExecBindingTest extends CamelTestSupport {
@Test
- public void testReadInput() throws Exception {
- ExecCommand command = readInput("exec:test", Collections.EMPTY_LIST);
- assertEquals(Collections.EMPTY_LIST, command.getArgs(), "Get a wrong
args.");
+ void shouldReadArgsFromHeaderWhenControlHeadersEnabled() throws Exception {
List<String> args = Arrays.asList("arg1", "arg2");
- command = readInput("exec:test", args);
- assertEquals(args, command.getArgs(), "Get a wrong args.");
+ ExecCommand command = readInput("exec:test", args, true);
+ assertEquals(args, command.getArgs());
- command = readInput("exec:test", "arg1 arg2");
- assertEquals(args, command.getArgs(), "Get a wrong args.");
+ command = readInput("exec:test", "arg1 arg2", true);
+ assertEquals(args, command.getArgs());
- command = readInput("exec:test?args=arg1 arg2", null);
- assertEquals(args, command.getArgs(), "Get a wrong args.");
+ command = readInput("exec:test?args=arg1 arg2", null, true);
+ assertEquals(args, command.getArgs());
+
+ command = readInput("exec:test", Collections.emptyList(), true);
+ assertEquals(Collections.emptyList(), command.getArgs());
+ }
+
+ @Test
+ void shouldIgnoreControlHeadersByDefault() throws Exception {
+ DefaultExecBinding binding = new DefaultExecBinding();
+ ExecEndpoint execEndpoint = createExecEndpoint("exec:hostname", false);
+ Exchange exchange = execEndpoint.createExchange();
+ exchange.getIn().setHeader(ExecBinding.EXEC_COMMAND_EXECUTABLE,
"whoami");
+ exchange.getIn().setHeader(ExecBinding.EXEC_COMMAND_ARGS, "ARGS-WORK");
+
+ ExecCommand command = binding.readInput(exchange, execEndpoint);
+
+ assertEquals("hostname", command.getExecutable());
+ assertEquals(Collections.emptyList(), command.getArgs());
+ assertEquals("whoami",
exchange.getIn().getHeader(ExecBinding.EXEC_COMMAND_EXECUTABLE));
+ assertEquals("ARGS-WORK",
exchange.getIn().getHeader(ExecBinding.EXEC_COMMAND_ARGS));
+ }
+
+ @Test
+ void shouldApplyControlHeadersWhenEnabled() throws Exception {
+ DefaultExecBinding binding = new DefaultExecBinding();
+ ExecEndpoint execEndpoint = createExecEndpoint("exec:hostname", true);
+ Exchange exchange = execEndpoint.createExchange();
+ exchange.getIn().setHeader(ExecBinding.EXEC_COMMAND_EXECUTABLE,
"whoami");
+ exchange.getIn().setHeader(ExecBinding.EXEC_COMMAND_ARGS, "ARGS-WORK");
+
+ ExecCommand command = binding.readInput(exchange, execEndpoint);
+
+ assertEquals("whoami", command.getExecutable());
+ assertEquals(List.of("ARGS-WORK"), command.getArgs());
+
assertNull(exchange.getIn().getHeader(ExecBinding.EXEC_COMMAND_EXECUTABLE));
+ assertNull(exchange.getIn().getHeader(ExecBinding.EXEC_COMMAND_ARGS));
+ }
+
+ @Test
+ void shouldKeepUriArgsWhenControlHeadersDisabled() throws Exception {
+ ExecCommand command = readInput("exec:echo?args=URIARGS-WORK",
"ARGS-WORK", false);
+
+ assertEquals(List.of("URIARGS-WORK"), command.getArgs());
+ assertEquals("echo", command.getExecutable());
+ }
+
+ @Test
+ void shouldWarnOncePerEndpointWhenControlHeadersIgnored() throws Exception
{
+ List<String> warnings = new CopyOnWriteArrayList<>();
+ AbstractAppender appender = new AbstractAppender("CaptureWarn", null,
null, true, Property.EMPTY_ARRAY) {
+ @Override
+ public void append(LogEvent event) {
+ if (event.getLevel() == Level.WARN
+ &&
event.getMessage().getFormattedMessage().contains("Control header")) {
+ warnings.add(event.getMessage().getFormattedMessage());
+ }
+ }
+ };
+ appender.start();
+ Logger logger = (Logger)
LogManager.getLogger(DefaultExecBinding.class);
+ logger.addAppender(appender);
+
+ try {
+ DefaultExecBinding binding = new DefaultExecBinding();
+ ExecComponent component = context.getComponent("exec",
ExecComponent.class);
+ component.setAllowControlHeaders(false);
+ component.setBinding(binding);
+
+ ExecEndpoint endpoint1 = (ExecEndpoint)
component.createEndpoint("exec:hostname");
+ ExecEndpoint endpoint2 = (ExecEndpoint)
component.createEndpoint("exec:echo");
+
+ Exchange exchange1 = endpoint1.createExchange();
+ exchange1.getIn().setHeader(ExecBinding.EXEC_COMMAND_EXECUTABLE,
"whoami");
+ binding.readInput(exchange1, endpoint1);
+
+ Exchange exchange2 = endpoint2.createExchange();
+ exchange2.getIn().setHeader(ExecBinding.EXEC_COMMAND_EXECUTABLE,
"whoami");
+ binding.readInput(exchange2, endpoint2);
+
+ Exchange exchange3 = endpoint1.createExchange();
+ exchange3.getIn().setHeader(ExecBinding.EXEC_COMMAND_EXECUTABLE,
"whoami");
+ binding.readInput(exchange3, endpoint1);
+
+ assertEquals(2, warnings.size(), "Expected one warning per
distinct exec endpoint");
+ } finally {
+ logger.removeAppender(appender);
+ appender.stop();
+ }
+ }
+
+ @Test
+ void shouldApplyControlHeadersFromEndpointOption() throws Exception {
+ DefaultExecBinding binding = new DefaultExecBinding();
+ ExecComponent component = context.getComponent("exec",
ExecComponent.class);
+ component.setAllowControlHeaders(false);
+ ExecEndpoint execEndpoint = (ExecEndpoint)
component.createEndpoint("exec:hostname?allowControlHeaders=true");
+ Exchange exchange = execEndpoint.createExchange();
+ exchange.getIn().setHeader(ExecBinding.EXEC_COMMAND_EXECUTABLE,
"whoami");
+
+ ExecCommand command = binding.readInput(exchange, execEndpoint);
+
+ assertEquals("whoami", command.getExecutable());
+
assertNull(exchange.getIn().getHeader(ExecBinding.EXEC_COMMAND_EXECUTABLE));
}
- private ExecCommand readInput(String execEndpointUri, Object args) throws
Exception {
+ private ExecCommand readInput(String execEndpointUri, Object args, boolean
allowControlHeaders) throws Exception {
DefaultExecBinding binding = new DefaultExecBinding();
- ExecEndpoint execEndpoint = createExecEndpoint(execEndpointUri);
+ ExecEndpoint execEndpoint = createExecEndpoint(execEndpointUri,
allowControlHeaders);
Exchange exchange = execEndpoint.createExchange();
exchange.getIn().setHeader(ExecBinding.EXEC_COMMAND_ARGS, args);
return binding.readInput(exchange, execEndpoint);
}
- private ExecEndpoint createExecEndpoint(String uri) throws Exception {
+ private ExecEndpoint createExecEndpoint(String uri, boolean
allowControlHeaders) throws Exception {
ExecComponent component = context.getComponent("exec",
ExecComponent.class);
- component.setAllowControlHeaders(true);
+ component.setAllowControlHeaders(allowControlHeaders);
return (ExecEndpoint) component.createEndpoint(uri);
}
diff --git
a/components/camel-exec/src/test/java/org/apache/camel/component/exec/ExecProducerTest.java
b/components/camel-exec/src/test/java/org/apache/camel/component/exec/ExecProducerTest.java
index fb77c129d2d4..fdc886f06e64 100644
---
a/components/camel-exec/src/test/java/org/apache/camel/component/exec/ExecProducerTest.java
+++
b/components/camel-exec/src/test/java/org/apache/camel/component/exec/ExecProducerTest.java
@@ -67,6 +67,25 @@ public class ExecProducerTest {
assertEquals("mockedByCommandExecutorMock.exe",
execCommandExecutorMock.lastCommandResult.getCommand().getExecutable());
}
+ @Test
+ @DirtiesContext
+ public void testIgnoreControlHeadersByDefault() {
+ final String command = "java";
+
+ producerTemplate.send(new Processor() {
+
+ public void process(Exchange exchange) {
+ exchange.getIn().setBody("noinput");
+ exchange.getIn().setHeader(EXEC_COMMAND_EXECUTABLE, command);
+ exchange.getIn().setHeader(EXEC_COMMAND_ARGS,
Arrays.asList("-version"));
+ }
+ });
+
+ assertEquals("mockedByCommandExecutorMock.exe",
+
execCommandExecutorMock.lastCommandResult.getCommand().getExecutable());
+
assertTrue(execCommandExecutorMock.lastCommandResult.getCommand().getArgs().isEmpty());
+ }
+
@Test
@DirtiesContext
public void testOverrideExecutable() {
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 f9a474d827fc..22da8e66497e 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
@@ -57,6 +57,7 @@ public final class SecurityUtils {
Map<String, SecurityOption> map = new HashMap<>();
// Generated by camel build tools - do NOT edit this map!
// SECURITY-OPTIONS: START
+ map.put("allowcontrolheaders", new SecurityOption(INSECURE_DEV,
"true"));
map.put("allowfilepathsource", new SecurityOption(INSECURE_DEV,
"true"));
map.put("allowjavaserializedobject", new
SecurityOption(INSECURE_SERIALIZATION, "true"));
map.put("allowlocalwebhookurls", new SecurityOption(INSECURE_DEV,
"true"));
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
index e6bb96e52b7b..625ae75e2620 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
@@ -33,6 +33,18 @@ The same default is now also applied to the `ClientConfig`
that Camel builds for
endpoints, when neither a referenced `ClientConfig` nor `hazelcastConfigUri`
is supplied. Client mode
previously behaved differently from node mode for an otherwise identical
endpoint configuration.
+=== camel-exec
+
+`allowControlHeaders` is now annotated `security = "insecure:dev"`.
+With `camel.main.profile = prod` the default policy for that category is
`fail`,
+so an endpoint or component that sets `allowControlHeaders=true` will not start
+unless you relax `camel.security.insecureDevPolicy`.
+
+When the flag is `false` (the default), any remaining `CamelExecCommand*`,
+`CamelExecExitValues`, or `CamelExecUseStderrOnEmptyStdout` headers are ignored
+and a WARN is logged once per exec endpoint. Those headers never overrode the
+URI without the flag; they were just silent before.
+
== Upgrading Camel 4.21 to 4.22
=== camel-tika
diff --git
a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/ExecComponentBuilderFactory.java
b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/ExecComponentBuilderFactory.java
index 5768d84bf5c4..61c316b92cab 100644
---
a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/ExecComponentBuilderFactory.java
+++
b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/ExecComponentBuilderFactory.java
@@ -110,11 +110,13 @@ public interface ExecComponentBuilderFactory {
/**
- * Whether to allow to use Camel headers or not (default false).
- * Enabling this allows to specify dynamic command line arguments via
- * message header. However this can be seen as a potential security
- * vulnerability if the header is coming from a malicious user, so use
- * this with care.
+ * Whether {code CamelExec} in-headers may override URI options
(default
+ * false since Camel 4.20). When false, CamelExecCommandExecutable,
+ * CamelExecCommandArgs, CamelExecCommandOutFile,
+ * CamelExecCommandWorkingDir, CamelExecCommandTimeout,
+ * CamelExecExitValues, CamelExecUseStderrOnEmptyStdout, and
+ * CamelExecCommandLogLevel are ignored. Enable only when those headers
+ * come from a trusted route, not from an untrusted consumer.
*
* The option is a: <code>boolean</code> type.
*
diff --git
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/ExecEndpointBuilderFactory.java
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/ExecEndpointBuilderFactory.java
index b87fdca5846a..541e42bf451d 100644
---
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/ExecEndpointBuilderFactory.java
+++
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/ExecEndpointBuilderFactory.java
@@ -266,11 +266,13 @@ public interface ExecEndpointBuilderFactory {
return this;
}
/**
- * Whether to allow to use Camel headers or not (default false).
- * Enabling this allows to specify dynamic command line arguments via
- * message header. However this can be seen as a potential security
- * vulnerability if the header is coming from a malicious user, so use
- * this with care.
+ * Whether {code CamelExec} in-headers may override URI options
(default
+ * false since Camel 4.20). When false, CamelExecCommandExecutable,
+ * CamelExecCommandArgs, CamelExecCommandOutFile,
+ * CamelExecCommandWorkingDir, CamelExecCommandTimeout,
+ * CamelExecExitValues, CamelExecUseStderrOnEmptyStdout, and
+ * CamelExecCommandLogLevel are ignored. Enable only when those headers
+ * come from a trusted route, not from an untrusted consumer.
*
* The option is a: <code>boolean</code> type.
*
@@ -285,11 +287,13 @@ public interface ExecEndpointBuilderFactory {
return this;
}
/**
- * Whether to allow to use Camel headers or not (default false).
- * Enabling this allows to specify dynamic command line arguments via
- * message header. However this can be seen as a potential security
- * vulnerability if the header is coming from a malicious user, so use
- * this with care.
+ * Whether {code CamelExec} in-headers may override URI options
(default
+ * false since Camel 4.20). When false, CamelExecCommandExecutable,
+ * CamelExecCommandArgs, CamelExecCommandOutFile,
+ * CamelExecCommandWorkingDir, CamelExecCommandTimeout,
+ * CamelExecExitValues, CamelExecUseStderrOnEmptyStdout, and
+ * CamelExecCommandLogLevel are ignored. Enable only when those headers
+ * come from a trusted route, not from an untrusted consumer.
*
* The option will be converted to a <code>boolean</code> type.
*
@@ -445,7 +449,8 @@ public interface ExecEndpointBuilderFactory {
/**
* The name of the system command that will be executed. Overrides
- * executable in the URI.
+ * executable in the URI. Requires allowControlHeaders=true on the exec
+ * endpoint or component (default is false since Camel 4.20).
*
* The option is a: {@code String} type.
*
@@ -459,7 +464,8 @@ public interface ExecEndpointBuilderFactory {
/**
* Command-line argument(s) to pass to the executed process. The
* argument(s) is/are used literally - no quoting is applied. Overrides
- * any existing args in the URI.
+ * any existing args in the URI. Requires allowControlHeaders=true on
+ * the exec endpoint or component (default is false since Camel 4.20).
*
* The option is a: {@code java.util.List<String> or String} type.
*
@@ -473,6 +479,8 @@ public interface ExecEndpointBuilderFactory {
/**
* The name of a file, created by the executable, that should be
* considered as its output. Overrides any existing outFile in the URI.
+ * Requires allowControlHeaders=true on the exec endpoint or component
+ * (default is false since Camel 4.20).
*
* The option is a: {@code String} type.
*
@@ -485,7 +493,8 @@ public interface ExecEndpointBuilderFactory {
}
/**
* The directory in which the command should be executed. Overrides any
- * existing workingDir in the URI.
+ * existing workingDir in the URI. Requires allowControlHeaders=true on
+ * the exec endpoint or component (default is false since Camel 4.20).
*
* The option is a: {@code String} type.
*
@@ -498,7 +507,9 @@ public interface ExecEndpointBuilderFactory {
}
/**
* The timeout, in milliseconds, after which the executable should be
- * terminated. Overrides any existing timeout in the URI.
+ * terminated. Overrides any existing timeout in the URI. Requires
+ * allowControlHeaders=true on the exec endpoint or component (default
+ * is false since Camel 4.20).
*
* The option is a: {@code long} type.
*
@@ -511,7 +522,9 @@ public interface ExecEndpointBuilderFactory {
}
/**
* The exit values for successful execution of the process. Overrides
- * any existing exitValues in the URI.
+ * any existing exitValues in the URI. Requires
allowControlHeaders=true
+ * on the exec endpoint or component (default is false since Camel
+ * 4.20).
*
* The option is a: {@code String} type.
*
@@ -552,7 +565,8 @@ public interface ExecEndpointBuilderFactory {
/**
* Indicates that when stdout is empty, this component will populate
the
* Camel Message Body with stderr. This behavior is disabled (false) by
- * default.
+ * default. Requires allowControlHeaders=true on the exec endpoint or
+ * component (default is false since Camel 4.20).
*
* The option is a: {@code boolean} type.
*
@@ -566,7 +580,9 @@ public interface ExecEndpointBuilderFactory {
/**
* Logging level to be used for commands during execution. The default
* value is DEBUG. Possible values are TRACE, DEBUG, INFO, WARN, ERROR
- * or OFF (Values of LoggingLevel enum).
+ * or OFF (Values of LoggingLevel enum). Requires
+ * allowControlHeaders=true on the exec endpoint or component (default
+ * is false since Camel 4.20).
*
* The option is a: {@code String} type.
*