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 Jul 23, 2023
1 parent 873b695 commit 1735c29
Show file tree
Hide file tree
Showing 25 changed files with 1,112 additions and 0 deletions.
10 changes: 10 additions & 0 deletions bom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,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
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
<module>ssm</module>
<module>sts</module>
<module>secretsmanager</module>
<module>secretsmanager-config</module>
<module>docs</module>
<module>integration-tests</module>
</modules>
Expand Down
70 changes: 70 additions & 0 deletions secretsmanager-config/deployment/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?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-devservices-spi</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-credentials-deployment</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>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>netty-nio-client</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,185 @@
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.AmazonClientInterceptorsPathBuildItem;
import io.quarkus.amazon.common.deployment.AmazonHttpClients;
import io.quarkus.amazon.common.deployment.RequireAmazonClientBuildItem;
import io.quarkus.amazon.common.runtime.AbstractAmazonClientTransportRecorder;
import io.quarkus.amazon.common.runtime.AsyncHttpClientBuildTimeConfig.AsyncClientType;
import io.quarkus.amazon.common.runtime.SyncHttpClientBuildTimeConfig;
import io.quarkus.amazon.secretsmanager.config.runtime.SecretsManagerConfigBootstrapRecorder;
import io.quarkus.amazon.secretsmanager.config.runtime.SecretsManagerConfigBuildTimeConfig;
import io.quarkus.amazon.secretsmanager.config.runtime.SecretsManagerConfigSourceAsyncFactoryBuilder;
import io.quarkus.amazon.secretsmanager.config.runtime.SecretsManagerConfigSourceSyncFactoryBuilder;
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.ExecutorBuildItem;
import io.quarkus.deployment.builditem.ExtensionSslNativeSupportBuildItem;
import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.deployment.builditem.RunTimeConfigBuilderBuildItem;
import io.quarkus.runtime.RuntimeValue;
import software.amazon.awssdk.services.secretsmanager.SecretsManagerAsyncClient;
import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;

