Skip to content

Commit

Permalink
feat: Add support for reading configuration from a Secrets Manager
Browse files Browse the repository at this point in the history
  • Loading branch information
scrocquesel committed May 27, 2023
1 parent 569e8d8 commit b403b65
Show file tree
Hide file tree
Showing 19 changed files with 582 additions and 11 deletions.
10 changes: 10 additions & 0 deletions bom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,16 @@
<artifactId>quarkus-amazon-secretsmanager-deployment</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkiverse.amazonservices</groupId>
<artifactId>quarkus-amazon-secretsmanager-config</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkiverse.amazonservices</groupId>
<artifactId>quarkus-amazon-secretsmanager-config-deployment</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkiverse.amazonservices</groupId>
<artifactId>quarkus-amazon-devservices-ses</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
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
*/
public final class AmazonClientAsyncResultBuildItem extends MultiBuildItem {

private final String awsClientName;
private RuntimeValue<AwsClientBuilder> asyncClientBuilder;

public AmazonClientAsyncResultBuildItem(String awsClientName) {
public AmazonClientAsyncResultBuildItem(String awsClientName, RuntimeValue<AwsClientBuilder> asyncClientBuilder) {
this.awsClientName = awsClientName;
this.asyncClientBuilder = asyncClientBuilder;
}

public String getAwsClientName() {
return awsClientName;
}

public RuntimeValue<AwsClientBuilder> getAsyncClientBuilder() {
return asyncClientBuilder;
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
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
*/
public final class AmazonClientSyncResultBuildItem extends MultiBuildItem {

private final String awsClientName;
private RuntimeValue<AwsClientBuilder> syncClientBuilder;

public AmazonClientSyncResultBuildItem(String awsClientName) {
public AmazonClientSyncResultBuildItem(String awsClientName, RuntimeValue<AwsClientBuilder> syncClientBuilder) {
this.awsClientName = awsClientName;
this.syncClientBuilder = syncClientBuilder;
}

public String getAwsClientName() {
return awsClientName;
}

public RuntimeValue<AwsClientBuilder> getSyncClientBuilder() {
return syncClientBuilder;
}
}
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
<module>ssm</module>
<module>sts</module>
<module>secretsmanager</module>
<module>secretsmanager-config</module>
<module>docs</module>
<module>integration-tests</module>
</modules>
Expand Down
61 changes: 61 additions & 0 deletions secretsmanager-config/deployment/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>io.quarkiverse.amazonservices</groupId>
<artifactId>quarkus-amazon-secretsmanager-config-parent</artifactId>
<version>999-SNAPSHOT</version>
</parent>

<artifactId>quarkus-amazon-secretsmanager-config-deployment</artifactId>
<name>Quarkus - Amazon Services - Secrets Manager Config - Deployment</name>

<dependencies>
<dependency>
<groupId>io.quarkiverse.amazonservices</groupId>
<artifactId>quarkus-amazon-common-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkiverse.amazonservices</groupId>
<artifactId>quarkus-amazon-common-deployment-spi</artifactId>
</dependency>
<dependency>
<groupId>io.quarkiverse.amazonservices</groupId>
<artifactId>quarkus-amazon-secretsmanager-config</artifactId>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>url-connection-client</artifactId>
</dependency>

<!-- Test dependencies -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-internal</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-extension-processor</artifactId>
<version>${quarkus.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -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<String, String> 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));
});
}
}
}
Original file line number Diff line number Diff line change
@@ -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.SecretsManagerConfigRecorder;
import io.quarkus.amazon.secretsmanager.config.runtime.SecretsManagerConfigSourceConfig;
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,
SecretsManagerConfigSourceConfig config, SecretsManagerConfigBuildTimeConfig buildTimeConfig,
List<AmazonClientSyncResultBuildItem> syncClientBuilders) {

String configName = configName();

RuntimeValue<AwsClientBuilder> syncClientBuilder = (RuntimeValue<AwsClientBuilder>) syncClientBuilders
.stream().filter(s -> configName.equals(s.getAwsClientName())).findFirst().get().getSyncClientBuilder();

return new RunTimeConfigurationSourceValueBuildItem(
recorder.configSources(config, buildTimeConfig, syncClientBuilder));
}

@BuildStep
void setup(List<RequireAmazonClientBuildItem> clientRequirements,
BuildProducer<ExtensionSslNativeSupportBuildItem> extensionSslNativeSupport,
BuildProducer<FeatureBuildItem> feature,
BuildProducer<AmazonClientInterceptorsPathBuildItem> interceptors,
BuildProducer<AmazonClientBuildItem> 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<AmazonClientBuildItem> amazonClients, SecretsManagerConfigRecorder recorder,
AmazonClientUrlConnectionTransportRecorder transportRecorder,
SecretsManagerConfigSourceConfig runtimeConfig, BuildProducer<AmazonClientSyncTransportBuildItem> 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,
SecretsManagerConfigSourceConfig runtimeConfig,
List<AmazonClientSyncTransportBuildItem> syncTransports,
BuildProducer<SyntheticBeanBuildItem> syntheticBeans,
BuildProducer<AmazonClientSyncResultBuildItem> 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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
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;

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addAsResource("devservices.properties", "application.properties"));

@Test
public void test() {
Assert.assertEquals("mysecretvalue", secret);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
quarkus.secretsmanager-config.devservices.secrets.mysecretkey=mysecretvalue
Loading

0 comments on commit b403b65

Please sign in to comment.