This is an automated email from the ASF dual-hosted git repository. acosentino pushed a commit to branch CAMEL-21834 in repository https://gitbox.apache.org/repos/asf/camel-spring-boot.git
commit 9cdd0b00bab4ac85a9c55c5cf23da97686e7772a Author: Andrea Cosentino <anco...@gmail.com> AuthorDate: Thu Mar 6 14:38:58 2025 +0100 CAMEL-21834 - Camel-Spring-Boot: Create ApplicationEnvironmentPreparedEvent for Vault IBM Secrets Manager Signed-off-by: Andrea Cosentino <anco...@gmail.com> --- .../camel-ibm-secrets-manager-starter/pom.xml | 12 ++ .../IBMSecretsManagerVaultPropertiesParser.java | 74 ++++++++++++ .../src/main/resources/META-INF/spring.factories | 2 + .../src/test/java/EarlyResolvedPropertiesTest.java | 126 +++++++++++++++++++++ .../src/test/resources/application.properties | 2 + 5 files changed, 216 insertions(+) diff --git a/components-starter/camel-ibm-secrets-manager-starter/pom.xml b/components-starter/camel-ibm-secrets-manager-starter/pom.xml index 39d3174d7c1..50c85d550e5 100644 --- a/components-starter/camel-ibm-secrets-manager-starter/pom.xml +++ b/components-starter/camel-ibm-secrets-manager-starter/pom.xml @@ -38,6 +38,18 @@ <artifactId>camel-ibm-secrets-manager</artifactId> <version>${camel-version}</version> </dependency> + <!-- for testing --> + <dependency> + <groupId>org.awaitility</groupId> + <artifactId>awaitility</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-test</artifactId> + <version>${spring-boot-version}</version> + <scope>test</scope> + </dependency> <!--START OF GENERATED CODE--> <dependency> <groupId>org.apache.camel.springboot</groupId> 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 new file mode 100644 index 00000000000..82f42dcbcec --- /dev/null +++ b/components-starter/camel-ibm-secrets-manager-starter/src/main/java/org/apache/camel/component/ibm/secrets/manager/springboot/IBMSecretsManagerVaultPropertiesParser.java @@ -0,0 +1,74 @@ +package org.apache.camel.component.ibm.secrets.manager.springboot; + +import com.ibm.cloud.sdk.core.security.IamAuthenticator; +import com.ibm.cloud.secrets_manager_sdk.secrets_manager.v2.SecretsManager; +import org.apache.camel.RuntimeCamelException; +import org.apache.camel.component.ibm.secrets.manager.IBMSecretsManagerPropertiesFunction; +import org.apache.camel.util.ObjectHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; +import org.springframework.boot.origin.OriginTrackedValue; +import org.springframework.context.ApplicationListener; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.PropertiesPropertySource; +import org.springframework.core.env.PropertySource; + +import java.util.Properties; + +public class IBMSecretsManagerVaultPropertiesParser implements ApplicationListener<ApplicationEnvironmentPreparedEvent> { + private static final Logger LOG = LoggerFactory.getLogger(IBMSecretsManagerVaultPropertiesParser.class); + + @Override + public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { + SecretsManager client; + ConfigurableEnvironment environment = event.getEnvironment(); + String token; + String serviceUrl; + if (Boolean.parseBoolean(environment.getProperty("camel.component.ibm-secrets-manager.early-resolve-properties"))) { + token = environment.getProperty("camel.vault.ibm.token"); + serviceUrl = environment.getProperty("camel.vault.ibm.serviceUrl"); + if (ObjectHelper.isNotEmpty(token) && ObjectHelper.isNotEmpty(serviceUrl)) { + IamAuthenticator iamAuthenticator = new IamAuthenticator.Builder() + .apikey(token) + .build(); + client = new SecretsManager("Camel Secrets Manager Service for Properties", iamAuthenticator); + client.setServiceUrl(serviceUrl); + } else { + throw new RuntimeCamelException( + "Using the IBM Secrets Manager Properties Function requires setting IBM Credentials and service url as application properties or environment variables"); + } + IBMSecretsManagerPropertiesFunction secretsManagerPropertiesFunction = new IBMSecretsManagerPropertiesFunction(client); + final Properties props = new Properties(); + for (PropertySource mutablePropertySources : event.getEnvironment().getPropertySources()) { + if (mutablePropertySources instanceof MapPropertySource mapPropertySource) { + mapPropertySource.getSource().forEach((key, value) -> { + String stringValue = null; + if ((value instanceof OriginTrackedValue originTrackedValue && + originTrackedValue.getValue() instanceof String v)) { + stringValue = v; + } else if (value instanceof String v) { + stringValue = v; + } + if (stringValue != null && + stringValue.startsWith("{{ibm:") && + stringValue.endsWith("}}")) { + LOG.debug("decrypting and overriding property {}", key); + try { + String element = secretsManagerPropertiesFunction.apply(stringValue + .replace("{{ibm:", "") + .replace("}}", "")); + props.put(key, element); + } catch (Exception e) { + // Log and do nothing + LOG.debug("failed to parse property {}. This exception is ignored.", key, e); + } + } + }); + } + } + environment.getPropertySources().addFirst(new PropertiesPropertySource("overridden-ibm-secrets-manager-properties", props)); + } + } +} \ No newline at end of file diff --git a/components-starter/camel-ibm-secrets-manager-starter/src/main/resources/META-INF/spring.factories b/components-starter/camel-ibm-secrets-manager-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000000..0cd64be49cc --- /dev/null +++ b/components-starter/camel-ibm-secrets-manager-starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.context.ApplicationListener=\ + org.apache.camel.component.ibm.secrets.manager.springboot.IBMSecretsManagerVaultPropertiesParser \ No newline at end of file diff --git a/components-starter/camel-ibm-secrets-manager-starter/src/test/java/EarlyResolvedPropertiesTest.java b/components-starter/camel-ibm-secrets-manager-starter/src/test/java/EarlyResolvedPropertiesTest.java new file mode 100644 index 00000000000..9c30283ac5e --- /dev/null +++ b/components-starter/camel-ibm-secrets-manager-starter/src/test/java/EarlyResolvedPropertiesTest.java @@ -0,0 +1,126 @@ +/* + * 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.ibm.secrets.manager.springboot; + +import com.ibm.cloud.sdk.core.http.Response; +import com.ibm.cloud.sdk.core.security.IamAuthenticator; +import com.ibm.cloud.secrets_manager_sdk.secrets_manager.v2.SecretsManager; +import com.ibm.cloud.secrets_manager_sdk.secrets_manager.v2.model.CreateSecretOptions; +import com.ibm.cloud.secrets_manager_sdk.secrets_manager.v2.model.DeleteSecretOptions; +import com.ibm.cloud.secrets_manager_sdk.secrets_manager.v2.model.KVSecretPrototype; +import com.ibm.cloud.secrets_manager_sdk.secrets_manager.v2.model.Secret; +import org.apache.camel.component.ibm.secrets.manager.IBMSecretsManagerConstants; +import org.apache.camel.spring.boot.CamelAutoConfiguration; +import org.apache.camel.test.spring.junit5.CamelSpringBootTest; +import org.apache.camel.util.ObjectHelper; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; +import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariables; +import org.junit.jupiter.api.condition.EnabledIfSystemProperties; +import org.junit.jupiter.api.condition.EnabledIfSystemProperty; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.annotation.DirtiesContext; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +@CamelSpringBootTest +@DirtiesContext +@SpringBootApplication +@SpringBootTest( + classes = { EarlyResolvedPropertiesTest.TestConfiguration.class }, + properties = { + "camel.component.ibm-secrets-manager.early-resolve-properties=true", + "early.resolved.property.simple={{ibm:default:databaseTestPassword#username}}" + }) + +// Must be manually tested. Provide your own accessKey and secretKey using -Dsecrets-manager and -Dcamel.ibm.sm.serviceurl +@EnabledIfSystemProperties({ + @EnabledIfSystemProperty(named = "camel.ibm.test.sm.token", matches = ".*", + disabledReason = "Secrets Manager Token not provided"), + @EnabledIfSystemProperty(named = "camel.ibm.test.sm.serviceurl", matches = ".*", + disabledReason = "Secrets Manager Service URL not provided") +}) +public class EarlyResolvedPropertiesTest { + + static SecretsManager client; + + static String secretId = ""; + @BeforeAll + public static void setup() throws IOException { + String token = System.getProperty("camel.ibm.test.sm.token"); + String serviceUrl = System.getProperty("camel.ibm.test.sm.serviceurl"); + System.setProperty("camel.vault.ibm.token", token); + System.setProperty("camel.vault.ibm.serviceUrl", serviceUrl); + + IamAuthenticator iamAuthenticator = new IamAuthenticator.Builder() + .apikey(token) + .build(); + client = new SecretsManager("Camel Secrets Manager Service for Properties", iamAuthenticator); + client.setServiceUrl(serviceUrl); + + KVSecretPrototype.Builder kvSecretResourceBuilder = new KVSecretPrototype.Builder(); + kvSecretResourceBuilder + .name("databaseTestPassword"); + Map<String, Object> payload = new HashMap<>(); + payload.put("username", "admin"); + payload.put("password", "password"); + kvSecretResourceBuilder.data(payload); + kvSecretResourceBuilder.secretType(KVSecretPrototype.SecretType.KV); + KVSecretPrototype kvSecretResource = kvSecretResourceBuilder.build(); + + CreateSecretOptions createSecretOptions = new CreateSecretOptions.Builder() + .secretPrototype(kvSecretResource) + .build(); + Response<Secret> createResp = client.createSecret(createSecretOptions).execute(); + + secretId = createResp.getResult().getId(); + } + + @AfterAll + public static void teardown() throws IOException { + + DeleteSecretOptions.Builder deleteSecretOptionsBuilder = new DeleteSecretOptions.Builder(); + deleteSecretOptionsBuilder.id(secretId); + client.deleteSecret(deleteSecretOptionsBuilder.build()); + } + + @Value("${early.resolved.property}") + private String earlyResolvedProperty; + + @Value("${early.resolved.property.simple}") + private String earlyResolvedPropertySimple; + + @Test + public void testEarlyResolvedProperties() { + Assertions.assertThat(earlyResolvedProperty).isEqualTo("admin"); + Assertions.assertThat(earlyResolvedPropertySimple).isEqualTo("admin"); + } + + @Configuration + @AutoConfigureBefore(CamelAutoConfiguration.class) + public static class TestConfiguration { + } +} diff --git a/components-starter/camel-ibm-secrets-manager-starter/src/test/resources/application.properties b/components-starter/camel-ibm-secrets-manager-starter/src/test/resources/application.properties new file mode 100644 index 00000000000..d36a9066a6d --- /dev/null +++ b/components-starter/camel-ibm-secrets-manager-starter/src/test/resources/application.properties @@ -0,0 +1,2 @@ +# Needed by EarlyResolvedPropertiesTest +early.resolved.property = {{ibm:default:databaseTestPassword#username}} \ No newline at end of file