This is an automated email from the ASF dual-hosted git repository. fmariani pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel-spring-boot.git
The following commit(s) were added to refs/heads/main by this push: new 23d6bc3ada8 Hashicorp vault early properties parser 23d6bc3ada8 is described below commit 23d6bc3ada84b3189f42734f523d7f910871d9db Author: Croway <federico.mariani.1...@gmail.com> AuthorDate: Wed Oct 9 11:53:08 2024 +0200 Hashicorp vault early properties parser --- .../camel-hashicorp-vault-starter/pom.xml | 44 ++++++++++++ .../SpringBootHashicorpVaultPropertiesParser.java | 80 ++++++++++++++++++++++ .../src/main/resources/META-INF/spring.factories | 2 + .../springboot/EarlyResolvedPropertiesTest.java | 75 ++++++++++++++++++++ .../src/test/resources/application.properties | 2 + 5 files changed, 203 insertions(+) diff --git a/components-starter/camel-hashicorp-vault-starter/pom.xml b/components-starter/camel-hashicorp-vault-starter/pom.xml index 60f7c30ece9..a54bdfebba1 100644 --- a/components-starter/camel-hashicorp-vault-starter/pom.xml +++ b/components-starter/camel-hashicorp-vault-starter/pom.xml @@ -38,6 +38,25 @@ <artifactId>camel-hashicorp-vault</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> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-test-infra-hashicorp-vault</artifactId> + <version>${project.version}</version> + <type>test-jar</type> + <scope>test</scope> + </dependency> <!--START OF GENERATED CODE--> <dependency> <groupId>org.apache.camel.springboot</groupId> @@ -45,4 +64,29 @@ </dependency> <!--END OF GENERATED CODE--> </dependencies> + <profiles> + <profile> + <id>ppc64le</id> + <activation> + <os> + <arch>ppc64le</arch> + </os> + </activation> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-failsafe-plugin</artifactId> + <configuration> + <skipITs>${skipTests}</skipITs> + <reuseForks>true</reuseForks> + <systemPropertyVariables> + <hashicorp.vault.container>icr.io/ppc64le-oss/vault-ppc64le:v1.13.1</hashicorp.vault.container> + </systemPropertyVariables> + </configuration> + </plugin> + </plugins> + </build> + </profile> + </profiles> </project> 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 new file mode 100644 index 00000000000..0b623ddbf8e --- /dev/null +++ b/components-starter/camel-hashicorp-vault-starter/src/main/java/org/apache/camel/component/hashicorp/vault/springboot/SpringBootHashicorpVaultPropertiesParser.java @@ -0,0 +1,80 @@ +package org.apache.camel.component.hashicorp.vault.springboot; + +import org.apache.camel.component.hashicorp.vault.HashicorpVaultPropertiesFunction; +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 org.springframework.vault.authentication.TokenAuthentication; +import org.springframework.vault.client.VaultEndpoint; +import org.springframework.vault.core.VaultTemplate; + +import java.util.Objects; +import java.util.Properties; + +public class SpringBootHashicorpVaultPropertiesParser implements ApplicationListener<ApplicationEnvironmentPreparedEvent> { + private static final Logger LOG = LoggerFactory.getLogger(SpringBootHashicorpVaultPropertiesParser.class); + + @Override + public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { + ConfigurableEnvironment environment = event.getEnvironment(); + 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"); + Objects.requireNonNull(environment.getProperty("camel.vault.hashicorp.port"), "Hashicorp Vault port is required"); + Objects.requireNonNull(environment.getProperty("camel.vault.hashicorp.scheme"), "Hashicorp Vault scheme is required"); + + String token = environment.getProperty("camel.vault.hashicorp.token"); + String host = environment.getProperty("camel.vault.hashicorp.host"); + + int port = Integer.parseInt(environment.getProperty("camel.vault.hashicorp.port")); + String scheme = environment.getProperty("camel.vault.hashicorp.scheme"); + + VaultEndpoint vaultEndpoint = new VaultEndpoint(); + vaultEndpoint.setHost(host); + vaultEndpoint.setPort(port); + vaultEndpoint.setScheme(scheme); + + VaultTemplate client = new VaultTemplate( + vaultEndpoint, + new TokenAuthentication(token)); + HashicorpVaultPropertiesFunction hashicorpVaultPropertiesFunction = new HashicorpVaultPropertiesFunction(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("{{hashicorp:") && + stringValue.endsWith("}}")) { + LOG.debug("decrypting and overriding property {}", key); + try { + props.put(key, hashicorpVaultPropertiesFunction.apply(stringValue + .replace("{{hashicorp:", "") + .replace("}}", ""))); + } 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-camel-hashicorp-vault-properties", props)); + } + } +} diff --git a/components-starter/camel-hashicorp-vault-starter/src/main/resources/META-INF/spring.factories b/components-starter/camel-hashicorp-vault-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000000..f8813e697fa --- /dev/null +++ b/components-starter/camel-hashicorp-vault-starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.context.ApplicationListener=\ + org.apache.camel.component.hashicorp.vault.springboot.SpringBootHashicorpVaultPropertiesParser \ No newline at end of file diff --git a/components-starter/camel-hashicorp-vault-starter/src/test/java/org/apache/camel/component/hashicorp/vault/springboot/EarlyResolvedPropertiesTest.java b/components-starter/camel-hashicorp-vault-starter/src/test/java/org/apache/camel/component/hashicorp/vault/springboot/EarlyResolvedPropertiesTest.java new file mode 100644 index 00000000000..adde78049c9 --- /dev/null +++ b/components-starter/camel-hashicorp-vault-starter/src/test/java/org/apache/camel/component/hashicorp/vault/springboot/EarlyResolvedPropertiesTest.java @@ -0,0 +1,75 @@ +package org.apache.camel.component.hashicorp.vault.springboot; + +import org.apache.camel.spring.boot.CamelAutoConfiguration; +import org.apache.camel.test.infra.hashicorp.vault.services.HashicorpServiceFactory; +import org.apache.camel.test.infra.hashicorp.vault.services.HashicorpVaultService; +import org.apache.camel.test.spring.junit5.CamelSpringBootTest; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +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 org.springframework.vault.authentication.TokenAuthentication; +import org.springframework.vault.client.VaultEndpoint; +import org.springframework.vault.core.VaultKeyValueOperations; +import org.springframework.vault.core.VaultKeyValueOperationsSupport; +import org.springframework.vault.core.VaultTemplate; + +import java.util.Map; + +@CamelSpringBootTest +@DirtiesContext +@SpringBootApplication +@SpringBootTest( + classes = { EarlyResolvedPropertiesTest.TestConfiguration.class }, + properties = { + "camel.component.hashicorp-vault.early-resolve-properties=true", + "early.resolved.property.simple={{hashicorp:secret:simple#string}}" + }) +public class EarlyResolvedPropertiesTest { + + @RegisterExtension + public static HashicorpVaultService service = HashicorpServiceFactory.createService(); + + @BeforeAll + public static void setup() { + System.setProperty("camel.vault.hashicorp.host", service.host()); + System.setProperty("camel.vault.hashicorp.port", String.valueOf(service.port())); + System.setProperty("camel.vault.hashicorp.scheme", "http"); + System.setProperty("camel.vault.hashicorp.token", service.token()); + + VaultEndpoint vaultEndpoint = new VaultEndpoint(); + vaultEndpoint.setHost(service.host()); + vaultEndpoint.setPort(service.port()); + vaultEndpoint.setScheme("http"); + + VaultTemplate client = new VaultTemplate( + vaultEndpoint, + new TokenAuthentication(service.token())); + VaultKeyValueOperations vaultKeyValueOperations = client.opsForKeyValue("secret", VaultKeyValueOperationsSupport.KeyValueBackend.versioned()); + vaultKeyValueOperations.put("simple", Map.of("string", "test")); + vaultKeyValueOperations.put("database/password", Map.of("string", "pazzword")); + } + + @Value("${early.resolved.property}") + private String earlyResolvedProperty; + + @Value("${early.resolved.property.simple}") + private String earlyResolvedPropertySimple; + + @Test + public void testEarlyResolvedProperties() { + Assertions.assertThat(earlyResolvedProperty).isEqualTo("pazzword"); + Assertions.assertThat(earlyResolvedPropertySimple).isEqualTo("test"); + } + + @Configuration + @AutoConfigureBefore(CamelAutoConfiguration.class) + public static class TestConfiguration { + } +} \ No newline at end of file diff --git a/components-starter/camel-hashicorp-vault-starter/src/test/resources/application.properties b/components-starter/camel-hashicorp-vault-starter/src/test/resources/application.properties new file mode 100644 index 00000000000..e3be01d4e06 --- /dev/null +++ b/components-starter/camel-hashicorp-vault-starter/src/test/resources/application.properties @@ -0,0 +1,2 @@ +# Needed by EarlyResolvedPropertiesTest +early.resolved.property = {{hashicorp:secret:database/password#string}} \ No newline at end of file