You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ac...@apache.org on 2025/11/10 12:23:30 UTC
(camel) branch CAMEL-22676 created (now 446585ceddb6)
This is an automated email from the ASF dual-hosted git repository.
acosentino pushed a change to branch CAMEL-22676
in repository https://gitbox.apache.org/repos/asf/camel.git
at 446585ceddb6 CAMEL-22676 - Camel-Hashicorp-Vault: Support Secret Refresh
This branch includes the following new commits:
new 137fd615be89 CAMEL-22676 - Camel-Hashicorp-Vault: Support Secret Refresh
new 446585ceddb6 CAMEL-22676 - Camel-Hashicorp-Vault: Support Secret Refresh
The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(camel) 02/02: CAMEL-22676 - Camel-Hashicorp-Vault: Support Secret Refresh
Posted by ac...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
acosentino pushed a commit to branch CAMEL-22676
in repository https://gitbox.apache.org/repos/asf/camel.git
commit 446585ceddb6a86fa024b319b0629a10f6359ea2
Author: Andrea Cosentino <an...@gmail.com>
AuthorDate: Mon Nov 10 13:22:42 2025 +0100
CAMEL-22676 - Camel-Hashicorp-Vault: Support Secret Refresh
Signed-off-by: Andrea Cosentino <an...@gmail.com>
---
docs/user-manual/modules/ROOT/pages/security.adoc | 70 +++++++++++++++++++++++
1 file changed, 70 insertions(+)
diff --git a/docs/user-manual/modules/ROOT/pages/security.adoc b/docs/user-manual/modules/ROOT/pages/security.adoc
index 1fe9ba02cd65..515eb1f0fe49 100644
--- a/docs/user-manual/modules/ROOT/pages/security.adoc
+++ b/docs/user-manual/modules/ROOT/pages/security.adoc
@@ -1125,3 +1125,73 @@ where `camel.vault.ibm.eventStreamBootstrapServers` is the comma-separated list
Note that `camel.vault.ibm.secrets` is not mandatory: if not specified the task responsible for checking updates events will take into accounts or the properties with an `ibm:` prefix.
The only requirement is adding the camel-ibm-secrets-manager jar to your Camel application.
+
+==== Automatic Camel context reloading on Secret Refresh while using Hashicorp Vault
+
+Being able to reload Camel context on a Secret Refresh could be done by specifying the usual credentials (the same used for Hashicorp Vault Property Function).
+
+With Environment variables:
+
+[source,bash]
+----
+export CAMEL_VAULT_HASHICORP_TOKEN=token
+export CAMEL_VAULT_HASHICORP_HOST=host
+export CAMEL_VAULT_HASHICORP_PORT=port
+export CAMEL_VAULT_HASHICORP_SCHEME=http/https
+----
+
+or as plain Camel main properties:
+
+[source,properties]
+----
+camel.vault.hashicorp.token = token
+camel.vault.hashicorp.host = host
+camel.vault.hashicorp.port = port
+camel.vault.hashicorp.scheme = scheme
+----
+
+To enable the automatic refresh, you'll need additional properties to set:
+
+[source,properties]
+----
+camel.vault.hashicorp.refreshEnabled=true
+camel.vault.hashicorp.refreshPeriod=60000
+camel.vault.hashicorp.secrets=database,api-keys
+camel.main.context-reload-enabled = true
+----
+
+where `camel.vault.hashicorp.refreshEnabled` will enable the automatic context reload, `camel.vault.hashicorp.refreshPeriod` is the interval of time between two different checks for update events (default 60000ms), and `camel.vault.hashicorp.secrets` is a comma-separated list of secret names (or patterns) to check for updates.
+
+The secret names should use the following format:
+
+- `mysecret` or `path/to/secret` - tracks a secret in the default `secret` engine
+- `myengine:mysecret` or `myengine:path/to/secret` - tracks a secret in the `myengine` engine (use `:` to separate engine from path)
+- You can use patterns like `database*` to match multiple secrets
+
+For example, to track secrets `omsecret/test` in the default `secret` engine and `myapp/credentials` in a custom `kv` engine:
+
+[source,properties]
+----
+camel.vault.hashicorp.secrets=omsecret/test,kv:myapp/credentials
+----
+
+Note that `camel.vault.hashicorp.secrets` is not mandatory: if not specified the task responsible for checking updates events will take into account all the properties with a `hashicorp:` prefix.
+
+===== How the Refresh Mechanism Works
+
+Unlike cloud-based secret managers (AWS, GCP, Azure) that provide event-driven notifications, Hashicorp Vault does not have a native event notification system for secret changes. Therefore, this implementation uses a *polling-based approach*:
+
+1. *Metadata Polling*: The refresh task periodically queries the Hashicorp Vault metadata endpoint (`/v1/{engine}/metadata/{secret}`) for each tracked secret.
+2. *Version Tracking*: It compares the `current_version` field to detect changes.
+3. *Change Detection*: When a version change is detected, it triggers a Camel context reload.
+4. *Efficiency*: Only metadata is queried (not the full secret content), making this approach lightweight.
+
+For example, if a secret named `database` is updated in Vault:
+
+- The metadata endpoint returns `"current_version": 5`
+- On the next check, if `current_version` changes to `6`, a reload is triggered
+- The refresh period (default 60 seconds) determines how quickly changes are detected
+
+This polling approach works with all Hashicorp Vault deployments (on-premise, cloud, enterprise, and open-source) without requiring additional infrastructure.
+
+The only requirement is adding the camel-hashicorp-vault jar to your Camel application.
(camel) 01/02: CAMEL-22676 - Camel-Hashicorp-Vault: Support Secret Refresh
Posted by ac...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
acosentino pushed a commit to branch CAMEL-22676
in repository https://gitbox.apache.org/repos/asf/camel.git
commit 137fd615be895de8cd4cefa15545a49942d5cea2
Author: Andrea Cosentino <an...@gmail.com>
AuthorDate: Mon Nov 10 13:17:53 2025 +0100
CAMEL-22676 - Camel-Hashicorp-Vault: Support Secret Refresh
Signed-off-by: Andrea Cosentino <an...@gmail.com>
---
.../main/camel-main-configuration-metadata.json | 3 +
.../camel/periodic-task/hashicorp-secret-refresh | 2 +
.../src/main/docs/hashicorp-vault-component.adoc | 59 ++++
.../vault/HashicorpVaultPropertiesFunction.java | 13 +
.../vault/HashicorpVaultReloadTriggerTask.java | 303 +++++++++++++++++++++
.../camel/vault/HashicorpVaultConfiguration.java | 39 +++
.../HashicorpVaultConfigurationConfigurer.java | 15 +
...corpVaultConfigurationPropertiesConfigurer.java | 18 ++
.../camel-main-configuration-metadata.json | 3 +
core/camel-main/src/main/docs/main.adoc | 5 +-
.../camel/main/DefaultConfigurationConfigurer.java | 19 ++
.../HashicorpVaultConfigurationProperties.java | 24 ++
12 files changed, 502 insertions(+), 1 deletion(-)
diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
index 9da54a95bd7f..07521281b375 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
@@ -425,7 +425,10 @@
{ "name": "camel.vault.hashicorp.host", "required": false, "description": "Host to access hashicorp vault", "sourceType": "org.apache.camel.vault.HashicorpVaultConfiguration", "type": "string", "javaType": "java.lang.String", "secret": false },
{ "name": "camel.vault.hashicorp.namespace", "required": false, "description": "If the Hashicorp Vault instance is deployed on Hashicorp Cloud, this field will determine the namespace", "sourceType": "org.apache.camel.vault.HashicorpVaultConfiguration", "type": "string", "javaType": "java.lang.String", "secret": false },
{ "name": "camel.vault.hashicorp.port", "required": false, "description": "Port to access hashicorp vault", "sourceType": "org.apache.camel.vault.HashicorpVaultConfiguration", "type": "string", "javaType": "java.lang.String", "secret": false },
+ { "name": "camel.vault.hashicorp.refreshEnabled", "required": false, "description": "Whether to automatically reload Camel upon secrets being updated in Hashicorp Vault.", "sourceType": "org.apache.camel.vault.HashicorpVaultConfiguration", "type": "boolean", "javaType": "boolean", "defaultValue": false, "secret": false },
+ { "name": "camel.vault.hashicorp.refreshPeriod", "required": false, "description": "The period (millis) between checking Hashicorp Vault for updated secrets.", "sourceType": "org.apache.camel.vault.HashicorpVaultConfiguration", "type": "integer", "javaType": "long", "defaultValue": 60000, "secret": false },
{ "name": "camel.vault.hashicorp.scheme", "required": false, "description": "Scheme to access hashicorp vault", "sourceType": "org.apache.camel.vault.HashicorpVaultConfiguration", "type": "string", "javaType": "java.lang.String", "secret": false },
+ { "name": "camel.vault.hashicorp.secrets", "required": false, "description": "Specify the secret names (or pattern) to check for updates. Multiple secrets can be separated by comma.", "sourceType": "org.apache.camel.vault.HashicorpVaultConfiguration", "type": "string", "javaType": "java.lang.String", "secret": false },
{ "name": "camel.vault.hashicorp.token", "required": false, "description": "Token to access hashicorp vault", "sourceType": "org.apache.camel.vault.HashicorpVaultConfiguration", "type": "string", "javaType": "java.lang.String", "secret": true },
{ "name": "camel.vault.ibm.eventStreamBootstrapServers", "required": false, "description": "Specify the Bootstrap servers for consuming notification on IBM Event Stream. Multiple servers can be separated by comma.", "sourceType": "org.apache.camel.vault.IBMSecretsManagerVaultConfiguration", "type": "string", "javaType": "java.lang.String", "secret": false },
{ "name": "camel.vault.ibm.eventStreamConsumerPollTimeout", "required": false, "description": "Specify the Consumer Poll Timeout while consuming from IBM Event Stream Topic", "sourceType": "org.apache.camel.vault.IBMSecretsManagerVaultConfiguration", "type": "integer", "javaType": "long", "defaultValue": 3000, "secret": false },
diff --git a/components/camel-hashicorp-vault/src/generated/resources/META-INF/services/org/apache/camel/periodic-task/hashicorp-secret-refresh b/components/camel-hashicorp-vault/src/generated/resources/META-INF/services/org/apache/camel/periodic-task/hashicorp-secret-refresh
new file mode 100644
index 000000000000..74324fe0ada0
--- /dev/null
+++ b/components/camel-hashicorp-vault/src/generated/resources/META-INF/services/org/apache/camel/periodic-task/hashicorp-secret-refresh
@@ -0,0 +1,2 @@
+# Generated by camel build tools - do NOT edit this file!
+class=org.apache.camel.component.hashicorp.vault.vault.HashicorpVaultReloadTriggerTask
diff --git a/components/camel-hashicorp-vault/src/main/docs/hashicorp-vault-component.adoc b/components/camel-hashicorp-vault/src/main/docs/hashicorp-vault-component.adoc
index 2def23cd9bf0..1bec261ce7ac 100644
--- a/components/camel-hashicorp-vault/src/main/docs/hashicorp-vault-component.adoc
+++ b/components/camel-hashicorp-vault/src/main/docs/hashicorp-vault-component.adoc
@@ -231,6 +231,65 @@ This approach will return the username field of the database secret with version
The only requirement is adding the camel-hashicorp-vault jar to your Camel application.
+=== Automatic Camel context reloading on Secret Refresh
+
+Being able to reload Camel context on a Secret Refresh could be done by specifying the usual credentials (the same used for Hashicorp Vault Property Function).
+
+With Environment variables:
+
+[source,bash]
+----
+export CAMEL_HASHICORP_VAULT_TOKEN=token
+export CAMEL_HASHICORP_VAULT_HOST=host
+export CAMEL_HASHICORP_VAULT_PORT=port
+export CAMEL_HASHICORP_VAULT_SCHEME=http/https
+----
+
+or as plain Camel main properties:
+
+[source,properties]
+----
+camel.vault.hashicorp.token = token
+camel.vault.hashicorp.host = host
+camel.vault.hashicorp.port = port
+camel.vault.hashicorp.scheme = scheme
+----
+
+To enable the automatic refresh, you'll need additional properties to set:
+
+[source,properties]
+----
+camel.vault.hashicorp.refreshEnabled=true
+camel.vault.hashicorp.refreshPeriod=60000
+camel.vault.hashicorp.secrets=database,api-keys
+camel.main.context-reload-enabled = true
+----
+
+where `camel.vault.hashicorp.refreshEnabled` will enable the automatic context reload, `camel.vault.hashicorp.refreshPeriod` is the interval of time between two different checks for update events (default 60000ms), and `camel.vault.hashicorp.secrets` is a comma-separated list of secret names (or patterns) to check for updates.
+
+The secret names should use the following format:
+- `mysecret` or `path/to/secret` - tracks a secret in the default `secret` engine
+- `myengine:mysecret` or `myengine:path/to/secret` - tracks a secret in the `myengine` engine (use `:` to separate engine from path)
+- You can use patterns like `database*` to match multiple secrets
+
+Note that `camel.vault.hashicorp.secrets` is not mandatory: if not specified the task responsible for checking updates events will take into account all the properties with a `hashicorp:` prefix.
+
+==== How the Refresh Mechanism Works
+
+Unlike cloud-based secret managers (AWS, GCP, Azure) that provide event-driven notifications, Hashicorp Vault does not have a native event notification system for secret changes. Therefore, this implementation uses a **polling-based approach**:
+
+1. **Metadata Polling**: The refresh task periodically queries the Hashicorp Vault metadata endpoint (`/v1/{engine}/metadata/{secret}`) for each tracked secret.
+2. **Version Tracking**: It compares the `current_version` field to detect changes.
+3. **Change Detection**: When a version change is detected, it triggers a Camel context reload.
+4. **Efficiency**: Only metadata is queried (not the full secret content), making this approach lightweight.
+
+For example, if a secret named `database` is updated in Vault:
+- The metadata endpoint returns `"current_version": 5`
+- On the next check, if `current_version` changes to `6`, a reload is triggered
+- The refresh period (default 60 seconds) determines how quickly changes are detected
+
+This polling approach works with all Hashicorp Vault deployments (on-premise, cloud, enterprise, and open-source) without requiring additional infrastructure.
+
include::spring-boot:partial$starter.adoc[]
=== Using Hashicorp Vault Property Function in Spring Boot for Early resolving properties
diff --git a/components/camel-hashicorp-vault/src/main/java/org/apache/camel/component/hashicorp/vault/HashicorpVaultPropertiesFunction.java b/components/camel-hashicorp-vault/src/main/java/org/apache/camel/component/hashicorp/vault/HashicorpVaultPropertiesFunction.java
index 5f4230f66ab5..60b40d0a1f3a 100644
--- a/components/camel-hashicorp-vault/src/main/java/org/apache/camel/component/hashicorp/vault/HashicorpVaultPropertiesFunction.java
+++ b/components/camel-hashicorp-vault/src/main/java/org/apache/camel/component/hashicorp/vault/HashicorpVaultPropertiesFunction.java
@@ -16,7 +16,9 @@
*/
package org.apache.camel.component.hashicorp.vault;
+import java.util.HashSet;
import java.util.Map;
+import java.util.Set;
import org.apache.camel.CamelContext;
import org.apache.camel.CamelContextAware;
@@ -82,6 +84,7 @@ public class HashicorpVaultPropertiesFunction extends ServiceSupport implements
= "CAMEL_HASHICORP_VAULT_NAMESPACE";
private CamelContext camelContext;
private VaultTemplate client;
+ private final Set<String> secrets = new HashSet<>();
private String engine;
private String namespace;
@@ -207,6 +210,9 @@ public class HashicorpVaultPropertiesFunction extends ServiceSupport implements
}
private String getSecretFromSource(String key, String subkey, String defaultValue, String version) {
+ // capture name of secret
+ secrets.add(key);
+
String returnValue = null;
try {
String completePath = "";
@@ -254,4 +260,11 @@ public class HashicorpVaultPropertiesFunction extends ServiceSupport implements
public CamelContext getCamelContext() {
return camelContext;
}
+
+ /**
+ * Ids of the secrets in use
+ */
+ public Set<String> getSecrets() {
+ return secrets;
+ }
}
diff --git a/components/camel-hashicorp-vault/src/main/java/org/apache/camel/component/hashicorp/vault/vault/HashicorpVaultReloadTriggerTask.java b/components/camel-hashicorp-vault/src/main/java/org/apache/camel/component/hashicorp/vault/vault/HashicorpVaultReloadTriggerTask.java
new file mode 100644
index 000000000000..bdf634f61980
--- /dev/null
+++ b/components/camel-hashicorp-vault/src/main/java/org/apache/camel/component/hashicorp/vault/vault/HashicorpVaultReloadTriggerTask.java
@@ -0,0 +1,303 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.hashicorp.vault.vault;
+
+import java.time.Instant;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.CamelContextAware;
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.component.hashicorp.vault.HashicorpVaultPropertiesFunction;
+import org.apache.camel.spi.ContextReloadStrategy;
+import org.apache.camel.spi.PropertiesComponent;
+import org.apache.camel.spi.PropertiesFunction;
+import org.apache.camel.spi.annotations.PeriodicTask;
+import org.apache.camel.support.PatternHelper;
+import org.apache.camel.support.service.ServiceSupport;
+import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.vault.HashicorpVaultConfiguration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.vault.authentication.TokenAuthentication;
+import org.springframework.vault.client.VaultEndpoint;
+import org.springframework.vault.core.VaultTemplate;
+import org.springframework.vault.support.VaultResponse;
+
+/**
+ * Period task which checks if Hashicorp Vault secrets has been updated and can trigger Camel to be reloaded.
+ */
+@PeriodicTask("hashicorp-secret-refresh")
+public class HashicorpVaultReloadTriggerTask extends ServiceSupport implements CamelContextAware, Runnable {
+
+ private static final String CAMEL_HASHICORP_VAULT_TOKEN_ENV = "CAMEL_HASHICORP_VAULT_TOKEN";
+ private static final String CAMEL_HASHICORP_VAULT_HOST_ENV = "CAMEL_HASHICORP_VAULT_HOST";
+ private static final String CAMEL_HASHICORP_VAULT_PORT_ENV = "CAMEL_HASHICORP_VAULT_PORT";
+ private static final String CAMEL_HASHICORP_VAULT_SCHEME_ENV = "CAMEL_HASHICORP_VAULT_SCHEME";
+ private static final String CAMEL_HASHICORP_VAULT_CLOUD_ENV = "CAMEL_HASHICORP_VAULT_CLOUD";
+ private static final String CAMEL_HASHICORP_VAULT_NAMESPACE_ENV = "CAMEL_HASHICORP_VAULT_NAMESPACE";
+
+ private static final Logger LOG = LoggerFactory.getLogger(HashicorpVaultReloadTriggerTask.class);
+
+ private CamelContext camelContext;
+ private boolean reloadEnabled = true;
+ private String secrets;
+ private VaultTemplate client;
+ private HashicorpVaultPropertiesFunction propertiesFunction;
+ private volatile Instant lastCheckTime;
+ private volatile Instant lastReloadTime;
+ private final Map<String, Instant> updates = new HashMap<>();
+ private final Map<String, Integer> versionsMap = new HashMap<>();
+ private boolean cloud;
+ private String namespace;
+
+ public HashicorpVaultReloadTriggerTask() {
+ }
+
+ @Override
+ public CamelContext getCamelContext() {
+ return camelContext;
+ }
+
+ @Override
+ public void setCamelContext(CamelContext camelContext) {
+ this.camelContext = camelContext;
+ }
+
+ public boolean isReloadEnabled() {
+ return reloadEnabled;
+ }
+
+ /**
+ * Whether Camel should be reloaded on Hashicorp Vault secret updated
+ */
+ public void setReloadEnabled(boolean reloadEnabled) {
+ this.reloadEnabled = reloadEnabled;
+ }
+
+ /**
+ * A map of the updated secrets with the latest updated time.
+ */
+ public Map<String, Instant> getUpdates() {
+ return Collections.unmodifiableMap(updates);
+ }
+
+ /**
+ * Last time this task checked Hashicorp Vault for updated secrets.
+ */
+ public Instant getLastCheckTime() {
+ return lastCheckTime;
+ }
+
+ /**
+ * Last time Hashicorp Vault secrets update triggered reload.
+ */
+ public Instant getLastReloadTime() {
+ return lastReloadTime;
+ }
+
+ @Override
+ protected void doStart() throws Exception {
+ super.doStart();
+
+ // auto-detect secrets in-use
+ PropertiesComponent pc = camelContext.getPropertiesComponent();
+ PropertiesFunction pf = pc.getPropertiesFunction("hashicorp");
+ if (pf instanceof HashicorpVaultPropertiesFunction) {
+ propertiesFunction = (HashicorpVaultPropertiesFunction) pf;
+ LOG.debug("Auto-detecting secrets from properties-function: {}", pf.getName());
+ }
+ // specific secrets
+ HashicorpVaultConfiguration hashicorpVaultConfiguration = getCamelContext().getVaultConfiguration().hashicorp();
+ secrets = hashicorpVaultConfiguration.getSecrets();
+ if (ObjectHelper.isEmpty(secrets) && propertiesFunction == null) {
+ throw new IllegalArgumentException("Secrets must be configured on Hashicorp vault configuration");
+ }
+
+ String token = System.getenv(CAMEL_HASHICORP_VAULT_TOKEN_ENV);
+ String host = System.getenv(CAMEL_HASHICORP_VAULT_HOST_ENV);
+ String port = System.getenv(CAMEL_HASHICORP_VAULT_PORT_ENV);
+ String scheme = System.getenv(CAMEL_HASHICORP_VAULT_SCHEME_ENV);
+ if (System.getenv(CAMEL_HASHICORP_VAULT_CLOUD_ENV) != null) {
+ cloud = Boolean.parseBoolean(System.getenv(CAMEL_HASHICORP_VAULT_CLOUD_ENV));
+ }
+ namespace = System.getenv(CAMEL_HASHICORP_VAULT_NAMESPACE_ENV);
+
+ if (ObjectHelper.isEmpty(token) && ObjectHelper.isEmpty(host)
+ && ObjectHelper.isEmpty(port) && ObjectHelper.isEmpty(scheme) && ObjectHelper.isEmpty(namespace)) {
+ if (ObjectHelper.isNotEmpty(hashicorpVaultConfiguration)) {
+ token = hashicorpVaultConfiguration.getToken();
+ host = hashicorpVaultConfiguration.getHost();
+ port = hashicorpVaultConfiguration.getPort();
+ scheme = hashicorpVaultConfiguration.getScheme();
+ cloud = hashicorpVaultConfiguration.isCloud();
+ if (hashicorpVaultConfiguration.isCloud()) {
+ namespace = hashicorpVaultConfiguration.getNamespace();
+ }
+ }
+ }
+
+ if (ObjectHelper.isNotEmpty(token) && ObjectHelper.isNotEmpty(host)
+ && ObjectHelper.isNotEmpty(port) && ObjectHelper.isNotEmpty(scheme)) {
+ VaultEndpoint vaultEndpoint = new VaultEndpoint();
+ vaultEndpoint.setHost(host);
+ vaultEndpoint.setPort(Integer.parseInt(port));
+ vaultEndpoint.setScheme(scheme);
+
+ client = new VaultTemplate(
+ vaultEndpoint,
+ new TokenAuthentication(token));
+ } else {
+ throw new RuntimeCamelException(
+ "Using the Hashicorp Vault Secrets Refresh Task requires setting Token, Host, port and scheme properties");
+ }
+ }
+
+ @Override
+ protected void doShutdown() throws Exception {
+ super.doShutdown();
+
+ client = null;
+ updates.clear();
+ versionsMap.clear();
+ }
+
+ @Override
+ public void run() {
+ lastCheckTime = Instant.now();
+ boolean triggerReloading = false;
+
+ try {
+ // Get set of secrets to check
+ Set<String> secretsToCheck = new HashSet<>();
+ if (secrets != null) {
+ Collections.addAll(secretsToCheck, secrets.split(","));
+ }
+ if (propertiesFunction != null) {
+ secretsToCheck.addAll(propertiesFunction.getSecrets());
+ }
+
+ // Check each secret for updates
+ for (String secretName : secretsToCheck) {
+ if (matchSecret(secretName)) {
+ try {
+ // Query metadata endpoint to get current version
+ String metadataPath = buildMetadataPath(secretName);
+ VaultResponse response = client.read(metadataPath);
+
+ if (response != null && response.getData() != null) {
+ Object currentVersionObj = response.getData().get("current_version");
+ if (currentVersionObj != null) {
+ Integer currentVersion = Integer.valueOf(currentVersionObj.toString());
+ Integer lastKnownVersion = versionsMap.get(secretName);
+
+ if (lastKnownVersion == null) {
+ // First time seeing this secret, just record the version
+ versionsMap.put(secretName, currentVersion);
+ LOG.debug("Tracking secret {} at version {}", secretName, currentVersion);
+ } else if (!currentVersion.equals(lastKnownVersion)) {
+ // Version changed, trigger reload
+ versionsMap.put(secretName, currentVersion);
+ updates.put(secretName, Instant.now());
+ if (isReloadEnabled()) {
+ LOG.info(
+ "Update for Hashicorp Vault secret: {} detected (version {} -> {}), triggering CamelContext reload",
+ secretName, lastKnownVersion, currentVersion);
+ triggerReloading = true;
+ }
+ }
+ }
+ }
+ } catch (Exception e) {
+ LOG.warn("Error checking secret {} for updates: {}. Will retry on next run.",
+ secretName, e.getMessage());
+ LOG.debug("Exception details:", e);
+ }
+ }
+ }
+ } catch (Exception e) {
+ LOG.warn(
+ "Error during Hashicorp Vault Secrets Refresh Task due to {}. This exception is ignored. Will try again on next run.",
+ e.getMessage(), e);
+ }
+
+ if (triggerReloading) {
+ ContextReloadStrategy reload = camelContext.hasService(ContextReloadStrategy.class);
+ if (reload != null) {
+ // trigger reload
+ lastReloadTime = Instant.now();
+ reload.onReload(this);
+ }
+ }
+ }
+
+ protected boolean matchSecret(String name) {
+ Set<String> set = new HashSet<>();
+ if (secrets != null) {
+ Collections.addAll(set, secrets.split(","));
+ }
+ if (propertiesFunction != null) {
+ set.addAll(propertiesFunction.getSecrets());
+ }
+
+ for (String part : set) {
+ boolean result = name.contains(part) || PatternHelper.matchPattern(name, part);
+ LOG.trace("Matching secret id: {}={} -> {}", name, part, result);
+ if (result) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private String buildMetadataPath(String secretName) {
+ // Extract engine and secret name from the secret identifier
+ // Format can be "engine:secretname" or just "secretname" (defaults to "secret" engine)
+ // Use ':' as separator since secret names can contain '/'
+ String engine = "secret"; // default engine
+ String actualSecretName = secretName;
+
+ // Check if secretName contains ':' which separates engine from secret path
+ if (secretName.contains(":")) {
+ int colonIndex = secretName.indexOf(':');
+ engine = secretName.substring(0, colonIndex);
+ actualSecretName = secretName.substring(colonIndex + 1);
+ }
+
+ String metadataPath;
+ if (!cloud) {
+ metadataPath = engine + "/" + "metadata" + "/" + actualSecretName;
+ } else {
+ if (ObjectHelper.isNotEmpty(namespace)) {
+ metadataPath = namespace + "/" + engine + "/" + "metadata" + "/" + actualSecretName;
+ } else {
+ metadataPath = engine + "/" + "metadata" + "/" + actualSecretName;
+ }
+ }
+ return metadataPath;
+ }
+
+ @Override
+ public String toString() {
+ return "Hashicorp Vault Secrets Refresh Task";
+ }
+}
diff --git a/core/camel-api/src/main/java/org/apache/camel/vault/HashicorpVaultConfiguration.java b/core/camel-api/src/main/java/org/apache/camel/vault/HashicorpVaultConfiguration.java
index 26fa28aad5cc..0d081e034a00 100644
--- a/core/camel-api/src/main/java/org/apache/camel/vault/HashicorpVaultConfiguration.java
+++ b/core/camel-api/src/main/java/org/apache/camel/vault/HashicorpVaultConfiguration.java
@@ -35,6 +35,12 @@ public class HashicorpVaultConfiguration extends VaultConfiguration {
private boolean cloud;
@Metadata
private String namespace;
+ @Metadata
+ private boolean refreshEnabled;
+ @Metadata(defaultValue = "60000")
+ private long refreshPeriod = 60000;
+ @Metadata
+ private String secrets;
public String getToken() {
return token;
@@ -101,4 +107,37 @@ public class HashicorpVaultConfiguration extends VaultConfiguration {
public void setNamespace(String namespace) {
this.namespace = namespace;
}
+
+ public boolean isRefreshEnabled() {
+ return refreshEnabled;
+ }
+
+ /**
+ * Whether to automatically reload Camel upon secrets being updated in Hashicorp Vault.
+ */
+ public void setRefreshEnabled(boolean refreshEnabled) {
+ this.refreshEnabled = refreshEnabled;
+ }
+
+ public long getRefreshPeriod() {
+ return refreshPeriod;
+ }
+
+ /**
+ * The period (millis) between checking Hashicorp Vault for updated secrets.
+ */
+ public void setRefreshPeriod(long refreshPeriod) {
+ this.refreshPeriod = refreshPeriod;
+ }
+
+ public String getSecrets() {
+ return secrets;
+ }
+
+ /**
+ * Specify the secret names (or pattern) to check for updates. Multiple secrets can be separated by comma.
+ */
+ public void setSecrets(String secrets) {
+ this.secrets = secrets;
+ }
}
diff --git a/core/camel-main/src/generated/java/org/apache/camel/main/HashicorpVaultConfigurationConfigurer.java b/core/camel-main/src/generated/java/org/apache/camel/main/HashicorpVaultConfigurationConfigurer.java
index 1253d894fbd2..f411e923a635 100644
--- a/core/camel-main/src/generated/java/org/apache/camel/main/HashicorpVaultConfigurationConfigurer.java
+++ b/core/camel-main/src/generated/java/org/apache/camel/main/HashicorpVaultConfigurationConfigurer.java
@@ -43,7 +43,12 @@ public class HashicorpVaultConfigurationConfigurer extends org.apache.camel.supp
case "kubernetesVaultConfiguration": target.setKubernetesVaultConfiguration(property(camelContext, org.apache.camel.vault.KubernetesVaultConfiguration.class, value)); return true;
case "namespace": target.setNamespace(property(camelContext, java.lang.String.class, value)); return true;
case "port": target.setPort(property(camelContext, java.lang.String.class, value)); return true;
+ case "refreshenabled":
+ case "refreshEnabled": target.setRefreshEnabled(property(camelContext, boolean.class, value)); return true;
+ case "refreshperiod":
+ case "refreshPeriod": target.setRefreshPeriod(property(camelContext, long.class, value)); return true;
case "scheme": target.setScheme(property(camelContext, java.lang.String.class, value)); return true;
+ case "secrets": target.setSecrets(property(camelContext, java.lang.String.class, value)); return true;
case "springcloudconfigconfiguration":
case "springCloudConfigConfiguration": target.setSpringCloudConfigConfiguration(property(camelContext, org.apache.camel.vault.SpringCloudConfigConfiguration.class, value)); return true;
case "token": target.setToken(property(camelContext, java.lang.String.class, value)); return true;
@@ -74,7 +79,12 @@ public class HashicorpVaultConfigurationConfigurer extends org.apache.camel.supp
case "kubernetesVaultConfiguration": return org.apache.camel.vault.KubernetesVaultConfiguration.class;
case "namespace": return java.lang.String.class;
case "port": return java.lang.String.class;
+ case "refreshenabled":
+ case "refreshEnabled": return boolean.class;
+ case "refreshperiod":
+ case "refreshPeriod": return long.class;
case "scheme": return java.lang.String.class;
+ case "secrets": return java.lang.String.class;
case "springcloudconfigconfiguration":
case "springCloudConfigConfiguration": return org.apache.camel.vault.SpringCloudConfigConfiguration.class;
case "token": return java.lang.String.class;
@@ -106,7 +116,12 @@ public class HashicorpVaultConfigurationConfigurer extends org.apache.camel.supp
case "kubernetesVaultConfiguration": return target.getKubernetesVaultConfiguration();
case "namespace": return target.getNamespace();
case "port": return target.getPort();
+ case "refreshenabled":
+ case "refreshEnabled": return target.isRefreshEnabled();
+ case "refreshperiod":
+ case "refreshPeriod": return target.getRefreshPeriod();
case "scheme": return target.getScheme();
+ case "secrets": return target.getSecrets();
case "springcloudconfigconfiguration":
case "springCloudConfigConfiguration": return target.getSpringCloudConfigConfiguration();
case "token": return target.getToken();
diff --git a/core/camel-main/src/generated/java/org/apache/camel/main/HashicorpVaultConfigurationPropertiesConfigurer.java b/core/camel-main/src/generated/java/org/apache/camel/main/HashicorpVaultConfigurationPropertiesConfigurer.java
index 300340992595..02d874cea74c 100644
--- a/core/camel-main/src/generated/java/org/apache/camel/main/HashicorpVaultConfigurationPropertiesConfigurer.java
+++ b/core/camel-main/src/generated/java/org/apache/camel/main/HashicorpVaultConfigurationPropertiesConfigurer.java
@@ -34,7 +34,10 @@ public class HashicorpVaultConfigurationPropertiesConfigurer extends org.apache.
map.put("KubernetesVaultConfiguration", org.apache.camel.vault.KubernetesVaultConfiguration.class);
map.put("Namespace", java.lang.String.class);
map.put("Port", java.lang.String.class);
+ map.put("RefreshEnabled", boolean.class);
+ map.put("RefreshPeriod", long.class);
map.put("Scheme", java.lang.String.class);
+ map.put("Secrets", java.lang.String.class);
map.put("SpringCloudConfigConfiguration", org.apache.camel.vault.SpringCloudConfigConfiguration.class);
map.put("Token", java.lang.String.class);
ALL_OPTIONS = map;
@@ -64,7 +67,12 @@ public class HashicorpVaultConfigurationPropertiesConfigurer extends org.apache.
case "kubernetesVaultConfiguration": target.setKubernetesVaultConfiguration(property(camelContext, org.apache.camel.vault.KubernetesVaultConfiguration.class, value)); return true;
case "namespace": target.setNamespace(property(camelContext, java.lang.String.class, value)); return true;
case "port": target.setPort(property(camelContext, java.lang.String.class, value)); return true;
+ case "refreshenabled":
+ case "refreshEnabled": target.setRefreshEnabled(property(camelContext, boolean.class, value)); return true;
+ case "refreshperiod":
+ case "refreshPeriod": target.setRefreshPeriod(property(camelContext, long.class, value)); return true;
case "scheme": target.setScheme(property(camelContext, java.lang.String.class, value)); return true;
+ case "secrets": target.setSecrets(property(camelContext, java.lang.String.class, value)); return true;
case "springcloudconfigconfiguration":
case "springCloudConfigConfiguration": target.setSpringCloudConfigConfiguration(property(camelContext, org.apache.camel.vault.SpringCloudConfigConfiguration.class, value)); return true;
case "token": target.setToken(property(camelContext, java.lang.String.class, value)); return true;
@@ -100,7 +108,12 @@ public class HashicorpVaultConfigurationPropertiesConfigurer extends org.apache.
case "kubernetesVaultConfiguration": return org.apache.camel.vault.KubernetesVaultConfiguration.class;
case "namespace": return java.lang.String.class;
case "port": return java.lang.String.class;
+ case "refreshenabled":
+ case "refreshEnabled": return boolean.class;
+ case "refreshperiod":
+ case "refreshPeriod": return long.class;
case "scheme": return java.lang.String.class;
+ case "secrets": return java.lang.String.class;
case "springcloudconfigconfiguration":
case "springCloudConfigConfiguration": return org.apache.camel.vault.SpringCloudConfigConfiguration.class;
case "token": return java.lang.String.class;
@@ -132,7 +145,12 @@ public class HashicorpVaultConfigurationPropertiesConfigurer extends org.apache.
case "kubernetesVaultConfiguration": return target.getKubernetesVaultConfiguration();
case "namespace": return target.getNamespace();
case "port": return target.getPort();
+ case "refreshenabled":
+ case "refreshEnabled": return target.isRefreshEnabled();
+ case "refreshperiod":
+ case "refreshPeriod": return target.getRefreshPeriod();
case "scheme": return target.getScheme();
+ case "secrets": return target.getSecrets();
case "springcloudconfigconfiguration":
case "springCloudConfigConfiguration": return target.getSpringCloudConfigConfiguration();
case "token": return target.getToken();
diff --git a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
index 9da54a95bd7f..07521281b375 100644
--- a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
+++ b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
@@ -425,7 +425,10 @@
{ "name": "camel.vault.hashicorp.host", "required": false, "description": "Host to access hashicorp vault", "sourceType": "org.apache.camel.vault.HashicorpVaultConfiguration", "type": "string", "javaType": "java.lang.String", "secret": false },
{ "name": "camel.vault.hashicorp.namespace", "required": false, "description": "If the Hashicorp Vault instance is deployed on Hashicorp Cloud, this field will determine the namespace", "sourceType": "org.apache.camel.vault.HashicorpVaultConfiguration", "type": "string", "javaType": "java.lang.String", "secret": false },
{ "name": "camel.vault.hashicorp.port", "required": false, "description": "Port to access hashicorp vault", "sourceType": "org.apache.camel.vault.HashicorpVaultConfiguration", "type": "string", "javaType": "java.lang.String", "secret": false },
+ { "name": "camel.vault.hashicorp.refreshEnabled", "required": false, "description": "Whether to automatically reload Camel upon secrets being updated in Hashicorp Vault.", "sourceType": "org.apache.camel.vault.HashicorpVaultConfiguration", "type": "boolean", "javaType": "boolean", "defaultValue": false, "secret": false },
+ { "name": "camel.vault.hashicorp.refreshPeriod", "required": false, "description": "The period (millis) between checking Hashicorp Vault for updated secrets.", "sourceType": "org.apache.camel.vault.HashicorpVaultConfiguration", "type": "integer", "javaType": "long", "defaultValue": 60000, "secret": false },
{ "name": "camel.vault.hashicorp.scheme", "required": false, "description": "Scheme to access hashicorp vault", "sourceType": "org.apache.camel.vault.HashicorpVaultConfiguration", "type": "string", "javaType": "java.lang.String", "secret": false },
+ { "name": "camel.vault.hashicorp.secrets", "required": false, "description": "Specify the secret names (or pattern) to check for updates. Multiple secrets can be separated by comma.", "sourceType": "org.apache.camel.vault.HashicorpVaultConfiguration", "type": "string", "javaType": "java.lang.String", "secret": false },
{ "name": "camel.vault.hashicorp.token", "required": false, "description": "Token to access hashicorp vault", "sourceType": "org.apache.camel.vault.HashicorpVaultConfiguration", "type": "string", "javaType": "java.lang.String", "secret": true },
{ "name": "camel.vault.ibm.eventStreamBootstrapServers", "required": false, "description": "Specify the Bootstrap servers for consuming notification on IBM Event Stream. Multiple servers can be separated by comma.", "sourceType": "org.apache.camel.vault.IBMSecretsManagerVaultConfiguration", "type": "string", "javaType": "java.lang.String", "secret": false },
{ "name": "camel.vault.ibm.eventStreamConsumerPollTimeout", "required": false, "description": "Specify the Consumer Poll Timeout while consuming from IBM Event Stream Topic", "sourceType": "org.apache.camel.vault.IBMSecretsManagerVaultConfiguration", "type": "integer", "javaType": "long", "defaultValue": 3000, "secret": false },
diff --git a/core/camel-main/src/main/docs/main.adoc b/core/camel-main/src/main/docs/main.adoc
index 3baca5cebc5e..cceb9356acb8 100644
--- a/core/camel-main/src/main/docs/main.adoc
+++ b/core/camel-main/src/main/docs/main.adoc
@@ -483,7 +483,7 @@ The camel.vault.kubernetescm supports 2 options, which are listed below.
=== Camel Hashicorp Vault configurations
-The camel.vault.hashicorp supports 6 options, which are listed below.
+The camel.vault.hashicorp supports 9 options, which are listed below.
[width="100%",cols="2,5,^1,2",options="header"]
|===
@@ -492,7 +492,10 @@ The camel.vault.hashicorp supports 6 options, which are listed below.
| *camel.vault.hashicorp.host* | Host to access hashicorp vault | | String
| *camel.vault.hashicorp.namespace* | If the Hashicorp Vault instance is deployed on Hashicorp Cloud, this field will determine the namespace | | String
| *camel.vault.hashicorp.port* | Port to access hashicorp vault | | String
+| *camel.vault.hashicorp.refresh{zwsp}Enabled* | Whether to automatically reload Camel upon secrets being updated in Hashicorp Vault. | false | boolean
+| *camel.vault.hashicorp.refresh{zwsp}Period* | The period (millis) between checking Hashicorp Vault for updated secrets. | 60000 | long
| *camel.vault.hashicorp.scheme* | Scheme to access hashicorp vault | | String
+| *camel.vault.hashicorp.secrets* | Specify the secret names (or pattern) to check for updates. Multiple secrets can be separated by comma. | | String
| *camel.vault.hashicorp.token* | Token to access hashicorp vault | | String
|===
diff --git a/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationConfigurer.java b/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationConfigurer.java
index 63bb32b3f063..311b7dfedbff 100644
--- a/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationConfigurer.java
+++ b/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationConfigurer.java
@@ -860,6 +860,25 @@ public final class DefaultConfigurationConfigurer {
}
}
+ if (vc.hashicorp().isRefreshEnabled()) {
+ Optional<Runnable> task = PluginHelper.getPeriodTaskResolver(camelContext)
+ .newInstance("hashicorp-secret-refresh", Runnable.class);
+ if (task.isPresent()) {
+ long period = vc.hashicorp().getRefreshPeriod();
+ Runnable r = task.get();
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Scheduling: {} (period: {})", r, TimeUtils.printDuration(period, false));
+ }
+ if (camelContext.hasService(ContextReloadStrategy.class) == null) {
+ // refresh is enabled then we need to automatically enable context-reload as well
+ ContextReloadStrategy reloader = new DefaultContextReloadStrategy();
+ camelContext.addService(reloader);
+ }
+ PeriodTaskScheduler scheduler = PluginHelper.getPeriodTaskScheduler(camelContext);
+ scheduler.schedulePeriodTask(r, period);
+ }
+ }
+
if (vc.springConfig().isRefreshEnabled()) {
Optional<Runnable> task = PluginHelper.getPeriodTaskResolver(camelContext)
.newInstance("spring-config-refresh", Runnable.class);
diff --git a/core/camel-main/src/main/java/org/apache/camel/main/HashicorpVaultConfigurationProperties.java b/core/camel-main/src/main/java/org/apache/camel/main/HashicorpVaultConfigurationProperties.java
index d11827dc915b..4f5a4cc15ece 100644
--- a/core/camel-main/src/main/java/org/apache/camel/main/HashicorpVaultConfigurationProperties.java
+++ b/core/camel-main/src/main/java/org/apache/camel/main/HashicorpVaultConfigurationProperties.java
@@ -97,4 +97,28 @@ public class HashicorpVaultConfigurationProperties extends HashicorpVaultConfigu
return this;
}
+ /**
+ * Whether to automatically reload Camel upon secrets being updated in Hashicorp Vault
+ */
+ public HashicorpVaultConfigurationProperties withRefreshEnabled(boolean refreshEnabled) {
+ setRefreshEnabled(refreshEnabled);
+ return this;
+ }
+
+ /**
+ * The period (millis) between checking Hashicorp Vault for updated secrets
+ */
+ public HashicorpVaultConfigurationProperties withRefreshPeriod(long refreshPeriod) {
+ setRefreshPeriod(refreshPeriod);
+ return this;
+ }
+
+ /**
+ * Specify the secret names (or pattern) to check for updates. Multiple secrets can be separated by comma
+ */
+ public HashicorpVaultConfigurationProperties withSecrets(String secrets) {
+ setSecrets(secrets);
+ return this;
+ }
+
}