public class SecretsManagerConfigProcessor extends AbstractAmazonServiceProcessor {

private static final String AMAZON_SECRETS_MANAGER_CONFIG = "amazon-secretsmanager-config";

private static final DotName SYNC_FACTORY = DotName.createSimple(SecretsManagerConfigSourceSyncFactoryBuilder.class);
private static final DotName ASYNC_FACTORY = DotName.createSimple(SecretsManagerConfigSourceAsyncFactoryBuilder.class);

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() {
return DotName.createSimple(SecretsManagerAsyncClient.class.getName());
}

@Override
protected String builtinInterceptorsPath() {
return "software/amazon/awssdk/services/secretsmanager/execution.interceptors";
}

@BuildStep
void setup(
BuildProducer<ExtensionSslNativeSupportBuildItem> extensionSslNativeSupport,
BuildProducer<FeatureBuildItem> feature,
BuildProducer<AmazonClientInterceptorsPathBuildItem> interceptors) {

setupExtension(extensionSslNativeSupport, feature, interceptors);
}

@BuildStep(onlyIf = AmazonHttpClients.IsAmazonNettyHttpServicePresent.class)
@Record(ExecutionTime.STATIC_INIT)
public void initBoostrapNettyAsyncTranspor(SecretsManagerConfigBootstrapRecorder recorder,
BuildProducer<SecretsManagerConfigTransportBuildItem> transport,
BuildProducer<RequireAmazonClientBuildItem> requireClientProducer) {

if (buildTimeConfig.asyncClient.type != AsyncClientType.NETTY) {
return;
}

RuntimeValue<AbstractAmazonClientTransportRecorder> transportRecorder = recorder
.createNettyTransportRecorder();

transport.produce(new SecretsManagerConfigTransportBuildItem(Optional.empty(), Optional.of(transportRecorder)));
requireClientProducer
.produce(new RequireAmazonClientBuildItem(Optional.empty(), Optional.of(asyncClientName())));
}

@BuildStep(onlyIf = AmazonHttpClients.IsAmazonAwsCrtHttpServicePresent.class)
@Record(ExecutionTime.STATIC_INIT)
public void initBoostrapCrtAsyncTranspor(SecretsManagerConfigBootstrapRecorder recorder,
BuildProducer<SecretsManagerConfigTransportBuildItem> transport,
BuildProducer<RequireAmazonClientBuildItem> requireClientProducer) {

if (buildTimeConfig.asyncClient.type != AsyncClientType.AWS_CRT) {
return;
}

RuntimeValue<AbstractAmazonClientTransportRecorder> transportRecorder = recorder
.createAwsCrtTransportRecorder();

transport.produce(new SecretsManagerConfigTransportBuildItem(Optional.empty(), Optional.of(transportRecorder)));
requireClientProducer
.produce(new RequireAmazonClientBuildItem(Optional.empty(), Optional.of(asyncClientName())));
}

@BuildStep(onlyIf = AmazonHttpClients.IsAmazonApacheHttpServicePresent.class)
@Record(ExecutionTime.STATIC_INIT)
public void initBoostrapApacheSyncTransport(SecretsManagerConfigBootstrapRecorder recorder,
BuildProducer<SecretsManagerConfigTransportBuildItem> transport,
BuildProducer<RequireAmazonClientBuildItem> requireClientProducer) {

if (buildTimeConfig.syncClient.type != SyncHttpClientBuildTimeConfig.SyncClientType.APACHE) {
return;
}

RuntimeValue<AbstractAmazonClientTransportRecorder> transportRecorder = recorder
.createApacheTransportRecorder();

transport.produce(new SecretsManagerConfigTransportBuildItem(Optional.of(transportRecorder), Optional.empty()));
requireClientProducer
.produce(new RequireAmazonClientBuildItem(Optional.of(syncClientName()), Optional.empty()));
}

@BuildStep(onlyIf = AmazonHttpClients.IsAmazonUrlConnectionHttpServicePresent.class)
@Record(ExecutionTime.STATIC_INIT)
public void initBoostrapUrlConnectionSyncTransport(SecretsManagerConfigBootstrapRecorder recorder,
BuildProducer<SecretsManagerConfigTransportBuildItem> transport,
BuildProducer<RequireAmazonClientBuildItem> requireClientProducer) {

if (buildTimeConfig.syncClient.type != SyncHttpClientBuildTimeConfig.SyncClientType.URL) {
return;
}

RuntimeValue<AbstractAmazonClientTransportRecorder> transportRecorder = recorder
.createUrlConnectionTransportRecorder();

transport.produce(new SecretsManagerConfigTransportBuildItem(Optional.of(transportRecorder), Optional.empty()));
requireClientProducer
.produce(new RequireAmazonClientBuildItem(Optional.of(syncClientName()), Optional.empty()));
}

@BuildStep
@Record(ExecutionTime.STATIC_INIT)
public void setupConfigSource(SecretsManagerConfigBootstrapRecorder recorder,
List<SecretsManagerConfigTransportBuildItem> transportRecorders,
ExecutorBuildItem executor,
BuildProducer<RunTimeConfigBuilderBuildItem> runTimeConfigBuilder) {

if (transportRecorders.isEmpty()) {
return;
}

Optional<RuntimeValue<AbstractAmazonClientTransportRecorder>> syncTransportRecorder = Optional.empty();
Optional<RuntimeValue<AbstractAmazonClientTransportRecorder>> asyncTransportRecorder = Optional.empty();
for (SecretsManagerConfigTransportBuildItem transportRecorder : transportRecorders) {

if (transportRecorder.getSyncTransporter().isPresent()) {
syncTransportRecorder = transportRecorder.getSyncTransporter();
}
if (transportRecorder.getAsyncTransporter().isPresent()) {
asyncTransportRecorder = transportRecorder.getAsyncTransporter();
}
}

if (syncTransportRecorder.isPresent() || asyncTransportRecorder.isPresent()) {

RuntimeValue<AbstractAmazonClientTransportRecorder> transportRecorder = syncTransportRecorder
.orElse(asyncTransportRecorder.get());

recorder.configure(transportRecorder, buildTimeConfig.sdk.interceptors);

if (syncTransportRecorder.isPresent()) {
runTimeConfigBuilder
.produce(new RunTimeConfigBuilderBuildItem(SYNC_FACTORY.toString()));
} else {
runTimeConfigBuilder
.produce(new RunTimeConfigBuilderBuildItem(ASYNC_FACTORY.toString()));
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.quarkus.amazon.secretsmanager.config.deployment;

import java.util.Optional;

import io.quarkus.amazon.common.runtime.AbstractAmazonClientTransportRecorder;
import io.quarkus.builder.item.MultiBuildItem;
import io.quarkus.runtime.RuntimeValue;

public final class SecretsManagerConfigTransportBuildItem extends MultiBuildItem {

private Optional<RuntimeValue<AbstractAmazonClientTransportRecorder>> syncTransporter;
private Optional<RuntimeValue<AbstractAmazonClientTransportRecorder>> asyncTransporter;

public SecretsManagerConfigTransportBuildItem(Optional<RuntimeValue<AbstractAmazonClientTransportRecorder>> syncTransporter,
Optional<RuntimeValue<AbstractAmazonClientTransportRecorder>> asyncTransporter) {
this.syncTransporter = syncTransporter;
this.asyncTransporter = asyncTransporter;
}

public Optional<RuntimeValue<AbstractAmazonClientTransportRecorder>> getSyncTransporter() {
return syncTransporter;
}

public Optional<RuntimeValue<AbstractAmazonClientTransportRecorder>> getAsyncTransporter() {
return asyncTransporter;
}
}
Loading

0 comments on commit 1735c29

Please sign in to comment.