This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch CAMEL-26551-dapr-secret-headers in repository https://gitbox.apache.org/repos/asf/camel.git
commit e160e2c9665a333abfe621a64efbd6d747c31ada Author: Claus Ibsen <[email protected]> AuthorDate: Thu Nov 5 21:29:32 2026 +0100 CAMEL-26551: camel-dapr - honour the secret store and key headers for the secret operation only with allowSecretStoreAndKeyFromHeaders=true The secret operation took the secret store and the key from the CamelDaprSecretStore and CamelDaprKey headers first, and the endpoint options only when no header was set, so a message could read another secret or use another secret store than the endpoint is configured with. The secret operation now uses the secretStore and key options of the endpoint, and the two headers only when the new allowSecretStoreAndKeyFromHeaders option is enabled (disabled by default). An endpoint without a key fails with an error naming the option when the message has a CamelDaprKey header, instead of reading all the secrets of the store. The state operations still take the key from the CamelDaprKey header. The component page lists the headers that override the endpoint, and the upgrade guide has an entry. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Claus Ibsen <[email protected]> --- .../org/apache/camel/catalog/components/dapr.json | 10 ++-- .../apache/camel/catalog/docs/dapr-component.adoc | 10 ++++ .../component/dapr/DaprComponentConfigurer.java | 6 +++ .../component/dapr/DaprEndpointConfigurer.java | 6 +++ .../component/dapr/DaprEndpointUriFactory.java | 3 +- .../org/apache/camel/component/dapr/dapr.json | 10 ++-- .../camel-dapr/src/main/docs/dapr-component.adoc | 10 ++++ .../camel/component/dapr/DaprConfiguration.java | 18 +++++++ .../dapr/DaprConfigurationOptionsProxy.java | 17 +++++- .../apache/camel/component/dapr/DaprConstants.java | 8 ++- .../dapr/operations/DaprSecretHandler.java | 19 ++++++- .../dapr/DaprConfigurationOptionsProxyTest.java | 22 ++++++++ .../camel/component/dapr/DaprProducerTest.java | 60 ++++++++++++++++++++++ .../java/org/apache/camel/util/SecurityUtils.java | 3 ++ .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 16 ++++++ .../component/dsl/DaprComponentBuilderFactory.java | 22 ++++++++ .../endpoint/dsl/DaprEndpointBuilderFactory.java | 46 ++++++++++++++++- 17 files changed, 271 insertions(+), 15 deletions(-) diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/dapr.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/dapr.json index 5828a5b38408..8efa79dba04a 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/dapr.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/dapr.json @@ -63,7 +63,8 @@ "workflowStartTime": { "index": 36, "kind": "property", "displayName": "Workflow Start Time", "group": "producer", "label": "producer", "required": false, "type": "object", "javaType": "java.time.Instant", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.dapr.DaprConfiguration", "configurationField": "configuration", "description": "The start time of the new workflow" }, "workflowVersion": { "index": 37, "kind": "property", "displayName": "Workflow Version", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.dapr.DaprConfiguration", "configurationField": "configuration", "description": "The version of the workflow to start" }, "autowiredEnabled": { "index": 38, "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 [...] - "headerFilterStrategy": { "index": 39, "kind": "property", "displayName": "Header Filter Strategy", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.spi.HeaderFilterStrategy", "deprecated": false, "autowired": false, "secret": false, "description": "To use a custom HeaderFilterStrategy to filter header to and from Camel message." } + "headerFilterStrategy": { "index": 39, "kind": "property", "displayName": "Header Filter Strategy", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.spi.HeaderFilterStrategy", "deprecated": false, "autowired": false, "secret": false, "description": "To use a custom HeaderFilterStrategy to filter header to and from Camel message." }, + "allowSecretStoreAndKeyFromHeaders": { "index": 40, "kind": "property", "displayName": "Allow Secret Store And Key From Headers", "group": "security", "label": "producer,security", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "security": "insecure:dev", "insecureValue": "true", "defaultValue": false, "configurationClass": "org.apache.camel.component.dapr.DaprConfiguration", "configurationField": "configuration" [...] }, "headers": { "CamelDaprServiceToInvoke": { "index": 0, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Target service to invoke. Can be a Dapr App ID, a named HTTPEndpoint, or a FQDN\/public URL", "constantName": "org.apache.camel.component.dapr.DaprConstants#SERVICE_TO_INVOKE" }, @@ -74,9 +75,9 @@ "CamelDaprHttpExtension": { "index": 5, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "HttpExtension", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The HttpExtension object for service invocation. Takes precedence over verb", "constantName": "org.apache.camel.component.dapr.DaprConstants#HTTP_EXTENSION" }, "CamelDaprStateOperation": { "index": 6, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "StateOperation", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "get", "description": "The state operation to perform on the state store. Required for DaprOperation.state operation", "constantName": "org.apache.camel.component.dapr.DaprConstants#STATE_OPERATION" }, "CamelDaprStateStore": { "index": 7, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The name of the Dapr state store to interact with, defined in statestore.yaml config", "constantName": "org.apache.camel.component.dapr.DaprConstants#STATE_STORE" }, - "CamelDaprSecretStore": { "index": 8, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The name of the Dapr secret store to interact with, defined in local-secret-store.yaml config", "constantName": "org.apache.camel.component.dapr.DaprConstants#SECRET_STORE" }, + "CamelDaprSecretStore": { "index": 8, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The name of the Dapr secret store to interact with, defined in local-secret-store.yaml config. For the secret operation, this header is ignored unless the endpoint enables allowSecretStoreAndKeyFromHeaders=true (disabled by default). [...] "CamelDaprConfigStore": { "index": 9, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The name of the Dapr config store to interact with, defined in statestore.yaml config", "constantName": "org.apache.camel.component.dapr.DaprConstants#CONFIG_STORE" }, - "CamelDaprKey": { "index": 10, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The key used to identify the state\/secret object within the specified state\/secret store", "constantName": "org.apache.camel.component.dapr.DaprConstants#KEY" }, + "CamelDaprKey": { "index": 10, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The key used to identify the state\/secret object within the specified state\/secret store. For the secret operation, this header is ignored unless the endpoint enables allowSecretStoreAndKeyFromHeaders=true (disabled by default).", "consta [...] "CamelDaprETag": { "index": 11, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The eTag for optimistic concurrency during state save or delete operations", "constantName": "org.apache.camel.component.dapr.DaprConstants#E_TAG" }, "CamelDaprConcurrency": { "index": 12, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "io.dapr.client.domain.StateOptions.Concurrency", "enum": [ "FIRST_WRITE", "LAST_WRITE" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Concurrency mode to use with state operations", "constantName": "org.apache.camel.component.dapr.DaprConstants#CONCURRENCY" }, "CamelDaprConsistency": { "index": 13, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "io.dapr.client.domain.StateOptions.Consistency", "enum": [ "EVENTUAL", "STRONG" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Consistency level to use with state operations", "constantName": "org.apache.camel.component.dapr.DaprConstants#CONSISTENCY" }, @@ -171,6 +172,7 @@ "workflowStartTime": { "index": 37, "kind": "parameter", "displayName": "Workflow Start Time", "group": "producer", "label": "producer", "required": false, "type": "object", "javaType": "java.time.Instant", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.dapr.DaprConfiguration", "configurationField": "configuration", "description": "The start time of the new workflow" }, "workflowVersion": { "index": 38, "kind": "parameter", "displayName": "Workflow Version", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.dapr.DaprConfiguration", "configurationField": "configuration", "description": "The version of the workflow to start" }, "lazyStartProducer": { "index": 39, "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 [...] - "headerFilterStrategy": { "index": 40, "kind": "parameter", "displayName": "Header Filter Strategy", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.spi.HeaderFilterStrategy", "deprecated": false, "autowired": false, "secret": false, "description": "To use a custom HeaderFilterStrategy to filter header to and from Camel message." } + "headerFilterStrategy": { "index": 40, "kind": "parameter", "displayName": "Header Filter Strategy", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.spi.HeaderFilterStrategy", "deprecated": false, "autowired": false, "secret": false, "description": "To use a custom HeaderFilterStrategy to filter header to and from Camel message." }, + "allowSecretStoreAndKeyFromHeaders": { "index": 41, "kind": "parameter", "displayName": "Allow Secret Store And Key From Headers", "group": "security", "label": "producer,security", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "security": "insecure:dev", "insecureValue": "true", "defaultValue": false, "configurationClass": "org.apache.camel.component.dapr.DaprConfiguration", "configurationField": "configuration [...] } } diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/dapr-component.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/dapr-component.adoc index cce7ea2f74c4..b739cc7105c2 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/dapr-component.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/dapr-component.adoc @@ -64,6 +64,8 @@ Dapr’s bindings building block allows interaction with external systems via in === 4. *secret*: Dapr’s secret management building block enables secure retrieval of secrets from external secret stores. The secret operation fetches a secret from the specified secret store. If a key is provided, the operation retrieves the corresponding secret; if no key is specified, a bulk retrieval of all secrets from the store is performed. This operation requires the secretStore name, and optionally the key. +The secret operation reads the secret store and the key of the endpoint. The `CamelDaprSecretStore` and `CamelDaprKey` headers are ignored unless the endpoint sets `allowSecretStoreAndKeyFromHeaders=true`. An endpoint without a key fails, instead of reading all the secrets of the store, when the message has a `CamelDaprKey` header that it ignores. + === 5. *configuration*: Dapr’s configuration building block allows applications to retrieve dynamic configuration values from external configuration stores. The configuration operation fetches values associated with keys specified in configKeys. This operation requires the configStore name, and the configKeys. @@ -96,6 +98,14 @@ Dapr Workflows enable developers to write long-running, persistent, and reliable |`raiseEvent` |Sends an event to a running workflow instance that is waiting for it. Requires `workflowInstanceId` and `eventName`. The message body is used as the event payload. |=== +=== Headers that override the endpoint + +The producer headers, such as `CamelDaprServiceToInvoke`, `CamelDaprStateStore`, `CamelDaprKey`, `CamelDaprPubSubName`, `CamelDaprTopic`, `CamelDaprBindingName`, `CamelDaprStoreName` or `CamelDaprWorkflowInstanceId`, take the place of the endpoint option of the same name for one message. The `CamelDaprMetadata` header gives the metadata that the operation sends to the Dapr building block. + +For the `secret` operation, the `CamelDaprSecretStore` and `CamelDaprKey` headers are ignored unless the endpoint sets `allowSecretStoreAndKeyFromHeaders=true`. With that option, they take the place of the secret store and the key of the endpoint for one message, so the message chooses the secret that the operation reads. + +As the producer keeps the headers of the message, a `CamelDapr*` header also applies to the next `dapr` call of the route. When the messages come from an untrusted producer through a consumer that does not filter out `Camel*` headers, or when a `dapr` call must use only the options of its endpoint, remove these headers with `removeHeaders("CamelDapr*")` before the `dapr` endpoint. + === Dapr Consumer operations === 1. *pubSub*: diff --git a/components/camel-dapr/src/generated/java/org/apache/camel/component/dapr/DaprComponentConfigurer.java b/components/camel-dapr/src/generated/java/org/apache/camel/component/dapr/DaprComponentConfigurer.java index bb88f2a62e15..02f1e2d1e67a 100644 --- a/components/camel-dapr/src/generated/java/org/apache/camel/component/dapr/DaprComponentConfigurer.java +++ b/components/camel-dapr/src/generated/java/org/apache/camel/component/dapr/DaprComponentConfigurer.java @@ -31,6 +31,8 @@ public class DaprComponentConfigurer extends PropertyConfigurerSupport implement public boolean configure(CamelContext camelContext, Object obj, String name, Object value, boolean ignoreCase) { DaprComponent target = (DaprComponent) obj; switch (ignoreCase ? name.toLowerCase(Locale.ROOT) : name) { + case "allowsecretstoreandkeyfromheaders": + case "allowSecretStoreAndKeyFromHeaders": getOrCreateConfiguration(target).setAllowSecretStoreAndKeyFromHeaders(property(camelContext, boolean.class, value)); return true; case "autowiredenabled": case "autowiredEnabled": target.setAutowiredEnabled(property(camelContext, boolean.class, value)); return true; case "bindingname": @@ -114,6 +116,8 @@ public class DaprComponentConfigurer extends PropertyConfigurerSupport implement @Override public Class<?> getOptionType(String name, boolean ignoreCase) { switch (ignoreCase ? name.toLowerCase(Locale.ROOT) : name) { + case "allowsecretstoreandkeyfromheaders": + case "allowSecretStoreAndKeyFromHeaders": return boolean.class; case "autowiredenabled": case "autowiredEnabled": return boolean.class; case "bindingname": @@ -193,6 +197,8 @@ public class DaprComponentConfigurer extends PropertyConfigurerSupport implement public Object getOptionValue(Object obj, String name, boolean ignoreCase) { DaprComponent target = (DaprComponent) obj; switch (ignoreCase ? name.toLowerCase(Locale.ROOT) : name) { + case "allowsecretstoreandkeyfromheaders": + case "allowSecretStoreAndKeyFromHeaders": return getOrCreateConfiguration(target).isAllowSecretStoreAndKeyFromHeaders(); case "autowiredenabled": case "autowiredEnabled": return target.isAutowiredEnabled(); case "bindingname": diff --git a/components/camel-dapr/src/generated/java/org/apache/camel/component/dapr/DaprEndpointConfigurer.java b/components/camel-dapr/src/generated/java/org/apache/camel/component/dapr/DaprEndpointConfigurer.java index 303a9be2d44c..4b1ce7fc4f2a 100644 --- a/components/camel-dapr/src/generated/java/org/apache/camel/component/dapr/DaprEndpointConfigurer.java +++ b/components/camel-dapr/src/generated/java/org/apache/camel/component/dapr/DaprEndpointConfigurer.java @@ -24,6 +24,8 @@ public class DaprEndpointConfigurer extends PropertyConfigurerSupport implements public boolean configure(CamelContext camelContext, Object obj, String name, Object value, boolean ignoreCase) { DaprEndpoint target = (DaprEndpoint) obj; switch (ignoreCase ? name.toLowerCase(Locale.ROOT) : name) { + case "allowsecretstoreandkeyfromheaders": + case "allowSecretStoreAndKeyFromHeaders": target.getConfiguration().setAllowSecretStoreAndKeyFromHeaders(property(camelContext, boolean.class, value)); return true; case "bindingname": case "bindingName": target.getConfiguration().setBindingName(property(camelContext, java.lang.String.class, value)); return true; case "bindingoperation": @@ -108,6 +110,8 @@ public class DaprEndpointConfigurer extends PropertyConfigurerSupport implements @Override public Class<?> getOptionType(String name, boolean ignoreCase) { switch (ignoreCase ? name.toLowerCase(Locale.ROOT) : name) { + case "allowsecretstoreandkeyfromheaders": + case "allowSecretStoreAndKeyFromHeaders": return boolean.class; case "bindingname": case "bindingName": return java.lang.String.class; case "bindingoperation": @@ -188,6 +192,8 @@ public class DaprEndpointConfigurer extends PropertyConfigurerSupport implements public Object getOptionValue(Object obj, String name, boolean ignoreCase) { DaprEndpoint target = (DaprEndpoint) obj; switch (ignoreCase ? name.toLowerCase(Locale.ROOT) : name) { + case "allowsecretstoreandkeyfromheaders": + case "allowSecretStoreAndKeyFromHeaders": return target.getConfiguration().isAllowSecretStoreAndKeyFromHeaders(); case "bindingname": case "bindingName": return target.getConfiguration().getBindingName(); case "bindingoperation": diff --git a/components/camel-dapr/src/generated/java/org/apache/camel/component/dapr/DaprEndpointUriFactory.java b/components/camel-dapr/src/generated/java/org/apache/camel/component/dapr/DaprEndpointUriFactory.java index e62127132c54..1e286312b7d7 100644 --- a/components/camel-dapr/src/generated/java/org/apache/camel/component/dapr/DaprEndpointUriFactory.java +++ b/components/camel-dapr/src/generated/java/org/apache/camel/component/dapr/DaprEndpointUriFactory.java @@ -24,7 +24,8 @@ public class DaprEndpointUriFactory 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<>(41); + Set<String> props = new HashSet<>(42); + props.add("allowSecretStoreAndKeyFromHeaders"); props.add("bindingName"); props.add("bindingOperation"); props.add("bridgeErrorHandler"); diff --git a/components/camel-dapr/src/generated/resources/META-INF/org/apache/camel/component/dapr/dapr.json b/components/camel-dapr/src/generated/resources/META-INF/org/apache/camel/component/dapr/dapr.json index 5828a5b38408..8efa79dba04a 100644 --- a/components/camel-dapr/src/generated/resources/META-INF/org/apache/camel/component/dapr/dapr.json +++ b/components/camel-dapr/src/generated/resources/META-INF/org/apache/camel/component/dapr/dapr.json @@ -63,7 +63,8 @@ "workflowStartTime": { "index": 36, "kind": "property", "displayName": "Workflow Start Time", "group": "producer", "label": "producer", "required": false, "type": "object", "javaType": "java.time.Instant", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.dapr.DaprConfiguration", "configurationField": "configuration", "description": "The start time of the new workflow" }, "workflowVersion": { "index": 37, "kind": "property", "displayName": "Workflow Version", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.dapr.DaprConfiguration", "configurationField": "configuration", "description": "The version of the workflow to start" }, "autowiredEnabled": { "index": 38, "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 [...] - "headerFilterStrategy": { "index": 39, "kind": "property", "displayName": "Header Filter Strategy", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.spi.HeaderFilterStrategy", "deprecated": false, "autowired": false, "secret": false, "description": "To use a custom HeaderFilterStrategy to filter header to and from Camel message." } + "headerFilterStrategy": { "index": 39, "kind": "property", "displayName": "Header Filter Strategy", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.spi.HeaderFilterStrategy", "deprecated": false, "autowired": false, "secret": false, "description": "To use a custom HeaderFilterStrategy to filter header to and from Camel message." }, + "allowSecretStoreAndKeyFromHeaders": { "index": 40, "kind": "property", "displayName": "Allow Secret Store And Key From Headers", "group": "security", "label": "producer,security", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "security": "insecure:dev", "insecureValue": "true", "defaultValue": false, "configurationClass": "org.apache.camel.component.dapr.DaprConfiguration", "configurationField": "configuration" [...] }, "headers": { "CamelDaprServiceToInvoke": { "index": 0, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Target service to invoke. Can be a Dapr App ID, a named HTTPEndpoint, or a FQDN\/public URL", "constantName": "org.apache.camel.component.dapr.DaprConstants#SERVICE_TO_INVOKE" }, @@ -74,9 +75,9 @@ "CamelDaprHttpExtension": { "index": 5, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "HttpExtension", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The HttpExtension object for service invocation. Takes precedence over verb", "constantName": "org.apache.camel.component.dapr.DaprConstants#HTTP_EXTENSION" }, "CamelDaprStateOperation": { "index": 6, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "StateOperation", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "get", "description": "The state operation to perform on the state store. Required for DaprOperation.state operation", "constantName": "org.apache.camel.component.dapr.DaprConstants#STATE_OPERATION" }, "CamelDaprStateStore": { "index": 7, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The name of the Dapr state store to interact with, defined in statestore.yaml config", "constantName": "org.apache.camel.component.dapr.DaprConstants#STATE_STORE" }, - "CamelDaprSecretStore": { "index": 8, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The name of the Dapr secret store to interact with, defined in local-secret-store.yaml config", "constantName": "org.apache.camel.component.dapr.DaprConstants#SECRET_STORE" }, + "CamelDaprSecretStore": { "index": 8, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The name of the Dapr secret store to interact with, defined in local-secret-store.yaml config. For the secret operation, this header is ignored unless the endpoint enables allowSecretStoreAndKeyFromHeaders=true (disabled by default). [...] "CamelDaprConfigStore": { "index": 9, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The name of the Dapr config store to interact with, defined in statestore.yaml config", "constantName": "org.apache.camel.component.dapr.DaprConstants#CONFIG_STORE" }, - "CamelDaprKey": { "index": 10, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The key used to identify the state\/secret object within the specified state\/secret store", "constantName": "org.apache.camel.component.dapr.DaprConstants#KEY" }, + "CamelDaprKey": { "index": 10, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The key used to identify the state\/secret object within the specified state\/secret store. For the secret operation, this header is ignored unless the endpoint enables allowSecretStoreAndKeyFromHeaders=true (disabled by default).", "consta [...] "CamelDaprETag": { "index": 11, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The eTag for optimistic concurrency during state save or delete operations", "constantName": "org.apache.camel.component.dapr.DaprConstants#E_TAG" }, "CamelDaprConcurrency": { "index": 12, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "io.dapr.client.domain.StateOptions.Concurrency", "enum": [ "FIRST_WRITE", "LAST_WRITE" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Concurrency mode to use with state operations", "constantName": "org.apache.camel.component.dapr.DaprConstants#CONCURRENCY" }, "CamelDaprConsistency": { "index": 13, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "io.dapr.client.domain.StateOptions.Consistency", "enum": [ "EVENTUAL", "STRONG" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Consistency level to use with state operations", "constantName": "org.apache.camel.component.dapr.DaprConstants#CONSISTENCY" }, @@ -171,6 +172,7 @@ "workflowStartTime": { "index": 37, "kind": "parameter", "displayName": "Workflow Start Time", "group": "producer", "label": "producer", "required": false, "type": "object", "javaType": "java.time.Instant", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.dapr.DaprConfiguration", "configurationField": "configuration", "description": "The start time of the new workflow" }, "workflowVersion": { "index": 38, "kind": "parameter", "displayName": "Workflow Version", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.dapr.DaprConfiguration", "configurationField": "configuration", "description": "The version of the workflow to start" }, "lazyStartProducer": { "index": 39, "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 [...] - "headerFilterStrategy": { "index": 40, "kind": "parameter", "displayName": "Header Filter Strategy", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.spi.HeaderFilterStrategy", "deprecated": false, "autowired": false, "secret": false, "description": "To use a custom HeaderFilterStrategy to filter header to and from Camel message." } + "headerFilterStrategy": { "index": 40, "kind": "parameter", "displayName": "Header Filter Strategy", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.spi.HeaderFilterStrategy", "deprecated": false, "autowired": false, "secret": false, "description": "To use a custom HeaderFilterStrategy to filter header to and from Camel message." }, + "allowSecretStoreAndKeyFromHeaders": { "index": 41, "kind": "parameter", "displayName": "Allow Secret Store And Key From Headers", "group": "security", "label": "producer,security", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "security": "insecure:dev", "insecureValue": "true", "defaultValue": false, "configurationClass": "org.apache.camel.component.dapr.DaprConfiguration", "configurationField": "configuration [...] } } diff --git a/components/camel-dapr/src/main/docs/dapr-component.adoc b/components/camel-dapr/src/main/docs/dapr-component.adoc index cce7ea2f74c4..b739cc7105c2 100644 --- a/components/camel-dapr/src/main/docs/dapr-component.adoc +++ b/components/camel-dapr/src/main/docs/dapr-component.adoc @@ -64,6 +64,8 @@ Dapr’s bindings building block allows interaction with external systems via in === 4. *secret*: Dapr’s secret management building block enables secure retrieval of secrets from external secret stores. The secret operation fetches a secret from the specified secret store. If a key is provided, the operation retrieves the corresponding secret; if no key is specified, a bulk retrieval of all secrets from the store is performed. This operation requires the secretStore name, and optionally the key. +The secret operation reads the secret store and the key of the endpoint. The `CamelDaprSecretStore` and `CamelDaprKey` headers are ignored unless the endpoint sets `allowSecretStoreAndKeyFromHeaders=true`. An endpoint without a key fails, instead of reading all the secrets of the store, when the message has a `CamelDaprKey` header that it ignores. + === 5. *configuration*: Dapr’s configuration building block allows applications to retrieve dynamic configuration values from external configuration stores. The configuration operation fetches values associated with keys specified in configKeys. This operation requires the configStore name, and the configKeys. @@ -96,6 +98,14 @@ Dapr Workflows enable developers to write long-running, persistent, and reliable |`raiseEvent` |Sends an event to a running workflow instance that is waiting for it. Requires `workflowInstanceId` and `eventName`. The message body is used as the event payload. |=== +=== Headers that override the endpoint + +The producer headers, such as `CamelDaprServiceToInvoke`, `CamelDaprStateStore`, `CamelDaprKey`, `CamelDaprPubSubName`, `CamelDaprTopic`, `CamelDaprBindingName`, `CamelDaprStoreName` or `CamelDaprWorkflowInstanceId`, take the place of the endpoint option of the same name for one message. The `CamelDaprMetadata` header gives the metadata that the operation sends to the Dapr building block. + +For the `secret` operation, the `CamelDaprSecretStore` and `CamelDaprKey` headers are ignored unless the endpoint sets `allowSecretStoreAndKeyFromHeaders=true`. With that option, they take the place of the secret store and the key of the endpoint for one message, so the message chooses the secret that the operation reads. + +As the producer keeps the headers of the message, a `CamelDapr*` header also applies to the next `dapr` call of the route. When the messages come from an untrusted producer through a consumer that does not filter out `Camel*` headers, or when a `dapr` call must use only the options of its endpoint, remove these headers with `removeHeaders("CamelDapr*")` before the `dapr` endpoint. + === Dapr Consumer operations === 1. *pubSub*: diff --git a/components/camel-dapr/src/main/java/org/apache/camel/component/dapr/DaprConfiguration.java b/components/camel-dapr/src/main/java/org/apache/camel/component/dapr/DaprConfiguration.java index 26c8ccdefd09..29fae4c28ad2 100644 --- a/components/camel-dapr/src/main/java/org/apache/camel/component/dapr/DaprConfiguration.java +++ b/components/camel-dapr/src/main/java/org/apache/camel/component/dapr/DaprConfiguration.java @@ -75,6 +75,12 @@ public class DaprConfiguration implements Cloneable { @UriParam(label = "producer", description = "The key used to identify the state/secret object within the specified state/secret store") private String key; + @UriParam(label = "producer,security", defaultValue = "false", security = "insecure:dev", insecureValue = "true", + description = "Whether to allow overriding the secret store and the key of the endpoint with the" + + " CamelDaprSecretStore and CamelDaprKey headers for the secret operation. Disabled by" + + " default; enable it only when the header source is trusted, since it lets a message" + + " choose the secret store and the secret that the secret operation reads.") + private boolean allowSecretStoreAndKeyFromHeaders; @UriParam(label = "producer", description = "The eTag for optimistic concurrency during state save or delete operations") private String eTag; @UriParam(label = "producer", description = "Concurrency mode to use with state operations", @@ -298,6 +304,18 @@ public class DaprConfiguration implements Cloneable { this.key = key; } + /** + * Whether the <b>CamelDaprSecretStore</b> and <b>CamelDaprKey</b> headers may override the secret store and the key + * of the endpoint for the secret operation. + */ + public boolean isAllowSecretStoreAndKeyFromHeaders() { + return allowSecretStoreAndKeyFromHeaders; + } + + public void setAllowSecretStoreAndKeyFromHeaders(boolean allowSecretStoreAndKeyFromHeaders) { + this.allowSecretStoreAndKeyFromHeaders = allowSecretStoreAndKeyFromHeaders; + } + /** * ETag used for <b>optimistic concurrency</b> during state save or delete operations. * <p> diff --git a/components/camel-dapr/src/main/java/org/apache/camel/component/dapr/DaprConfigurationOptionsProxy.java b/components/camel-dapr/src/main/java/org/apache/camel/component/dapr/DaprConfigurationOptionsProxy.java index dd651b00745e..87af87f3e6bb 100644 --- a/components/camel-dapr/src/main/java/org/apache/camel/component/dapr/DaprConfigurationOptionsProxy.java +++ b/components/camel-dapr/src/main/java/org/apache/camel/component/dapr/DaprConfigurationOptionsProxy.java @@ -79,8 +79,23 @@ public class DaprConfigurationOptionsProxy { return getOption(DaprExchangeHeaders::getStateStoreFromHeaders, configuration::getStateStore, exchange); } + // the secret store and key headers may only make the secret operation read another secret when the endpoint opts in public String getSecretStore(final Exchange exchange) { - return getOption(DaprExchangeHeaders::getSecretStoreFromHeaders, configuration::getSecretStore, exchange); + if (configuration.isAllowSecretStoreAndKeyFromHeaders()) { + return getOption(DaprExchangeHeaders::getSecretStoreFromHeaders, configuration::getSecretStore, exchange); + } + return configuration.getSecretStore(); + } + + public String getSecretKey(final Exchange exchange) { + if (configuration.isAllowSecretStoreAndKeyFromHeaders()) { + return getKey(exchange); + } + return configuration.getKey(); + } + + public boolean isAllowSecretStoreAndKeyFromHeaders() { + return configuration.isAllowSecretStoreAndKeyFromHeaders(); } public String getConfigStore(final Exchange exchange) { diff --git a/components/camel-dapr/src/main/java/org/apache/camel/component/dapr/DaprConstants.java b/components/camel-dapr/src/main/java/org/apache/camel/component/dapr/DaprConstants.java index be4a21aa64df..7edf85de3efd 100644 --- a/components/camel-dapr/src/main/java/org/apache/camel/component/dapr/DaprConstants.java +++ b/components/camel-dapr/src/main/java/org/apache/camel/component/dapr/DaprConstants.java @@ -49,7 +49,9 @@ public class DaprConstants { javaType = "String") public static final String STATE_STORE = HEADER_PREFIX + "StateStore"; @Metadata(label = "producer", - description = "The name of the Dapr secret store to interact with, defined in local-secret-store.yaml config", + description = "The name of the Dapr secret store to interact with, defined in local-secret-store.yaml config." + + " For the secret operation, this header is ignored unless the endpoint enables" + + " allowSecretStoreAndKeyFromHeaders=true (disabled by default).", javaType = "String") public static final String SECRET_STORE = HEADER_PREFIX + "SecretStore"; @Metadata(label = "producer", @@ -57,7 +59,9 @@ public class DaprConstants { javaType = "String") public static final String CONFIG_STORE = HEADER_PREFIX + "ConfigStore"; @Metadata(label = "producer", - description = "The key used to identify the state/secret object within the specified state/secret store", + description = "The key used to identify the state/secret object within the specified state/secret store." + + " For the secret operation, this header is ignored unless the endpoint enables" + + " allowSecretStoreAndKeyFromHeaders=true (disabled by default).", javaType = "String") public static final String KEY = HEADER_PREFIX + "Key"; @Metadata(label = "producer", description = "The eTag for optimistic concurrency during state save or delete operations", diff --git a/components/camel-dapr/src/main/java/org/apache/camel/component/dapr/operations/DaprSecretHandler.java b/components/camel-dapr/src/main/java/org/apache/camel/component/dapr/operations/DaprSecretHandler.java index 5c4cc21c0d3e..af87990102c9 100644 --- a/components/camel-dapr/src/main/java/org/apache/camel/component/dapr/operations/DaprSecretHandler.java +++ b/components/camel-dapr/src/main/java/org/apache/camel/component/dapr/operations/DaprSecretHandler.java @@ -23,7 +23,9 @@ import io.dapr.client.domain.GetBulkSecretRequest; import io.dapr.client.domain.GetSecretRequest; import org.apache.camel.Exchange; import org.apache.camel.component.dapr.DaprConfigurationOptionsProxy; +import org.apache.camel.component.dapr.DaprConstants; import org.apache.camel.component.dapr.DaprEndpoint; +import org.apache.camel.component.dapr.DaprExchangeHeaders; import org.apache.camel.util.ObjectHelper; public class DaprSecretHandler implements DaprOperationHandler { @@ -39,7 +41,7 @@ public class DaprSecretHandler implements DaprOperationHandler { @Override public DaprOperationResponse handle(Exchange exchange) { String secretStore = configurationOptionsProxy.getSecretStore(exchange); - String key = configurationOptionsProxy.getKey(exchange); + String key = configurationOptionsProxy.getSecretKey(exchange); Map<String, String> metadata = configurationOptionsProxy.getMetadata(exchange); DaprClient client = endpoint.getClient(); @@ -62,7 +64,22 @@ public class DaprSecretHandler implements DaprOperationHandler { String secretStore = configurationOptionsProxy.getSecretStore(exchange); if (ObjectHelper.isEmpty(secretStore)) { + if (!configurationOptionsProxy.isAllowSecretStoreAndKeyFromHeaders() + && ObjectHelper.isNotEmpty(DaprExchangeHeaders.getSecretStoreFromHeaders(exchange))) { + throw new IllegalArgumentException( + "secretStore is mandatory to get secret. The " + DaprConstants.SECRET_STORE + + " header is ignored unless allowSecretStoreAndKeyFromHeaders=true"); + } throw new IllegalArgumentException("secretStore is mandatory to get secret"); } + // without a key the endpoint reads all the secrets of the store, so the key header must not be silently ignored + if (!configurationOptionsProxy.isAllowSecretStoreAndKeyFromHeaders() + && ObjectHelper.isEmpty(configurationOptionsProxy.getSecretKey(exchange)) + && ObjectHelper.isNotEmpty(DaprExchangeHeaders.getKeyFromHeaders(exchange))) { + throw new IllegalArgumentException( + "The " + DaprConstants.KEY + " header is ignored by the secret operation unless" + + " allowSecretStoreAndKeyFromHeaders=true. Set the key option on the" + + " endpoint, or remove the header to get all the secrets of the store"); + } } } diff --git a/components/camel-dapr/src/test/java/org/apache/camel/component/dapr/DaprConfigurationOptionsProxyTest.java b/components/camel-dapr/src/test/java/org/apache/camel/component/dapr/DaprConfigurationOptionsProxyTest.java index 1f18a883635d..1ca764ec5e96 100644 --- a/components/camel-dapr/src/test/java/org/apache/camel/component/dapr/DaprConfigurationOptionsProxyTest.java +++ b/components/camel-dapr/src/test/java/org/apache/camel/component/dapr/DaprConfigurationOptionsProxyTest.java @@ -49,4 +49,26 @@ public class DaprConfigurationOptionsProxyTest extends CamelTestSupport { assertNull(configurationOptionsProxy.getMethodToInvoke(exchange)); } + + @Test + void secretStoreAndKeyHeadersApplyToTheSecretOperationOnlyWhenAllowed() { + final DaprConfiguration configuration = new DaprConfiguration(); + configuration.setSecretStore("configStore"); + configuration.setKey("configKey"); + final DaprConfigurationOptionsProxy configurationOptionsProxy = new DaprConfigurationOptionsProxy(configuration); + + final Exchange exchange = new DefaultExchange(context); + exchange.getIn().setHeader(DaprConstants.SECRET_STORE, "headerStore"); + exchange.getIn().setHeader(DaprConstants.KEY, "headerKey"); + + // by default, the secret operation uses the endpoint, and the state operations still use the key header + assertEquals("configStore", configurationOptionsProxy.getSecretStore(exchange)); + assertEquals("configKey", configurationOptionsProxy.getSecretKey(exchange)); + assertEquals("headerKey", configurationOptionsProxy.getKey(exchange)); + + configuration.setAllowSecretStoreAndKeyFromHeaders(true); + + assertEquals("headerStore", configurationOptionsProxy.getSecretStore(exchange)); + assertEquals("headerKey", configurationOptionsProxy.getSecretKey(exchange)); + } } diff --git a/components/camel-dapr/src/test/java/org/apache/camel/component/dapr/DaprProducerTest.java b/components/camel-dapr/src/test/java/org/apache/camel/component/dapr/DaprProducerTest.java index 6397e71a2f0f..06819a118566 100644 --- a/components/camel-dapr/src/test/java/org/apache/camel/component/dapr/DaprProducerTest.java +++ b/components/camel-dapr/src/test/java/org/apache/camel/component/dapr/DaprProducerTest.java @@ -44,10 +44,12 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -98,6 +100,64 @@ class DaprProducerTest extends CamelTestSupport { assertEquals(secrets, exchange.getMessage().getBody()); } + @Test + void secretStoreAndKeyHeadersAreIgnoredByDefaultForSecret() { + Map<String, String> secret = Map.of("mySecret", "myValue"); + when(client.getSecret(any(GetSecretRequest.class))).thenReturn(Mono.just(secret)); + + Exchange exchange = template.request("dapr:secret?secretStore=myStore&key=mySecret&" + CLIENTS, e -> { + e.getIn().setHeader(DaprConstants.SECRET_STORE, "otherStore"); + e.getIn().setHeader(DaprConstants.KEY, "otherSecret"); + }); + + assertNull(exchange.getException()); + ArgumentCaptor<GetSecretRequest> request = ArgumentCaptor.forClass(GetSecretRequest.class); + verify(client).getSecret(request.capture()); + assertEquals("myStore", request.getValue().getStoreName()); + assertEquals("mySecret", request.getValue().getKey()); + } + + @Test + void keyHeaderDoesNotTurnABulkSecretEndpointIntoAnotherSecretByDefault() { + Exchange exchange = template.request("dapr:secret?secretStore=myStore&" + CLIENTS, + e -> e.getIn().setHeader(DaprConstants.KEY, "otherSecret")); + + IllegalArgumentException cause = assertInstanceOf(IllegalArgumentException.class, exchange.getException()); + assertTrue(cause.getMessage().contains("allowSecretStoreAndKeyFromHeaders=true")); + verify(client, never()).getSecret(any(GetSecretRequest.class)); + verify(client, never()).getBulkSecret(any(GetBulkSecretRequest.class)); + } + + @Test + void secretStoreHeaderDoesNotStandInForAMissingSecretStoreByDefault() { + Exchange exchange = template.request("dapr:secret?key=mySecret&" + CLIENTS, + e -> e.getIn().setHeader(DaprConstants.SECRET_STORE, "otherStore")); + + IllegalArgumentException cause = assertInstanceOf(IllegalArgumentException.class, exchange.getException()); + assertTrue(cause.getMessage().contains("secretStore is mandatory")); + assertTrue(cause.getMessage().contains("allowSecretStoreAndKeyFromHeaders=true")); + verify(client, never()).getSecret(any(GetSecretRequest.class)); + } + + @Test + void secretStoreAndKeyHeadersOverrideTheEndpointWhenAllowed() { + Map<String, String> secret = Map.of("otherSecret", "otherValue"); + when(client.getSecret(any(GetSecretRequest.class))).thenReturn(Mono.just(secret)); + + Exchange exchange = template.request( + "dapr:secret?secretStore=myStore&allowSecretStoreAndKeyFromHeaders=true&" + CLIENTS, e -> { + e.getIn().setHeader(DaprConstants.SECRET_STORE, "otherStore"); + e.getIn().setHeader(DaprConstants.KEY, "otherSecret"); + }); + + assertNull(exchange.getException()); + assertEquals(secret, exchange.getMessage().getBody()); + ArgumentCaptor<GetSecretRequest> request = ArgumentCaptor.forClass(GetSecretRequest.class); + verify(client).getSecret(request.capture()); + assertEquals("otherStore", request.getValue().getStoreName()); + assertEquals("otherSecret", request.getValue().getKey()); + } + @Test void keepsTheHeadersOfTheMessageAndAddsTheResponseHeaders() { Map<String, ConfigurationItem> config = Map.of("myKey", new ConfigurationItem("myKey", "myValue", "1")); 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 80abc1dfe62c..6df6e7c1d127 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 @@ -71,6 +71,7 @@ public final class SecurityUtils { map.put("allowoperationheader", new SecurityOption(INSECURE_DEV, "true")); map.put("allowpredicatefrommessage", new SecurityOption(INSECURE_DEV, "true")); map.put("allowqueryfromheader", new SecurityOption(INSECURE_DEV, "true")); + map.put("allowsecretstoreandkeyfromheaders", new SecurityOption(INSECURE_DEV, "true")); map.put("allowserializedheaders", new SecurityOption(INSECURE_SERIALIZATION, "true")); map.put("allowsharedstatefulkey", new SecurityOption(INSECURE_DEV, "true")); map.put("allowtemplatefromheader", new SecurityOption(INSECURE_DEV, "true")); @@ -154,6 +155,8 @@ public final class SecurityUtils { "component:dynamicroutercontrol")); owners.put("allowqueryfromheader", Set.of( "component:sql")); + owners.put("allowsecretstoreandkeyfromheaders", Set.of( + "component:dapr")); owners.put("allowserializedheaders", Set.of( "component:activemq", "component:activemq6", diff --git a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc index c7fa7e136c87..32dfb74e0991 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc @@ -999,6 +999,22 @@ as a quoted, escaped JSON string; a stream body failed. A route that relied on t it wants, for example with `marshal().json()`. Other bodies, such as a `Map` or a POJO, are still serialized as JSON. The workflow operations give the body to the workflow client as before. +=== camel-dapr - the secret store and key headers need the allowSecretStoreAndKeyFromHeaders option + +The `secret` operation of the `dapr` producer took the secret store and the key from the `CamelDaprSecretStore` and +`CamelDaprKey` headers when they were set, over the `secretStore` and `key` options of the endpoint, so a message could +read another secret, or use another secret store, than the endpoint is configured with. For the `secret` operation, +these headers are now ignored unless the endpoint sets the new `allowSecretStoreAndKeyFromHeaders=true` option +(disabled by default), or the component sets `camel.component.dapr.allowSecretStoreAndKeyFromHeaders=true`. A route +that relies on them must set this option. + +A `secret` endpoint without a `key`, which took the key from the `CamelDaprKey` header, now fails with an error that +names the option, instead of reading all the secrets of the store. A `secret` endpoint without a `secretStore`, which +took it from the `CamelDaprSecretStore` header, now fails because the secret store is missing. + +The other operations do not change: the `state` operations still take the key from the `CamelDaprKey` header, and the +other `CamelDapr*` headers still take the place of the options of the endpoint, as before. + === camel-consul - no Optional in the body, and the CamelConsulResult header of every action The `GET_SESSIONS` action of `consul:kv`, the `RENEW` action of `consul:session` and the `GET` action of diff --git a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/DaprComponentBuilderFactory.java b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/DaprComponentBuilderFactory.java index 577a8863ade8..bc34e319537d 100644 --- a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/DaprComponentBuilderFactory.java +++ b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/DaprComponentBuilderFactory.java @@ -719,6 +719,27 @@ public interface DaprComponentBuilderFactory { doSetProperty("headerFilterStrategy", headerFilterStrategy); return this; } + + + /** + * Whether to allow overriding the secret store and the key of the + * endpoint with the CamelDaprSecretStore and CamelDaprKey headers for + * the secret operation. Disabled by default; enable it only when the + * header source is trusted, since it lets a message choose the secret + * store and the secret that the secret operation reads. + * + * The option is a: <code>boolean</code> type. + * + * Default: false + * Group: security + * + * @param allowSecretStoreAndKeyFromHeaders the value to set + * @return the dsl builder + */ + default DaprComponentBuilder allowSecretStoreAndKeyFromHeaders(boolean allowSecretStoreAndKeyFromHeaders) { + doSetProperty("allowSecretStoreAndKeyFromHeaders", allowSecretStoreAndKeyFromHeaders); + return this; + } } class DaprComponentBuilderImpl @@ -780,6 +801,7 @@ public interface DaprComponentBuilderFactory { case "workflowVersion": getOrCreateConfiguration((DaprComponent) component).setWorkflowVersion((java.lang.String) value); return true; case "autowiredEnabled": ((DaprComponent) component).setAutowiredEnabled((boolean) value); return true; case "headerFilterStrategy": ((DaprComponent) component).setHeaderFilterStrategy((org.apache.camel.spi.HeaderFilterStrategy) value); return true; + case "allowSecretStoreAndKeyFromHeaders": getOrCreateConfiguration((DaprComponent) component).setAllowSecretStoreAndKeyFromHeaders((boolean) value); return true; default: return false; } } diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/DaprEndpointBuilderFactory.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/DaprEndpointBuilderFactory.java index 73c58d0553aa..d5359a55809d 100644 --- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/DaprEndpointBuilderFactory.java +++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/DaprEndpointBuilderFactory.java @@ -1070,6 +1070,44 @@ public interface DaprEndpointBuilderFactory { doSetProperty("workflowVersion", workflowVersion); return this; } + /** + * Whether to allow overriding the secret store and the key of the + * endpoint with the CamelDaprSecretStore and CamelDaprKey headers for + * the secret operation. Disabled by default; enable it only when the + * header source is trusted, since it lets a message choose the secret + * store and the secret that the secret operation reads. + * + * The option is a: <code>boolean</code> type. + * + * Default: false + * Group: security + * + * @param allowSecretStoreAndKeyFromHeaders the value to set + * @return the dsl builder + */ + default DaprEndpointProducerBuilder allowSecretStoreAndKeyFromHeaders(boolean allowSecretStoreAndKeyFromHeaders) { + doSetProperty("allowSecretStoreAndKeyFromHeaders", allowSecretStoreAndKeyFromHeaders); + return this; + } + /** + * Whether to allow overriding the secret store and the key of the + * endpoint with the CamelDaprSecretStore and CamelDaprKey headers for + * the secret operation. Disabled by default; enable it only when the + * header source is trusted, since it lets a message choose the secret + * store and the secret that the secret operation reads. + * + * The option will be converted to a <code>boolean</code> type. + * + * Default: false + * Group: security + * + * @param allowSecretStoreAndKeyFromHeaders the value to set + * @return the dsl builder + */ + default DaprEndpointProducerBuilder allowSecretStoreAndKeyFromHeaders(String allowSecretStoreAndKeyFromHeaders) { + doSetProperty("allowSecretStoreAndKeyFromHeaders", allowSecretStoreAndKeyFromHeaders); + return this; + } } /** @@ -1523,7 +1561,9 @@ public interface DaprEndpointBuilderFactory { } /** * The name of the Dapr secret store to interact with, defined in - * local-secret-store.yaml config. + * local-secret-store.yaml config. For the secret operation, this header + * is ignored unless the endpoint enables + * allowSecretStoreAndKeyFromHeaders=true (disabled by default). * * The option is a: {@code String} type. * @@ -1549,7 +1589,9 @@ public interface DaprEndpointBuilderFactory { } /** * The key used to identify the state/secret object within the specified - * state/secret store. + * state/secret store. For the secret operation, this header is ignored + * unless the endpoint enables allowSecretStoreAndKeyFromHeaders=true + * (disabled by default). * * The option is a: {@code String} type. *
