This is an automated email from the ASF dual-hosted git repository. Croway pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel-spring-boot.git
commit a2f7cbf0f46fa62e49ae339becc048b026e4de0b Author: Andrea Cosentino <[email protected]> AuthorDate: Wed Aug 26 10:21:12 2026 +0200 CAMEL-24508: vault and secrets starters - fail closed when early property resolution fails A placeholder that matched a vault prefix but could not be resolved was left in place, so the literal {{prefix:...}} text became the effective value of whatever it configured, with only a DEBUG line to say so. Resolution failures now abort startup with the property named. camel.vault.ignore-resolution-failures=true restores the previous tolerance and logs at WARN instead of DEBUG. Applied to the aws-secrets-manager, azure-key-vault, cyberark-vault, google-secret-manager, hashicorp-vault, ibm-secrets-manager and spring-cloud-config parsers. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Signed-off-by: Andrea Cosentino <[email protected]> --- ...pringBootAwsSecretsManagerPropertiesParser.java | 17 ++++- .../SpringBootAzureKeyVaultPropertiesParser.java | 17 ++++- .../SpringBootCyberArkVaultPropertiesParser.java | 17 ++++- ...ingBootGoogleSecretManagerPropertiesParser.java | 17 ++++- .../SpringBootHashicorpVaultPropertiesParser.java | 18 ++++- .../springboot/EarlyResolutionFailureTest.java | 81 ++++++++++++++++++++++ .../IBMSecretsManagerVaultPropertiesParser.java | 17 ++++- .../SpringBootCloudConfigPropertiesParser.java | 18 ++++- 8 files changed, 188 insertions(+), 14 deletions(-) diff --git a/components-starter/camel-aws-secrets-manager-starter/src/main/java/org/apache/camel/component/aws/secretsmanager/springboot/SpringBootAwsSecretsManagerPropertiesParser.java b/components-starter/camel-aws-secrets-manager-starter/src/main/java/org/apache/camel/component/aws/secretsmanager/springboot/SpringBootAwsSecretsManagerPropertiesParser.java index a9e4855f779..25b02114f79 100644 --- a/components-starter/camel-aws-secrets-manager-starter/src/main/java/org/apache/camel/component/aws/secretsmanager/springboot/SpringBootAwsSecretsManagerPropertiesParser.java +++ b/components-starter/camel-aws-secrets-manager-starter/src/main/java/org/apache/camel/component/aws/secretsmanager/springboot/SpringBootAwsSecretsManagerPropertiesParser.java @@ -44,6 +44,10 @@ public class SpringBootAwsSecretsManagerPropertiesParser implements ApplicationL public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { SecretsManagerClient client; ConfigurableEnvironment environment = event.getEnvironment(); + // an unresolved placeholder would otherwise stay in the property value and become the effective + // secret, so resolution failures abort startup unless the operator opts back into the old behaviour + final boolean ignoreResolutionFailures + = Boolean.parseBoolean(environment.getProperty("camel.vault.ignore-resolution-failures")); if (Boolean.parseBoolean(environment.getProperty("camel.component.aws-secrets-manager.early-resolve-properties"))) { String accessKey = environment.getProperty("camel.vault.aws.accessKey"); String secretKey = environment.getProperty("camel.vault.aws.secretKey"); @@ -94,8 +98,17 @@ public class SpringBootAwsSecretsManagerPropertiesParser implements ApplicationL .replace("}}", "")); props.put(key, element); } catch (Exception e) { - // Log and do nothing - LOG.debug("failed to parse property {}. This exception is ignored.", key, e); + if (ignoreResolutionFailures) { + LOG.warn("Failed to resolve property {} from the vault; the placeholder is left " + + "unresolved because camel.vault.ignore-resolution-failures is enabled", key, e); + } else { + throw new RuntimeCamelException( + "Failed to resolve property " + key + " from the vault. Startup is aborted so " + + "that the unresolved placeholder cannot become the effective " + + "value; set camel.vault.ignore-resolution-failures=true to " + + "continue anyway.", + e); + } } } }); diff --git a/components-starter/camel-azure-key-vault-starter/src/main/java/org/apache/camel/component/azure/key/vault/springboot/SpringBootAzureKeyVaultPropertiesParser.java b/components-starter/camel-azure-key-vault-starter/src/main/java/org/apache/camel/component/azure/key/vault/springboot/SpringBootAzureKeyVaultPropertiesParser.java index cc1614a04e6..f240c1cd013 100644 --- a/components-starter/camel-azure-key-vault-starter/src/main/java/org/apache/camel/component/azure/key/vault/springboot/SpringBootAzureKeyVaultPropertiesParser.java +++ b/components-starter/camel-azure-key-vault-starter/src/main/java/org/apache/camel/component/azure/key/vault/springboot/SpringBootAzureKeyVaultPropertiesParser.java @@ -44,6 +44,10 @@ public class SpringBootAzureKeyVaultPropertiesParser implements ApplicationListe public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { SecretClient client; ConfigurableEnvironment environment = event.getEnvironment(); + // an unresolved placeholder would otherwise stay in the property value and become the effective + // secret, so resolution failures abort startup unless the operator opts back into the old behaviour + final boolean ignoreResolutionFailures + = Boolean.parseBoolean(environment.getProperty("camel.vault.ignore-resolution-failures")); if (Boolean.parseBoolean(environment.getProperty("camel.component.azure-key-vault.early-resolve-properties"))) { String vaultName = environment.getProperty("camel.vault.azure.vaultName"); String clientId = environment.getProperty("camel.vault.azure.clientId"); @@ -103,8 +107,17 @@ public class SpringBootAzureKeyVaultPropertiesParser implements ApplicationListe .replace("}}", "")); props.put(key, element); } catch (Exception e) { - // Log and do nothing - LOG.debug("failed to parse property {}. This exception is ignored.", key, e); + if (ignoreResolutionFailures) { + LOG.warn("Failed to resolve property {} from the vault; the placeholder is left " + + "unresolved because camel.vault.ignore-resolution-failures is enabled", key, e); + } else { + throw new RuntimeCamelException( + "Failed to resolve property " + key + " from the vault. Startup is aborted so " + + "that the unresolved placeholder cannot become the effective " + + "value; set camel.vault.ignore-resolution-failures=true to " + + "continue anyway.", + e); + } } } }); diff --git a/components-starter/camel-cyberark-vault-starter/src/main/java/org/apache/camel/component/cyberark/vault/springboot/SpringBootCyberArkVaultPropertiesParser.java b/components-starter/camel-cyberark-vault-starter/src/main/java/org/apache/camel/component/cyberark/vault/springboot/SpringBootCyberArkVaultPropertiesParser.java index c6be6f787f4..4242da15ea2 100644 --- a/components-starter/camel-cyberark-vault-starter/src/main/java/org/apache/camel/component/cyberark/vault/springboot/SpringBootCyberArkVaultPropertiesParser.java +++ b/components-starter/camel-cyberark-vault-starter/src/main/java/org/apache/camel/component/cyberark/vault/springboot/SpringBootCyberArkVaultPropertiesParser.java @@ -40,6 +40,10 @@ public class SpringBootCyberArkVaultPropertiesParser implements ApplicationListe public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { ConjurClient client; ConfigurableEnvironment environment = event.getEnvironment(); + // an unresolved placeholder would otherwise stay in the property value and become the effective + // secret, so resolution failures abort startup unless the operator opts back into the old behaviour + final boolean ignoreResolutionFailures + = Boolean.parseBoolean(environment.getProperty("camel.vault.ignore-resolution-failures")); if (Boolean.parseBoolean(environment.getProperty("camel.component.cyberark-vault.early-resolve-properties"))) { String url = environment.getProperty("camel.vault.cyberark.url"); String account = environment.getProperty("camel.vault.cyberark.account"); @@ -92,8 +96,17 @@ public class SpringBootCyberArkVaultPropertiesParser implements ApplicationListe .replace("}}", "")); props.put(key, element); } catch (Exception e) { - // Log and do nothing - LOG.debug("failed to parse property {}. This exception is ignored.", key, e); + if (ignoreResolutionFailures) { + LOG.warn("Failed to resolve property {} from the vault; the placeholder is left " + + "unresolved because camel.vault.ignore-resolution-failures is enabled", key, e); + } else { + throw new RuntimeCamelException( + "Failed to resolve property " + key + " from the vault. Startup is aborted so " + + "that the unresolved placeholder cannot become the effective " + + "value; set camel.vault.ignore-resolution-failures=true to " + + "continue anyway.", + e); + } } } }); diff --git a/components-starter/camel-google-secret-manager-starter/src/main/java/org/apache/camel/component/google/secret/manager/springboot/SpringBootGoogleSecretManagerPropertiesParser.java b/components-starter/camel-google-secret-manager-starter/src/main/java/org/apache/camel/component/google/secret/manager/springboot/SpringBootGoogleSecretManagerPropertiesParser.java index 979ce6087cf..696c9c4d0de 100644 --- a/components-starter/camel-google-secret-manager-starter/src/main/java/org/apache/camel/component/google/secret/manager/springboot/SpringBootGoogleSecretManagerPropertiesParser.java +++ b/components-starter/camel-google-secret-manager-starter/src/main/java/org/apache/camel/component/google/secret/manager/springboot/SpringBootGoogleSecretManagerPropertiesParser.java @@ -41,6 +41,10 @@ public class SpringBootGoogleSecretManagerPropertiesParser implements Applicatio public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { SecretManagerServiceClient client; ConfigurableEnvironment environment = event.getEnvironment(); + // an unresolved placeholder would otherwise stay in the property value and become the effective + // secret, so resolution failures abort startup unless the operator opts back into the old behaviour + final boolean ignoreResolutionFailures + = Boolean.parseBoolean(environment.getProperty("camel.vault.ignore-resolution-failures")); String projectId; if (Boolean.parseBoolean(environment.getProperty("camel.component.google-secret-manager.early-resolve-properties"))) { projectId = environment.getProperty("camel.vault.gcp.projectId"); @@ -79,8 +83,17 @@ public class SpringBootGoogleSecretManagerPropertiesParser implements Applicatio .replace("}}", "")); props.put(key, element); } catch (Exception e) { - // Log and do nothing - LOG.debug("failed to parse property {}. This exception is ignored.", key, e); + if (ignoreResolutionFailures) { + LOG.warn("Failed to resolve property {} from the vault; the placeholder is left " + + "unresolved because camel.vault.ignore-resolution-failures is enabled", key, e); + } else { + throw new RuntimeCamelException( + "Failed to resolve property " + key + " from the vault. Startup is aborted so " + + "that the unresolved placeholder cannot become the effective " + + "value; set camel.vault.ignore-resolution-failures=true to " + + "continue anyway.", + e); + } } } }); diff --git a/components-starter/camel-hashicorp-vault-starter/src/main/java/org/apache/camel/component/hashicorp/vault/springboot/SpringBootHashicorpVaultPropertiesParser.java b/components-starter/camel-hashicorp-vault-starter/src/main/java/org/apache/camel/component/hashicorp/vault/springboot/SpringBootHashicorpVaultPropertiesParser.java index e3b2fdc1829..eb930f40d1f 100644 --- a/components-starter/camel-hashicorp-vault-starter/src/main/java/org/apache/camel/component/hashicorp/vault/springboot/SpringBootHashicorpVaultPropertiesParser.java +++ b/components-starter/camel-hashicorp-vault-starter/src/main/java/org/apache/camel/component/hashicorp/vault/springboot/SpringBootHashicorpVaultPropertiesParser.java @@ -16,6 +16,7 @@ */ package org.apache.camel.component.hashicorp.vault.springboot; +import org.apache.camel.RuntimeCamelException; import org.apache.camel.component.hashicorp.vault.HashicorpVaultPropertiesFunction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,6 +40,10 @@ public class SpringBootHashicorpVaultPropertiesParser implements ApplicationList @Override public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { ConfigurableEnvironment environment = event.getEnvironment(); + // an unresolved placeholder would otherwise stay in the property value and become the effective + // secret, so resolution failures abort startup unless the operator opts back into the old behaviour + final boolean ignoreResolutionFailures + = Boolean.parseBoolean(environment.getProperty("camel.vault.ignore-resolution-failures")); if (Boolean.parseBoolean(environment.getProperty("camel.component.hashicorp-vault.early-resolve-properties"))) { Objects.requireNonNull(environment.getProperty("camel.vault.hashicorp.token"), "Hashicorp Vault token is required"); Objects.requireNonNull(environment.getProperty("camel.vault.hashicorp.host"), "Hashicorp Vault host is required"); @@ -82,8 +87,17 @@ public class SpringBootHashicorpVaultPropertiesParser implements ApplicationList .replace("{{hashicorp:", "") .replace("}}", ""))); } catch (Exception e) { - // Log and do nothing - LOG.debug("failed to parse property {}. This exception is ignored.", key, e); + if (ignoreResolutionFailures) { + LOG.warn("Failed to resolve property {} from the vault; the placeholder is left " + + "unresolved because camel.vault.ignore-resolution-failures is enabled", key, e); + } else { + throw new RuntimeCamelException( + "Failed to resolve property " + key + " from the vault. Startup is aborted so " + + "that the unresolved placeholder cannot become the effective " + + "value; set camel.vault.ignore-resolution-failures=true to " + + "continue anyway.", + e); + } } } }); diff --git a/components-starter/camel-hashicorp-vault-starter/src/test/java/org/apache/camel/component/hashicorp/vault/springboot/EarlyResolutionFailureTest.java b/components-starter/camel-hashicorp-vault-starter/src/test/java/org/apache/camel/component/hashicorp/vault/springboot/EarlyResolutionFailureTest.java new file mode 100644 index 00000000000..3ebb71d79ad --- /dev/null +++ b/components-starter/camel-hashicorp-vault-starter/src/test/java/org/apache/camel/component/hashicorp/vault/springboot/EarlyResolutionFailureTest.java @@ -0,0 +1,81 @@ +/* + * 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.springboot; + +import org.apache.camel.RuntimeCamelException; +import org.junit.jupiter.api.Test; +import org.springframework.boot.bootstrap.DefaultBootstrapContext; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; +import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.StandardEnvironment; + +import java.util.LinkedHashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * A placeholder that matched the vault prefix but could not be resolved must not be left in place, since the literal + * placeholder text would then become the effective value of whatever it configures. + */ +public class EarlyResolutionFailureTest { + + private static final String SECRET_KEY = "my.secret"; + + private static ApplicationEnvironmentPreparedEvent eventWith(boolean ignoreResolutionFailures) { + Map<String, Object> props = new LinkedHashMap<>(); + props.put("camel.component.hashicorp-vault.early-resolve-properties", "true"); + props.put("camel.vault.hashicorp.token", "not-a-real-token"); + props.put("camel.vault.hashicorp.host", "127.0.0.1"); + // nothing listens here, so the lookup fails locally without touching the network + props.put("camel.vault.hashicorp.port", "1"); + props.put("camel.vault.hashicorp.scheme", "http"); + props.put(SECRET_KEY, "{{hashicorp:secret:does/not/exist#value}}"); + if (ignoreResolutionFailures) { + props.put("camel.vault.ignore-resolution-failures", "true"); + } + + StandardEnvironment environment = new StandardEnvironment(); + environment.getPropertySources().addFirst(new MapPropertySource("test-properties", props)); + + return new ApplicationEnvironmentPreparedEvent( + new DefaultBootstrapContext(), new SpringApplication(), new String[0], environment); + } + + @Test + public void unresolvableSecretAbortsStartup() { + SpringBootHashicorpVaultPropertiesParser parser = new SpringBootHashicorpVaultPropertiesParser(); + + RuntimeCamelException thrown = assertThrows(RuntimeCamelException.class, + () -> parser.onApplicationEvent(eventWith(false))); + + assertTrue(thrown.getMessage().contains(SECRET_KEY), + "the failure should name the property that could not be resolved, was: " + thrown.getMessage()); + assertTrue(thrown.getMessage().contains("camel.vault.ignore-resolution-failures"), + "the failure should point at the opt-back property, was: " + thrown.getMessage()); + } + + @Test + public void ignoreResolutionFailuresRestoresTheTolerantBehaviour() { + SpringBootHashicorpVaultPropertiesParser parser = new SpringBootHashicorpVaultPropertiesParser(); + + assertDoesNotThrow(() -> parser.onApplicationEvent(eventWith(true))); + } +} diff --git a/components-starter/camel-ibm-secrets-manager-starter/src/main/java/org/apache/camel/component/ibm/secrets/manager/springboot/IBMSecretsManagerVaultPropertiesParser.java b/components-starter/camel-ibm-secrets-manager-starter/src/main/java/org/apache/camel/component/ibm/secrets/manager/springboot/IBMSecretsManagerVaultPropertiesParser.java index 5918c59dd12..5bca3dc9ef8 100644 --- a/components-starter/camel-ibm-secrets-manager-starter/src/main/java/org/apache/camel/component/ibm/secrets/manager/springboot/IBMSecretsManagerVaultPropertiesParser.java +++ b/components-starter/camel-ibm-secrets-manager-starter/src/main/java/org/apache/camel/component/ibm/secrets/manager/springboot/IBMSecretsManagerVaultPropertiesParser.java @@ -40,6 +40,10 @@ public class IBMSecretsManagerVaultPropertiesParser implements ApplicationListen public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { SecretsManager client; ConfigurableEnvironment environment = event.getEnvironment(); + // an unresolved placeholder would otherwise stay in the property value and become the effective + // secret, so resolution failures abort startup unless the operator opts back into the old behaviour + final boolean ignoreResolutionFailures + = Boolean.parseBoolean(environment.getProperty("camel.vault.ignore-resolution-failures")); String token; String serviceUrl; if (Boolean.parseBoolean(environment.getProperty("camel.component.ibm-secrets-manager.early-resolve-properties"))) { @@ -77,8 +81,17 @@ public class IBMSecretsManagerVaultPropertiesParser implements ApplicationListen .replace("}}", "")); props.put(key, element); } catch (Exception e) { - // Log and do nothing - LOG.debug("failed to parse property {}. This exception is ignored.", key, e); + if (ignoreResolutionFailures) { + LOG.warn("Failed to resolve property {} from the vault; the placeholder is left " + + "unresolved because camel.vault.ignore-resolution-failures is enabled", key, e); + } else { + throw new RuntimeCamelException( + "Failed to resolve property " + key + " from the vault. Startup is aborted so " + + "that the unresolved placeholder cannot become the effective " + + "value; set camel.vault.ignore-resolution-failures=true to " + + "continue anyway.", + e); + } } } }); diff --git a/components-starter/camel-spring-cloud-config-starter/src/main/java/org/apache/camel/component/spring/cloud/config/springboot/SpringBootCloudConfigPropertiesParser.java b/components-starter/camel-spring-cloud-config-starter/src/main/java/org/apache/camel/component/spring/cloud/config/springboot/SpringBootCloudConfigPropertiesParser.java index ef8957d1bf9..5886ef85201 100644 --- a/components-starter/camel-spring-cloud-config-starter/src/main/java/org/apache/camel/component/spring/cloud/config/springboot/SpringBootCloudConfigPropertiesParser.java +++ b/components-starter/camel-spring-cloud-config-starter/src/main/java/org/apache/camel/component/spring/cloud/config/springboot/SpringBootCloudConfigPropertiesParser.java @@ -16,6 +16,7 @@ */ package org.apache.camel.component.spring.cloud.config.springboot; +import org.apache.camel.RuntimeCamelException; import org.apache.camel.component.spring.cloud.config.SpringCloudConfigPropertiesFunction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -36,6 +37,10 @@ public class SpringBootCloudConfigPropertiesParser implements ApplicationListene public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { Properties properties = new Properties(); ConfigurableEnvironment environment = event.getEnvironment(); + // an unresolved placeholder would otherwise stay in the property value and become the effective + // secret, so resolution failures abort startup unless the operator opts back into the old behaviour + final boolean ignoreResolutionFailures + = Boolean.parseBoolean(environment.getProperty("camel.vault.ignore-resolution-failures")); if (Boolean.parseBoolean( environment.getProperty("camel.component.spring-cloud-config.early-resolve-properties"))) { @@ -59,8 +64,17 @@ public class SpringBootCloudConfigPropertiesParser implements ApplicationListene .apply(stringValue.replace("{{spring-config:", "").replace("}}", "")); properties.put(key, element); } catch (Exception e) { - // Log and do nothing - LOG.debug("failed to parse property {}. This exception is ignored.", key, e); + if (ignoreResolutionFailures) { + LOG.warn("Failed to resolve property {} from the vault; the placeholder is left " + + "unresolved because camel.vault.ignore-resolution-failures is enabled", key, e); + } else { + throw new RuntimeCamelException( + "Failed to resolve property " + key + " from the vault. Startup is aborted so " + + "that the unresolved placeholder cannot become the effective " + + "value; set camel.vault.ignore-resolution-failures=true to " + + "continue anyway.", + e); + } } } });
