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 <[email protected]> AuthorDate: Mon Nov 10 13:17:53 2025 +0100 CAMEL-22676 - Camel-Hashicorp-Vault: Support Secret Refresh 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 ++ 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; + } + }
