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-spring-boot.git
The following commit(s) were added to refs/heads/main by this push: new 078ce74a72e CAMEL-21265 - Create ApplicationEnvironmentPreparedEvent for Vault co… (#1254) 078ce74a72e is described below commit 078ce74a72e739a38d0c5e2e672117af0317cf86 Author: Andrea Cosentino <anco...@gmail.com> AuthorDate: Mon Oct 14 14:41:24 2024 +0200 CAMEL-21265 - Create ApplicationEnvironmentPreparedEvent for Vault co… (#1254) * CAMEL-21265 - Create ApplicationEnvironmentPreparedEvent for Vault components - AWS Secrets Manager Signed-off-by: Andrea Cosentino <anco...@gmail.com> * CAMEL-21265 - Create ApplicationEnvironmentPreparedEvent for Vault components - AWS Secrets Manager Signed-off-by: Andrea Cosentino <anco...@gmail.com> --------- Signed-off-by: Andrea Cosentino <anco...@gmail.com> --- .../camel-aws-secrets-manager-starter/pom.xml | 12 +++ ...pringBootAwsSecretsManagerPropertiesParser.java | 109 ++++++++++++++++++++ .../src/main/resources/META-INF/spring.factories | 2 + .../springboot/EarlyResolvedPropertiesTest.java | 110 +++++++++++++++++++++ .../src/test/resources/application.properties | 2 + 5 files changed, 235 insertions(+) diff --git a/components-starter/camel-aws-secrets-manager-starter/pom.xml b/components-starter/camel-aws-secrets-manager-starter/pom.xml index 117200eb40d..779d0162e46 100644 --- a/components-starter/camel-aws-secrets-manager-starter/pom.xml +++ b/components-starter/camel-aws-secrets-manager-starter/pom.xml @@ -46,6 +46,18 @@ </exclusions> <!--END OF GENERATED CODE--> </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-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 new file mode 100644 index 00000000000..6eeb980dee5 --- /dev/null +++ b/components-starter/camel-aws-secrets-manager-starter/src/main/java/org/apache/camel/component/aws/secretsmanager/springboot/SpringBootAwsSecretsManagerPropertiesParser.java @@ -0,0 +1,109 @@ +/* + * 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.aws.secretsmanager.springboot; + +import org.apache.camel.RuntimeCamelException; +import org.apache.camel.component.aws.secretsmanager.SecretsManagerPropertiesFunction; +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 software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient; +import software.amazon.awssdk.services.secretsmanager.SecretsManagerClientBuilder; + +import java.util.Properties; + +public class SpringBootAwsSecretsManagerPropertiesParser implements ApplicationListener<ApplicationEnvironmentPreparedEvent> { + private static final Logger LOG = LoggerFactory.getLogger(SpringBootAwsSecretsManagerPropertiesParser.class); + + @Override + public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { + SecretsManagerClient client; + ConfigurableEnvironment environment = event.getEnvironment(); + 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"); + String region = environment.getProperty("camel.vault.aws.region"); + boolean useDefaultCredentialsProvider = Boolean.parseBoolean(environment.getProperty("camel.vault.aws.defaultCredentialsProvider")); + boolean useProfileCredentialsProvider = Boolean.parseBoolean(environment.getProperty("camel.vault.aws.profileCredentialsProvider")); + String profileName = environment.getProperty("camel.vault.aws.profileName"); + if (ObjectHelper.isNotEmpty(accessKey) && ObjectHelper.isNotEmpty(secretKey) && ObjectHelper.isNotEmpty(region)) { + SecretsManagerClientBuilder clientBuilder = SecretsManagerClient.builder(); + AwsBasicCredentials cred = AwsBasicCredentials.create(accessKey, secretKey); + clientBuilder = clientBuilder.credentialsProvider(StaticCredentialsProvider.create(cred)); + clientBuilder.region(Region.of(region)); + client = clientBuilder.build(); + } else if (useDefaultCredentialsProvider && ObjectHelper.isNotEmpty(region)) { + SecretsManagerClientBuilder clientBuilder = SecretsManagerClient.builder(); + clientBuilder.region(Region.of(region)); + client = clientBuilder.build(); + } else if (useProfileCredentialsProvider && ObjectHelper.isNotEmpty(profileName)) { + SecretsManagerClientBuilder clientBuilder = SecretsManagerClient.builder(); + clientBuilder.credentialsProvider(ProfileCredentialsProvider.create(profileName)); + clientBuilder.region(Region.of(region)); + client = clientBuilder.build(); + } else { + throw new RuntimeCamelException( + "Using the AWS Secrets Manager Properties Function requires setting AWS credentials as application properties or environment variables"); + } + SecretsManagerPropertiesFunction secretsManagerPropertiesFunction = new SecretsManagerPropertiesFunction(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("{{aws:") && + stringValue.endsWith("}}")) { + LOG.debug("decrypting and overriding property {}", key); + try { + String element = secretsManagerPropertiesFunction.apply(stringValue + .replace("{{aws:", "") + .replace("}}", "")); + System.err.println("Element: " + element); + 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-camel-aws-secrets-manager-properties", props)); + } + } +} diff --git a/components-starter/camel-aws-secrets-manager-starter/src/main/resources/META-INF/spring.factories b/components-starter/camel-aws-secrets-manager-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000000..cb00d00a806 --- /dev/null +++ b/components-starter/camel-aws-secrets-manager-starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.context.ApplicationListener=\ + org.apache.camel.component.aws.secretsmanager.springboot.SpringBootAwsSecretsManagerPropertiesParser \ No newline at end of file diff --git a/components-starter/camel-aws-secrets-manager-starter/src/test/java/org/apache/camel/component/aws/secretsmanager/springboot/EarlyResolvedPropertiesTest.java b/components-starter/camel-aws-secrets-manager-starter/src/test/java/org/apache/camel/component/aws/secretsmanager/springboot/EarlyResolvedPropertiesTest.java new file mode 100644 index 00000000000..3b12cb00c95 --- /dev/null +++ b/components-starter/camel-aws-secrets-manager-starter/src/test/java/org/apache/camel/component/aws/secretsmanager/springboot/EarlyResolvedPropertiesTest.java @@ -0,0 +1,110 @@ +/* + * 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.aws.secretsmanager.springboot; + +import org.apache.camel.spring.boot.CamelAutoConfiguration; +import org.apache.camel.test.spring.junit5.CamelSpringBootTest; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfSystemProperties; +import org.junit.jupiter.api.condition.EnabledIfSystemProperty; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.context.annotation.Configuration; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient; +import software.amazon.awssdk.services.secretsmanager.SecretsManagerClientBuilder; +import software.amazon.awssdk.services.secretsmanager.model.CreateSecretRequest; +import org.assertj.core.api.Assertions; +import software.amazon.awssdk.services.secretsmanager.model.DeleteSecretRequest; + +@CamelSpringBootTest +@DirtiesContext +@SpringBootApplication +@SpringBootTest( + classes = { EarlyResolvedPropertiesTest.TestConfiguration.class }, + properties = { + "camel.component.aws-secrets-manager.early-resolve-properties=true", + "early.resolved.property.simple={{aws:databaseTest/password}}" + }) + +// Must be manually tested. Provide your own accessKey and secretKey using -Dcamel.vault.aws.accessKey, -Dcamel.vault.aws.secretKey and -Dcamel.vault.aws.region +@EnabledIfSystemProperties({ + @EnabledIfSystemProperty(named = "camel.vault.test.aws.accessKey", matches = ".*", + disabledReason = "Access key not provided"), + @EnabledIfSystemProperty(named = "camel.vault.test.aws.secretKey", matches = ".*", + disabledReason = "Secret key not provided"), + @EnabledIfSystemProperty(named = "camel.vault.test.aws.region", matches = ".*", disabledReason = "Region not provided"), +}) +public class EarlyResolvedPropertiesTest { + + @BeforeAll + public static void setup() { + String accessKey = System.getProperty("camel.vault.test.aws.accessKey"); + String secretKey = System.getProperty("camel.vault.test.aws.secretKey"); + String region = System.getProperty("camel.vault.test.aws.region"); + System.setProperty("camel.vault.aws.accessKey", accessKey); + System.setProperty("camel.vault.aws.secretKey", secretKey); + System.setProperty("camel.vault.aws.region", region); + + SecretsManagerClientBuilder clientBuilder = SecretsManagerClient.builder(); + AwsBasicCredentials cred = AwsBasicCredentials.create(accessKey, secretKey); + clientBuilder = clientBuilder.credentialsProvider(StaticCredentialsProvider.create(cred)); + clientBuilder.region(Region.of(region)); + SecretsManagerClient client = clientBuilder.build(); + CreateSecretRequest req = CreateSecretRequest.builder().name("databaseTest/password").secretString("string").build(); + client.createSecret(req); + } + + @AfterAll + public static void teardown() { + String accessKey = System.getProperty("camel.vault.test.aws.accessKey"); + String secretKey = System.getProperty("camel.vault.test.aws.secretKey"); + String region = System.getProperty("camel.vault.test.aws.region"); + + SecretsManagerClientBuilder clientBuilder = SecretsManagerClient.builder(); + AwsBasicCredentials cred = AwsBasicCredentials.create(accessKey, secretKey); + clientBuilder = clientBuilder.credentialsProvider(StaticCredentialsProvider.create(cred)); + clientBuilder.region(Region.of(region)); + SecretsManagerClient client = clientBuilder.build(); + DeleteSecretRequest req = DeleteSecretRequest.builder().secretId("databaseTest/password").forceDeleteWithoutRecovery(true).build(); + client.deleteSecret(req); + } + + @Value("${early.resolved.property}") + private String earlyResolvedProperty; + + @Value("${early.resolved.property.simple}") + private String earlyResolvedPropertySimple; + + @Test + public void testEarlyResolvedProperties() { + Assertions.assertThat(earlyResolvedProperty).isEqualTo("string"); + Assertions.assertThat(earlyResolvedPropertySimple).isEqualTo("string"); + } + + @Configuration + @AutoConfigureBefore(CamelAutoConfiguration.class) + public static class TestConfiguration { + } +} diff --git a/components-starter/camel-aws-secrets-manager-starter/src/test/resources/application.properties b/components-starter/camel-aws-secrets-manager-starter/src/test/resources/application.properties new file mode 100644 index 00000000000..f36b2d60d93 --- /dev/null +++ b/components-starter/camel-aws-secrets-manager-starter/src/test/resources/application.properties @@ -0,0 +1,2 @@ +# Needed by EarlyResolvedPropertiesTest +early.resolved.property = {{aws:databaseTest/password}} \ No newline at end of file