This is an automated email from the ASF dual-hosted git repository.

acosentino pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new ed0548278c45 CAMEL-22676 - Camel-Hashicorp-Vault: Support Secret 
Refresh (#19868)
ed0548278c45 is described below

commit ed0548278c45f7c581a182c5bcfbd0acf4bee248
Author: Andrea Cosentino <[email protected]>
AuthorDate: Mon Nov 10 14:04:03 2025 +0100

    CAMEL-22676 - Camel-Hashicorp-Vault: Support Secret Refresh (#19868)
    
    * CAMEL-22676 - Camel-Hashicorp-Vault: Support Secret Refresh
    
    Signed-off-by: Andrea Cosentino <[email protected]>
    
    * CAMEL-22676 - Camel-Hashicorp-Vault: Support Secret Refresh
    
    Signed-off-by: Andrea Cosentino <[email protected]>
    
    * CAMEL-22676 - Camel-Hashicorp-Vault: Support Secret Refresh
    
    Signed-off-by: Andrea Cosentino <[email protected]>
    
    ---------
    
    Signed-off-by: Andrea Cosentino <[email protected]>
---
 .../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 ++
 docs/user-manual/modules/ROOT/pages/security.adoc  |  70 +++++
 13 files changed, 572 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..15d0bdfc3c31 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;
+    }
+
 }
diff --git a/docs/user-manual/modules/ROOT/pages/security.adoc 
b/docs/user-manual/modules/ROOT/pages/security.adoc
index 1fe9ba02cd65..e0444ec69420 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.


Reply via email to