diff --git a/bom/pom.xml b/bom/pom.xml index 1cd014286..b3767bdc5 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -147,6 +147,16 @@ quarkus-amazon-secretsmanager-deployment ${project.version} + + io.quarkiverse.amazonservices + quarkus-amazon-secretsmanager-config + ${project.version} + + + io.quarkiverse.amazonservices + quarkus-amazon-secretsmanager-config-deployment + ${project.version} + io.quarkiverse.amazonservices quarkus-amazon-devservices-ses diff --git a/common/deployment/src/main/java/io/quarkus/amazon/common/deployment/AbstractAmazonServiceProcessor.java b/common/deployment/src/main/java/io/quarkus/amazon/common/deployment/AbstractAmazonServiceProcessor.java index df2b48bb6..7f0fa3059 100644 --- a/common/deployment/src/main/java/io/quarkus/amazon/common/deployment/AbstractAmazonServiceProcessor.java +++ b/common/deployment/src/main/java/io/quarkus/amazon/common/deployment/AbstractAmazonServiceProcessor.java @@ -87,10 +87,10 @@ protected void setupExtension( //Discover all clients injections in order to determine if async or sync client is required for (RequireAmazonClientBuildItem clientRequirement : clientRequirements) { - if (clientRequirement.getSyncClassName().filter(syncClientName()::equals).isPresent()) { + if (clientRequirement.getSyncClassName().filter(p -> syncClientName().equals(p)).isPresent()) { syncClassName = Optional.of(syncClientName()); } - if (clientRequirement.getAsyncClassName().filter(asyncClientName()::equals).isPresent()) { + if (clientRequirement.getAsyncClassName().filter(p -> asyncClientName().equals(p)).isPresent()) { asyncClassName = Optional.of(asyncClientName()); } } @@ -220,7 +220,7 @@ protected void createClientBuilders( .scope(ApplicationScoped.class) .runtimeValue(syncClientBuilder) .done()); - clientSync.produce(new AmazonClientSyncResultBuildItem(configName)); + clientSync.produce(new AmazonClientSyncResultBuildItem(configName, syncClientBuilder)); } if (asyncClientBuilder != null) { asyncClientBuilder = recorder.configure(asyncClientBuilder, awsConfigRuntime, sdkConfigRuntime, @@ -230,7 +230,7 @@ protected void createClientBuilders( .scope(ApplicationScoped.class) .runtimeValue(asyncClientBuilder) .done()); - clientAsync.produce(new AmazonClientAsyncResultBuildItem(configName)); + clientAsync.produce(new AmazonClientAsyncResultBuildItem(configName, asyncClientBuilder)); } if (presignerBuilder != null) { presignerBuilder = recorder.configurePresigner(presignerBuilder, awsConfigRuntime, sdkConfigRuntime, diff --git a/common/deployment/src/main/java/io/quarkus/amazon/common/deployment/AmazonClientAsyncResultBuildItem.java b/common/deployment/src/main/java/io/quarkus/amazon/common/deployment/AmazonClientAsyncResultBuildItem.java index 1ebbdb8c8..923b752d2 100644 --- a/common/deployment/src/main/java/io/quarkus/amazon/common/deployment/AmazonClientAsyncResultBuildItem.java +++ b/common/deployment/src/main/java/io/quarkus/amazon/common/deployment/AmazonClientAsyncResultBuildItem.java @@ -1,6 +1,8 @@ package io.quarkus.amazon.common.deployment; import io.quarkus.builder.item.MultiBuildItem; +import io.quarkus.runtime.RuntimeValue; +import software.amazon.awssdk.awscore.client.builder.AwsClientBuilder; /* * Describes what async clients are provided by a given extension @@ -8,12 +10,18 @@ public final class AmazonClientAsyncResultBuildItem extends MultiBuildItem { private final String awsClientName; + private RuntimeValue asyncClientBuilder; - public AmazonClientAsyncResultBuildItem(String awsClientName) { + public AmazonClientAsyncResultBuildItem(String awsClientName, RuntimeValue asyncClientBuilder) { this.awsClientName = awsClientName; + this.asyncClientBuilder = asyncClientBuilder; } public String getAwsClientName() { return awsClientName; } + + public RuntimeValue getAsyncClientBuilder() { + return asyncClientBuilder; + } } diff --git a/common/deployment/src/main/java/io/quarkus/amazon/common/deployment/AmazonClientSyncResultBuildItem.java b/common/deployment/src/main/java/io/quarkus/amazon/common/deployment/AmazonClientSyncResultBuildItem.java index 2f2305f70..260564148 100644 --- a/common/deployment/src/main/java/io/quarkus/amazon/common/deployment/AmazonClientSyncResultBuildItem.java +++ b/common/deployment/src/main/java/io/quarkus/amazon/common/deployment/AmazonClientSyncResultBuildItem.java @@ -1,6 +1,8 @@ package io.quarkus.amazon.common.deployment; import io.quarkus.builder.item.MultiBuildItem; +import io.quarkus.runtime.RuntimeValue; +import software.amazon.awssdk.awscore.client.builder.AwsClientBuilder; /* * Describes what sync clients are provided by a given extension @@ -8,12 +10,18 @@ public final class AmazonClientSyncResultBuildItem extends MultiBuildItem { private final String awsClientName; + private RuntimeValue syncClientBuilder; - public AmazonClientSyncResultBuildItem(String awsClientName) { + public AmazonClientSyncResultBuildItem(String awsClientName, RuntimeValue syncClientBuilder) { this.awsClientName = awsClientName; + this.syncClientBuilder = syncClientBuilder; } public String getAwsClientName() { return awsClientName; } + + public RuntimeValue getSyncClientBuilder() { + return syncClientBuilder; + } } diff --git a/pom.xml b/pom.xml index f9913970d..652e56c9e 100644 --- a/pom.xml +++ b/pom.xml @@ -28,6 +28,7 @@ ssm sts secretsmanager + secretsmanager-config docs integration-tests diff --git a/secretsmanager-config/deployment/pom.xml b/secretsmanager-config/deployment/pom.xml new file mode 100644 index 000000000..67ccdfe59 --- /dev/null +++ b/secretsmanager-config/deployment/pom.xml @@ -0,0 +1,65 @@ + + + 4.0.0 + + + io.quarkiverse.amazonservices + quarkus-amazon-secretsmanager-config-parent + 999-SNAPSHOT + + + quarkus-amazon-secretsmanager-config-deployment + Quarkus - Amazon Services - Secrets Manager Config - Deployment + + + + io.quarkiverse.amazonservices + quarkus-amazon-common-deployment + + + io.quarkiverse.amazonservices + quarkus-amazon-common-deployment-spi + + + io.quarkus + quarkus-credentials-deployment + + + io.quarkiverse.amazonservices + quarkus-amazon-secretsmanager-config + + + software.amazon.awssdk + url-connection-client + + + + + io.quarkus + quarkus-junit5-internal + test + + + io.rest-assured + rest-assured + test + + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${quarkus.version} + + + + + + + diff --git a/secretsmanager-config/deployment/src/main/java/io/quarkus/amazon/secretsmanager/config/deployment/SecretsManagerConfigDevServicesProcessor.java b/secretsmanager-config/deployment/src/main/java/io/quarkus/amazon/secretsmanager/config/deployment/SecretsManagerConfigDevServicesProcessor.java new file mode 100644 index 000000000..953f33561 --- /dev/null +++ b/secretsmanager-config/deployment/src/main/java/io/quarkus/amazon/secretsmanager/config/deployment/SecretsManagerConfigDevServicesProcessor.java @@ -0,0 +1,54 @@ +package io.quarkus.amazon.secretsmanager.config.deployment; + +import java.util.Map; + +import org.testcontainers.containers.localstack.LocalStackContainer; +import org.testcontainers.containers.localstack.LocalStackContainer.Service; + +import io.quarkus.amazon.common.deployment.spi.AbstractDevServicesLocalStackProcessor; +import io.quarkus.amazon.common.deployment.spi.DevServicesLocalStackProviderBuildItem; +import io.quarkus.amazon.common.runtime.DevServicesBuildTimeConfig; +import io.quarkus.amazon.secretsmanager.config.runtime.SecretsManagerConfigBuildTimeConfig; +import io.quarkus.amazon.secretsmanager.config.runtime.SecretsManagerDevServicesBuildTimeConfig; +import io.quarkus.deployment.annotations.BuildStep; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient; + +public class SecretsManagerConfigDevServicesProcessor extends AbstractDevServicesLocalStackProcessor { + + @BuildStep + DevServicesLocalStackProviderBuildItem setupSecretsManager(SecretsManagerConfigBuildTimeConfig clientBuildTimeConfig) { + return this.setup(Service.SECRETSMANAGER, clientBuildTimeConfig.devservices); + } + + @Override + protected void overrideDefaultConfig(Map defaultConfig) { + for (String key : defaultConfig.keySet().toArray(new String[0])) { + defaultConfig.put(key.replace(Service.SECRETSMANAGER.getName(), Service.SECRETSMANAGER.getName() + "-config"), + defaultConfig.remove(key)); + } + } + + @Override + protected void prepareLocalStack(DevServicesBuildTimeConfig clientBuildTimeConfig, + LocalStackContainer localstack) { + createSecrets(localstack, (SecretsManagerDevServicesBuildTimeConfig) clientBuildTimeConfig); + } + + public void createSecrets(LocalStackContainer localstack, SecretsManagerDevServicesBuildTimeConfig configuration) { + try (SecretsManagerClient client = SecretsManagerClient.builder() + .endpointOverride(localstack.getEndpointOverride(LocalStackContainer.Service.S3)) + .region(Region.of(localstack.getRegion())) + .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials + .create(localstack.getAccessKey(), localstack.getSecretKey()))) + .httpClientBuilder(UrlConnectionHttpClient.builder()) + .build()) { + configuration.secrets.forEach((key, value) -> { + client.createSecret(r -> r.name(key).secretString(value)); + }); + } + } +} diff --git a/secretsmanager-config/deployment/src/main/java/io/quarkus/amazon/secretsmanager/config/deployment/SecretsManagerConfigProcessor.java b/secretsmanager-config/deployment/src/main/java/io/quarkus/amazon/secretsmanager/config/deployment/SecretsManagerConfigProcessor.java new file mode 100644 index 000000000..d3e46a1b5 --- /dev/null +++ b/secretsmanager-config/deployment/src/main/java/io/quarkus/amazon/secretsmanager/config/deployment/SecretsManagerConfigProcessor.java @@ -0,0 +1,136 @@ +package io.quarkus.amazon.secretsmanager.config.deployment; + +import java.util.List; +import java.util.Optional; + +import org.jboss.jandex.DotName; + +import io.quarkus.amazon.common.deployment.AbstractAmazonServiceProcessor; +import io.quarkus.amazon.common.deployment.AmazonClientBuildItem; +import io.quarkus.amazon.common.deployment.AmazonClientInterceptorsPathBuildItem; +import io.quarkus.amazon.common.deployment.AmazonClientSyncResultBuildItem; +import io.quarkus.amazon.common.deployment.AmazonClientSyncTransportBuildItem; +import io.quarkus.amazon.common.deployment.RequireAmazonClientBuildItem; +import io.quarkus.amazon.common.runtime.AmazonClientRecorder; +import io.quarkus.amazon.common.runtime.AmazonClientUrlConnectionTransportRecorder; +import io.quarkus.amazon.common.runtime.SyncHttpClientBuildTimeConfig; +import io.quarkus.amazon.common.runtime.SyncHttpClientBuildTimeConfig.SyncClientType; +import io.quarkus.amazon.secretsmanager.config.runtime.SecretsManagerConfigBuildTimeConfig; +import io.quarkus.amazon.secretsmanager.config.runtime.SecretsManagerConfigConfig; +import io.quarkus.amazon.secretsmanager.config.runtime.SecretsManagerConfigRecorder; +import io.quarkus.arc.deployment.SyntheticBeanBuildItem; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.ExecutionTime; +import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.builditem.ExtensionSslNativeSupportBuildItem; +import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.deployment.builditem.RunTimeConfigurationSourceValueBuildItem; +import io.quarkus.runtime.RuntimeValue; +import software.amazon.awssdk.awscore.client.builder.AwsClientBuilder; +import software.amazon.awssdk.services.secretsmanager.SecretsManagerAsyncClientBuilder; +import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient; +import software.amazon.awssdk.services.secretsmanager.SecretsManagerClientBuilder; + +public class SecretsManagerConfigProcessor extends AbstractAmazonServiceProcessor { + + private static final String AMAZON_SECRETS_MANAGER_CONFIG = "amazon-secretsmanager-config"; + + SecretsManagerConfigBuildTimeConfig buildTimeConfig; + + @Override + protected String amazonServiceClientName() { + return AMAZON_SECRETS_MANAGER_CONFIG; + } + + @Override + protected String configName() { + return "secretsmanager-config"; + } + + @Override + protected DotName syncClientName() { + return DotName.createSimple(SecretsManagerClient.class.getName()); + } + + @Override + protected DotName asyncClientName() { + throw new UnsupportedOperationException(); + } + + @Override + protected String builtinInterceptorsPath() { + return "software/amazon/awssdk/services/secretsmanager/execution.interceptors"; + } + + @BuildStep + @Record(ExecutionTime.RUNTIME_INIT) + public RunTimeConfigurationSourceValueBuildItem configure(SecretsManagerConfigRecorder recorder, + SecretsManagerConfigConfig config, SecretsManagerConfigBuildTimeConfig buildTimeConfig, + List syncClientBuilders) { + + String configName = configName(); + + RuntimeValue syncClientBuilder = (RuntimeValue) syncClientBuilders + .stream().filter(s -> configName.equals(s.getAwsClientName())).findFirst().get().getSyncClientBuilder(); + + return new RunTimeConfigurationSourceValueBuildItem( + recorder.configSources(config, buildTimeConfig, syncClientBuilder)); + } + + @BuildStep + void setup(List clientRequirements, + BuildProducer extensionSslNativeSupport, + BuildProducer feature, + BuildProducer interceptors, + BuildProducer clientProducer) { + + SyncHttpClientBuildTimeConfig syncClientConfig = new SyncHttpClientBuildTimeConfig(); + syncClientConfig.type = SyncClientType.URL; + + setupExtension(List.of(new RequireAmazonClientBuildItem(Optional.of(syncClientName()), Optional.empty())), + extensionSslNativeSupport, feature, interceptors, clientProducer, + buildTimeConfig.sdk, syncClientConfig); + } + + @BuildStep + @Record(ExecutionTime.RUNTIME_INIT) + void setupUrlConnectionSyncTransport(List amazonClients, SecretsManagerConfigRecorder recorder, + AmazonClientUrlConnectionTransportRecorder transportRecorder, + SecretsManagerConfigConfig runtimeConfig, BuildProducer syncTransports) { + SyncHttpClientBuildTimeConfig syncClientConfig = new SyncHttpClientBuildTimeConfig(); + syncClientConfig.type = SyncClientType.URL; + + createUrlConnectionSyncTransportBuilder(amazonClients, + transportRecorder, + syncClientConfig, + recorder.getSyncConfig(runtimeConfig), + syncTransports); + } + + @BuildStep + @Record(ExecutionTime.RUNTIME_INIT) + void createClientBuilders(SecretsManagerConfigRecorder recorder, + AmazonClientRecorder commonRecorder, + SecretsManagerConfigConfig runtimeConfig, + List syncTransports, + BuildProducer syntheticBeans, + BuildProducer clientSync) { + + createClientBuilders(commonRecorder, + recorder.getAwsConfig(runtimeConfig), + recorder.getSdkConfig(runtimeConfig), + buildTimeConfig.sdk, + syncTransports, + List.of(), + SecretsManagerClientBuilder.class, + (syncTransport) -> recorder.createSyncBuilder(runtimeConfig, syncTransport), + SecretsManagerAsyncClientBuilder.class, + null, + null, + null, + syntheticBeans, + clientSync, + null); + } +} diff --git a/secretsmanager-config/deployment/src/test/java/io/quarkus/amazon/secretsmanager/config/deployment/SecretsManagerConfigTest.java b/secretsmanager-config/deployment/src/test/java/io/quarkus/amazon/secretsmanager/config/deployment/SecretsManagerConfigTest.java new file mode 100644 index 000000000..02198ea81 --- /dev/null +++ b/secretsmanager-config/deployment/src/test/java/io/quarkus/amazon/secretsmanager/config/deployment/SecretsManagerConfigTest.java @@ -0,0 +1,40 @@ +package io.quarkus.amazon.secretsmanager.config.deployment; + +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.junit.Assert; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class SecretsManagerConfigTest { + + @ConfigProperty(name = "mysecretkey") + String secret; + + @ConfigProperty(name = "myprefix.mysecretkey") + String secretWithPrefix; + + @ConfigProperty(name = "otherprefix.mysecretkey") + String secretWithOtherPrefix; + + @ConfigProperty(name = "disabledprefix.mysecretkey", defaultValue = "unset") + String secretWithDisabledPrefix; + + @ConfigProperty(name = "othersecretkey", defaultValue = "unset") + String unset; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addAsResource("devservices.properties", "application.properties")); + + @Test + public void test() { + Assert.assertEquals("mysecretvalue", secret); + Assert.assertEquals("mysecretvalue", secretWithPrefix); + Assert.assertEquals("mysecretvalue", secretWithOtherPrefix); + Assert.assertEquals("unset", secretWithDisabledPrefix); + Assert.assertEquals("unset", unset); + } +} diff --git a/secretsmanager-config/deployment/src/test/resources/devservices.properties b/secretsmanager-config/deployment/src/test/resources/devservices.properties new file mode 100644 index 000000000..82a6f6122 --- /dev/null +++ b/secretsmanager-config/deployment/src/test/resources/devservices.properties @@ -0,0 +1,7 @@ +quarkus.secretsmanager-config.devservices.secrets.mysecretkey=mysecretvalue +quarkus.secretsmanager-config.devservices.secrets.othersecretkey=othersecretvalue + +quarkus.secretsmanager-config.filter.name-prefix=mysecret +quarkus.secretsmanager-config.filter.myprefix.name-prefix=mysecret +quarkus.secretsmanager-config.filter.otherprefix.enabled=true +quarkus.secretsmanager-config.filter.disabledprefix.enabled=false diff --git a/secretsmanager-config/pom.xml b/secretsmanager-config/pom.xml new file mode 100644 index 000000000..c257385cf --- /dev/null +++ b/secretsmanager-config/pom.xml @@ -0,0 +1,21 @@ + + + 4.0.0 + + + io.quarkiverse.amazonservices + quarkus-amazon-services-build-parent + 999-SNAPSHOT + ../build-parent/pom.xml + + + quarkus-amazon-secretsmanager-config-parent + Quarkus - Amazon Services - Secrets Manager Config + pom + + + runtime + deployment + + + diff --git a/secretsmanager-config/runtime/pom.xml b/secretsmanager-config/runtime/pom.xml new file mode 100644 index 000000000..05e084ea1 --- /dev/null +++ b/secretsmanager-config/runtime/pom.xml @@ -0,0 +1,60 @@ + + + 4.0.0 + + + io.quarkiverse.amazonservices + quarkus-amazon-secretsmanager-config-parent + 999-SNAPSHOT + + + quarkus-amazon-secretsmanager-config + Quarkus - Amazon Services - Secrets Manager Config - Runtime + Connect to Amazon Secrets Manager + + + + io.quarkiverse.amazonservices + quarkus-amazon-common + + + io.quarkus + quarkus-credentials + + + software.amazon.awssdk + secretsmanager + + + software.amazon.awssdk + netty-nio-client + + + software.amazon.awssdk + apache-client + + + + + + + + + io.quarkus + quarkus-extension-maven-plugin + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${quarkus.version} + + + + + + + diff --git a/secretsmanager-config/runtime/src/main/java/io/quarkus/amazon/secretsmanager/config/runtime/SecretsManagerConfigBuildTimeConfig.java b/secretsmanager-config/runtime/src/main/java/io/quarkus/amazon/secretsmanager/config/runtime/SecretsManagerConfigBuildTimeConfig.java new file mode 100644 index 000000000..24326b3ca --- /dev/null +++ b/secretsmanager-config/runtime/src/main/java/io/quarkus/amazon/secretsmanager/config/runtime/SecretsManagerConfigBuildTimeConfig.java @@ -0,0 +1,25 @@ +package io.quarkus.amazon.secretsmanager.config.runtime; + +import io.quarkus.amazon.common.runtime.SdkBuildTimeConfig; +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +/** + * Amazon Secrets Manager Config build time configuration + */ +@ConfigRoot(name = "secretsmanager-config", phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED) +public class SecretsManagerConfigBuildTimeConfig { + + /** + * SDK client configurations for AWS Secrets Manager client + */ + @ConfigItem(name = ConfigItem.PARENT) + public SdkBuildTimeConfig sdk; + + /** + * Config for dev services + */ + @ConfigItem + public SecretsManagerDevServicesBuildTimeConfig devservices; +} diff --git a/secretsmanager-config/runtime/src/main/java/io/quarkus/amazon/secretsmanager/config/runtime/SecretsManagerConfigConfig.java b/secretsmanager-config/runtime/src/main/java/io/quarkus/amazon/secretsmanager/config/runtime/SecretsManagerConfigConfig.java new file mode 100644 index 000000000..c1d41a6ad --- /dev/null +++ b/secretsmanager-config/runtime/src/main/java/io/quarkus/amazon/secretsmanager/config/runtime/SecretsManagerConfigConfig.java @@ -0,0 +1,107 @@ +package io.quarkus.amazon.secretsmanager.config.runtime; + +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import io.quarkus.amazon.common.runtime.AwsConfig; +import io.quarkus.amazon.common.runtime.SdkConfig; +import io.quarkus.amazon.common.runtime.SyncHttpClientConfig; +import io.quarkus.runtime.annotations.ConfigDocMapKey; +import io.quarkus.runtime.annotations.ConfigDocSection; +import io.quarkus.runtime.annotations.ConfigGroup; +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +@ConfigRoot(name = "secretsmanager-config", phase = ConfigPhase.BOOTSTRAP) +public class SecretsManagerConfigConfig { + /** + * AWS SDK client configurations + */ + @ConfigItem(name = ConfigItem.PARENT) + @ConfigDocSection + public SdkConfig sdk; + + /** + * AWS services configurations + */ + @ConfigItem + @ConfigDocSection + public AwsConfig aws; + + /** + * Sync HTTP transport configurations + */ + @ConfigItem + @ConfigDocSection + public SyncHttpClientConfig syncClient; + + /** + * Allows you to add filters when listing secrets + */ + @ConfigItem + public FilterConfig filter; + + /** + * Secrets matching those filter criteria will have their key prefixed + */ + @ConfigItem(name = "filter") + @ConfigDocMapKey("prefix") + public Map filterPrefix; + + /** + * Allows you to add filters when listing secrets + */ + @ConfigGroup + public static class FilterConfig { + + /** + * enable this filter criterion + */ + @ConfigItem(defaultValue = "true") + boolean enabled; + + /** + * description: Prefix match, not case-sensitive. + */ + @ConfigItem + Optional> description; + + /** + * name: Prefix match, case-sensitive. + */ + @ConfigItem + Optional> namePrefix; + + /** + * tag-key: Prefix match, case-sensitive. + */ + @ConfigItem + Optional> tagKey; + + /** + * tag-value: Prefix match, case-sensitive. + */ + @ConfigItem + Optional> tagValue; + + /** + * primary-region: Prefix match, case-sensitive. + */ + @ConfigItem + Optional> primaryRegionPrefix; + + /** + * owning-service: Prefix match, case-sensitive. + */ + @ConfigItem + Optional> owningServicePrefix; + + /** + * all + */ + @ConfigItem + Optional> all; + } +} diff --git a/secretsmanager-config/runtime/src/main/java/io/quarkus/amazon/secretsmanager/config/runtime/SecretsManagerConfigRecorder.java b/secretsmanager-config/runtime/src/main/java/io/quarkus/amazon/secretsmanager/config/runtime/SecretsManagerConfigRecorder.java new file mode 100644 index 000000000..43091779c --- /dev/null +++ b/secretsmanager-config/runtime/src/main/java/io/quarkus/amazon/secretsmanager/config/runtime/SecretsManagerConfigRecorder.java @@ -0,0 +1,47 @@ +package io.quarkus.amazon.secretsmanager.config.runtime; + +import org.eclipse.microprofile.config.spi.ConfigSourceProvider; + +import io.quarkus.amazon.common.runtime.AwsConfig; +import io.quarkus.amazon.common.runtime.SdkConfig; +import io.quarkus.amazon.common.runtime.SyncHttpClientConfig; +import io.quarkus.runtime.RuntimeValue; +import io.quarkus.runtime.annotations.Recorder; +import software.amazon.awssdk.awscore.client.builder.AwsClientBuilder; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient; +import software.amazon.awssdk.services.secretsmanager.SecretsManagerClientBuilder; + +@Recorder +public class SecretsManagerConfigRecorder { + + public RuntimeValue getSyncConfig(SecretsManagerConfigConfig config) { + return new RuntimeValue<>(config.syncClient); + } + + public RuntimeValue getAwsConfig(SecretsManagerConfigConfig config) { + return new RuntimeValue<>(config.aws); + } + + public RuntimeValue getSdkConfig(SecretsManagerConfigConfig config) { + return new RuntimeValue<>(config.sdk); + } + + public RuntimeValue createSyncBuilder(SecretsManagerConfigConfig config, + RuntimeValue transport) { + SecretsManagerClientBuilder builder = SecretsManagerClient.builder(); + if (transport != null) { + builder.httpClientBuilder(transport.getValue()); + } + return new RuntimeValue<>(builder); + } + + public RuntimeValue configSources(SecretsManagerConfigConfig config, + SecretsManagerConfigBuildTimeConfig buildTimeConfig, + RuntimeValue syncClientBuilder) { + + return new RuntimeValue<>( + new SecretsManagerConfigSourceProvider((SecretsManagerClientBuilder) syncClientBuilder.getValue(), + config)); + } +} diff --git a/secretsmanager-config/runtime/src/main/java/io/quarkus/amazon/secretsmanager/config/runtime/SecretsManagerConfigSourceProvider.java b/secretsmanager-config/runtime/src/main/java/io/quarkus/amazon/secretsmanager/config/runtime/SecretsManagerConfigSourceProvider.java new file mode 100644 index 000000000..1a155834b --- /dev/null +++ b/secretsmanager-config/runtime/src/main/java/io/quarkus/amazon/secretsmanager/config/runtime/SecretsManagerConfigSourceProvider.java @@ -0,0 +1,122 @@ +package io.quarkus.amazon.secretsmanager.config.runtime; + +import static java.util.stream.Collectors.toMap; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.eclipse.microprofile.config.spi.ConfigSource; +import org.eclipse.microprofile.config.spi.ConfigSourceProvider; + +import io.smallrye.config.common.MapBackedConfigSource; +import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient; +import software.amazon.awssdk.services.secretsmanager.SecretsManagerClientBuilder; +import software.amazon.awssdk.services.secretsmanager.model.Filter; +import software.amazon.awssdk.services.secretsmanager.model.FilterNameStringType; +import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueRequest; +import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueResponse; +import software.amazon.awssdk.services.secretsmanager.model.SecretListEntry; + +public class SecretsManagerConfigSourceProvider implements ConfigSourceProvider { + + SecretsManagerClient secretsManagerClient; + private SecretsManagerConfigConfig config; + + public SecretsManagerConfigSourceProvider(SecretsManagerClientBuilder syncClientBuilder, + SecretsManagerConfigConfig config) { + this.config = config; + secretsManagerClient = syncClientBuilder.build(); + } + + @Override + public Iterable getConfigSources(ClassLoader forClassLoader) { + int ordinal = 299; // one less than env vars + + List result = new ArrayList<>(); + + // Retrieve all secrets using pagination and stream + Map secretsMap = new HashMap<>(); + if (config.filter.enabled) { + fetchSecrets(secretsMap, config.filter, null); + } + config.filterPrefix.forEach((t, u) -> { + if (u.enabled) { + fetchSecrets(secretsMap, u, t); + } + }); + + result.add(new SecretsManagerConfigSource(secretsMap, ordinal)); + + return result; + } + + private void fetchSecrets(Map properties, SecretsManagerConfigConfig.FilterConfig filters, + String prefix) { + + Map secretsMap = secretsManagerClient.listSecretsPaginator(b -> { + List requestFilters = new ArrayList<>(); + filters.namePrefix.ifPresent(namePrefix -> { + requestFilters.add(Filter.builder().key(FilterNameStringType.NAME).values(namePrefix).build()); + }); + + filters.primaryRegionPrefix.ifPresent(primaryRegionPrefix -> { + requestFilters.add(Filter.builder().key(FilterNameStringType.PRIMARY_REGION) + .values(primaryRegionPrefix).build()); + }); + filters.owningServicePrefix.ifPresent(owningServicePrefix -> { + requestFilters.add(Filter.builder().key(FilterNameStringType.OWNING_SERVICE) + .values(owningServicePrefix).build()); + }); + filters.tagKey.ifPresent(tagKey -> { + requestFilters.add(Filter.builder().key(FilterNameStringType.TAG_KEY).values(tagKey).build()); + }); + filters.tagValue.ifPresent(tagValue -> { + requestFilters + .add(Filter.builder().key(FilterNameStringType.TAG_VALUE).values(tagValue).build()); + }); + filters.all.ifPresent(all -> { + requestFilters + .add(Filter.builder().key(FilterNameStringType.ALL).values(all).build()); + }); + filters.description.ifPresent(description -> { + requestFilters + .add(Filter.builder().key(FilterNameStringType.DESCRIPTION).values(description).build()); + }); + + b.filters(requestFilters); + }).stream().flatMap(response -> response.secretList().stream()) + .collect(Collectors.toUnmodifiableMap(this::getSecretKey, + this::getSecretValue)); + + properties.putAll(prefixMap(secretsMap, prefix)); + } + + private String getSecretKey(SecretListEntry secret) { + return secret.name(); + } + + private String getSecretValue(SecretListEntry secret) { + + GetSecretValueRequest getSecretValueRequest = GetSecretValueRequest.builder() + .secretId(secret.arn()) + .build(); + GetSecretValueResponse getSecretValueResponse = secretsManagerClient.getSecretValue(getSecretValueRequest); + return getSecretValueResponse.secretString(); + } + + private Map prefixMap(Map map, String prefix) { + return prefix == null + ? map + : map.entrySet().stream().collect(toMap(entry -> prefix + "." + entry.getKey(), Map.Entry::getValue)); + } + + private static final class SecretsManagerConfigSource extends MapBackedConfigSource { + + public SecretsManagerConfigSource(Map propertyMap, int ordinal) { + super("SecretsManagerConfigSource", propertyMap, ordinal); + } + } +} diff --git a/secretsmanager-config/runtime/src/main/java/io/quarkus/amazon/secretsmanager/config/runtime/SecretsManagerCredentialsProvider.java b/secretsmanager-config/runtime/src/main/java/io/quarkus/amazon/secretsmanager/config/runtime/SecretsManagerCredentialsProvider.java new file mode 100644 index 000000000..496f6f2bb --- /dev/null +++ b/secretsmanager-config/runtime/src/main/java/io/quarkus/amazon/secretsmanager/config/runtime/SecretsManagerCredentialsProvider.java @@ -0,0 +1,24 @@ +package io.quarkus.amazon.secretsmanager.config.runtime; + +import java.util.Map; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.inject.Named; + +import io.quarkus.credentials.CredentialsProvider; +import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient; + +@ApplicationScoped +@Named("secretsmanager-credentials-provider") +public class SecretsManagerCredentialsProvider implements CredentialsProvider { + + @Inject + SecretsManagerClient client; + + @Override + public Map getCredentials(String credentialsProviderName) { + + return Map.of(); + } +} diff --git a/secretsmanager-config/runtime/src/main/java/io/quarkus/amazon/secretsmanager/config/runtime/SecretsManagerDevServicesBuildTimeConfig.java b/secretsmanager-config/runtime/src/main/java/io/quarkus/amazon/secretsmanager/config/runtime/SecretsManagerDevServicesBuildTimeConfig.java new file mode 100644 index 000000000..62525a030 --- /dev/null +++ b/secretsmanager-config/runtime/src/main/java/io/quarkus/amazon/secretsmanager/config/runtime/SecretsManagerDevServicesBuildTimeConfig.java @@ -0,0 +1,17 @@ +package io.quarkus.amazon.secretsmanager.config.runtime; + +import java.util.Map; + +import io.quarkus.amazon.common.runtime.DevServicesBuildTimeConfig; +import io.quarkus.runtime.annotations.ConfigGroup; +import io.quarkus.runtime.annotations.ConfigItem; + +@ConfigGroup +public class SecretsManagerDevServicesBuildTimeConfig extends DevServicesBuildTimeConfig { + + /** + * The secrets to create on startup. + */ + @ConfigItem + public Map secrets; +} diff --git a/secretsmanager-config/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/secretsmanager-config/runtime/src/main/resources/META-INF/quarkus-extension.yaml new file mode 100644 index 000000000..89e9190a3 --- /dev/null +++ b/secretsmanager-config/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -0,0 +1,15 @@ +--- +artifact: ${project.groupId}:${project.artifactId}:${project.version} +name: "Amazon Secrets Manager Config" +metadata: + keywords: + - "secretsmanager" + - "aws" + - "amazon" + - "config" + categories: + - "data" + guide: https://quarkiverse.github.io/quarkiverse-docs/quarkus-amazon-services/dev/amazon-secretsmanager.html + status: "preview" + config: + - "quarkus.secretsmanager-config." diff --git a/sns/deployment/pom.xml b/sns/deployment/pom.xml index d8d7fa24d..02f25960e 100644 --- a/sns/deployment/pom.xml +++ b/sns/deployment/pom.xml @@ -44,11 +44,6 @@ rest-assured test - - software.amazon.awssdk - netty-nio-client - test - software.amazon.awssdk url-connection-client