-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Amazon Secrets Manager extension based on pattern of SSM extension
- Loading branch information
1 parent
7c077f6
commit 0709b7a
Showing
22 changed files
with
1,028 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,316 @@ | ||
//// | ||
This guide is maintained in the main Quarkus repository | ||
and pull requests should be submitted there: | ||
https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc | ||
//// | ||
= Amazon Secrets Manager Client | ||
:extension-status: preview | ||
|
||
include::./attributes.adoc[] | ||
|
||
AWS Secrets Manager enables you to replace hardcoded credentials in your code, including passwords, with an API call to Secrets Manager to retrieve the secret programmatically. | ||
This helps ensure the secret can't be compromised by someone examining your code, because the secret no longer exists in the code. | ||
|
||
You can find more information about Secrets Manager at https://docs.aws.amazon.com/secretsmanager/[the AWS Secrets Manager website]. | ||
|
||
NOTE: The Secrets Manager extension is based on https://docs.aws.amazon.com/sdk-for-java/v2/developer-guide/welcome.html[AWS Java SDK 2.x]. | ||
It's a major rewrite of the 1.x code base that offers two programming models (Blocking & Async). | ||
|
||
include::./status-include.adoc[] | ||
|
||
The Quarkus extension supports two programming models: | ||
|
||
* Blocking access using URL Connection HTTP client (by default) or the Apache HTTP Client | ||
* https://docs.aws.amazon.com/sdk-for-java/v2/developer-guide/basics-async.html[Asynchronous programming] based on JDK's `CompletableFuture` objects and the Netty HTTP client. | ||
In this guide, we see how you can get your REST services to use Secrets Manager locally and on AWS. | ||
|
||
== Prerequisites | ||
|
||
To complete this guide, you need: | ||
|
||
* JDK 11+ installed with `JAVA_HOME` configured appropriately | ||
* an IDE | ||
* Apache Maven {maven-version} | ||
* An AWS Account to access the Secrets Manager service | ||
* Docker for your system to run Secrets Manager locally for testing purposes | ||
|
||
=== Set up Secrets Manager locally | ||
|
||
The easiest way to start working with Secrets Manager is to run a local instance as a container. | ||
|
||
[source,bash,subs="verbatim,attributes"] | ||
---- | ||
docker run --rm --name local-secrets-manager --publish 8014:4584 -e SERVICES=secretsmanager -e START_WEB=0 -d localstack/localstack:0.11.1 | ||
---- | ||
This starts a Secrets Manager instance that is accessible on port `8014`. | ||
|
||
Create an AWS profile for your local instance using AWS CLI: | ||
[source,shell,subs="verbatim,attributes"] | ||
---- | ||
$ aws configure --profile localstack | ||
AWS Access Key ID [None]: test-key | ||
AWS Secret Access Key [None]: test-secret | ||
Default region name [None]: us-east-1 | ||
Default output format [None]: | ||
---- | ||
|
||
== Solution | ||
The application built here allows to store and retrieve credentials using Secrets Manager. | ||
|
||
We recommend that you follow the instructions in the next sections and create the application step by step. | ||
However, you can go right to the completed example. | ||
|
||
Clone the Git repository: `git clone {quickstarts-clone-url}`, or download an {quickstarts-archive-url}[archive]. | ||
|
||
The solution is located in the `amazon-secretsmanager-quickstart` {quickstarts-tree-url}/amazon-secretsmanager-quickstart[directory]. | ||
|
||
== Creating the Maven project | ||
|
||
First, we need a new project. Create a new project with the following command: | ||
|
||
[source,bash,subs=attributes+] | ||
---- | ||
mvn io.quarkus.platform:quarkus-maven-plugin:{quarkus-version}:create \ | ||
-DprojectGroupId=org.acme \ | ||
-DprojectArtifactId=amazon-secretsmanager-quickstart \ | ||
-DclassName="org.acme.secretsmanager.QuarkusSecretsManagerSyncResource" \ | ||
-Dpath="/sync" \ | ||
-Dextensions="resteasy,resteasy-jackson,amazon-secretsmanager,resteasy-mutiny" | ||
cd amazon-secretsmanager-quickstart | ||
---- | ||
|
||
This command generates a Maven structure importing the RESTEasy/JAX-RS, Mutiny and Amazon Secrets Manager Client extensions. | ||
After this, the `amazon-secretsmanager` extension has been added to your `pom.xml` as well as the Mutiny support for RESTEasy. | ||
|
||
== Creating JSON REST service | ||
|
||
In this example, we will create an application that allows us to store and retrieve parameters to and from SSM parameter store using a RESTful API. | ||
The example application will demonstrate the two programming models supported by the extension. | ||
|
||
Let's start with an abstract `org.acme.secretsmanager.QuarkusSecretsManagerResource` class to provide the common functionality we will need for both the synchronous and asynchrounous exposures. | ||
|
||
[source,java] | ||
---- | ||
package org.acme.secretsmanager; | ||
import static java.lang.Boolean.TRUE; | ||
import static java.util.stream.Collectors.toMap; | ||
import java.util.Map; | ||
import java.util.stream.Collector; | ||
import javax.annotation.PostConstruct; | ||
import org.eclipse.microprofile.config.inject.ConfigProperty; | ||
import software.amazon.awssdk.services.secretsmanager.model.CreateSecretRequest; | ||
import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueRequest; | ||
public abstract class QuarkusSecretsManagerResource { | ||
public static final String VERSION_STAGE = "AWSCURRENT"; | ||
@ConfigProperty(name = "secret.name") <1> | ||
String secretName; | ||
protected GetSecretValueRequest generateGetSecretValueRequest() { | ||
return GetSecretValueRequest.builder() <2> | ||
.secretId(secretName) | ||
.versionStage(VERSION_STAGE) | ||
.build(); | ||
} | ||
protected CreateSecretRequest generateCreateSecretRequest(String name, String secret) { | ||
return CreateSecretRequest.builder() <3> | ||
.name(name) | ||
.secretString(secret) | ||
.build(); | ||
} | ||
} | ||
---- | ||
|
||
<1> Inject a configured name under which is stored the secret | ||
<2> Generate a request for the credentials with the configured name | ||
<3> Generate a request to create a specific secret | ||
|
||
Now, we can extend the class and create the synchronous implementation in the `org.acme.ssm.QuarkusSecretsManagerSyncResource` class. | ||
|
||
[source,java] | ||
---- | ||
package org.acme.secretsmanager; | ||
import java.util.Map; | ||
import javax.inject.Inject; | ||
import javax.ws.rs.Consumes; | ||
import javax.ws.rs.DefaultValue; | ||
import javax.ws.rs.GET; | ||
import javax.ws.rs.PUT; | ||
import javax.ws.rs.Path; | ||
import javax.ws.rs.PathParam; | ||
import javax.ws.rs.Produces; | ||
import javax.ws.rs.QueryParam; | ||
import javax.ws.rs.core.MediaType; | ||
import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient; | ||
@Path("/sync") | ||
public class QuarkusSecretsManagerSyncResource extends QuarkusSsmResource { | ||
@Inject <1> | ||
SecretsManagerClient secretsManagerClient; | ||
@GET | ||
@Produces(MediaType.APPLICATION_JSON) | ||
public String getSecret() { | ||
return secretsManagerClient.getSecretValue(generateGetSecretValueRequest()).secretString(); | ||
} | ||
@POST | ||
@Path("/{name}") | ||
@Consumes(MediaType.TEXT_PLAIN) | ||
public void createSecret(@PathParam("name") String name, String value) { | ||
secretsManagerClient.createSecret(generateCreateSecretRequest(name, secret)); | ||
} | ||
} | ||
---- | ||
|
||
<1> Inject the client provided by the amazon-secretsmanager extension | ||
|
||
Using the Amazon Secrets Manager SDK, we can easily store and retrieve secrets. | ||
|
||
== Configuring Secrets Manager clients | ||
|
||
Both Secrets Manager clients (sync and async) are configurable via the `application.properties` file that can be provided in the `src/main/resources` directory. | ||
Additionally, you need to add to the classpath a proper implementation of the sync client. By default the extension uses the URL connection HTTP client, so | ||
you need to add a URL connection client dependency to the `pom.xml` file: | ||
|
||
[source,xml] | ||
---- | ||
<dependency> | ||
<groupId>software.amazon.awssdk</groupId> | ||
<artifactId>url-connection-client</artifactId> | ||
</dependency> | ||
---- | ||
|
||
If you want to use Apache HTTP client instead, configure it as follows: | ||
|
||
[source,properties] | ||
---- | ||
quarkus.secretsmanager.sync-client.type=apache | ||
---- | ||
|
||
And add the following dependency to the application `pom.xml`: | ||
|
||
[source,xml] | ||
---- | ||
<dependency> | ||
<groupId>software.amazon.awssdk</groupId> | ||
<artifactId>apache-client</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.quarkus</groupId> | ||
<artifactId>quarkus-apache-httpclient</artifactId> | ||
</dependency> | ||
---- | ||
|
||
If you're going to use a local Secrets Manager instance, configure it as follows: | ||
|
||
[source,properties] | ||
---- | ||
quarkus.secretsmanager.endpoint-override=http://localhost:8014 <1> | ||
quarkus.secretsmanager.aws.region=us-east-1 <2> | ||
quarkus.secretsmanager.aws.credentials.type=static <3> | ||
quarkus.secretsmanager.aws.credentials.static-provider.access-key-id=test-key | ||
quarkus.secretsmanager.aws.credentials.static-provider.secret-access-key=test-secret | ||
---- | ||
|
||
<1> Override the Secret Manager client to use localstack instead of the actual AWS service | ||
<2> Localstack defaults to `us-east-1` | ||
<3> The `static` credentials provider lets you set the `access-key-id` and `secret-access-key` directly | ||
|
||
If you want to work with an AWS account, you can simply remove or comment out all Amazon SSM related properties. By default, the Secrets Manager client extension will use the `default` credentials provider chain that looks for credentials in this order: | ||
|
||
include::./amazon-credentials.adoc[] | ||
|
||
And the region from your AWS CLI profile will be used. | ||
|
||
== Next steps | ||
|
||
=== Packaging | ||
|
||
Packaging your application is as simple as `./mvnw clean package`. | ||
It can then be run with `java -Dparameters.path=/quarkus/is/awesome/ -jar target/quarkus-app/quarkus-run.jar`. | ||
|
||
With GraalVM installed, you can also create a native executable binary: `./mvnw clean package -Dnative`. | ||
Depending on your system, that will take some time. | ||
|
||
=== Going asynchronous | ||
|
||
Thanks to the AWS SDK v2.x used by the Quarkus extension, you can use the asynchronous programming model out of the box. | ||
|
||
Create a `org.acme.secretsmanager.QuarkusSecretsManagerAsyncResource` REST resource that will be similar to our `QuarkusSecretsManagerSyncResource` but using an asynchronous programming model. | ||
|
||
[source,java] | ||
---- | ||
package org.acme.secretsmanager; | ||
import java.util.Map; | ||
import javax.inject.Inject; | ||
import javax.ws.rs.Consumes; | ||
import javax.ws.rs.DefaultValue; | ||
import javax.ws.rs.GET; | ||
import javax.ws.rs.PUT; | ||
import javax.ws.rs.Path; | ||
import javax.ws.rs.PathParam; | ||
import javax.ws.rs.Produces; | ||
import javax.ws.rs.QueryParam; | ||
import javax.ws.rs.core.MediaType; | ||
import io.smallrye.mutiny.Uni; | ||
import software.amazon.awssdk.services.secretsmanager.SecretsManagerAsyncClient; | ||
@Path("/async") | ||
public class QuarkusSecretsManagerAsyncResource extends QuarkusSecretsManagerResource { | ||
@Inject | ||
SecretsManagerAsyncClient secretsManagerAsyncClient; | ||
@GET | ||
@Produces(MediaType.APPLICATION_JSON) | ||
public Uni<String> getSecret() { | ||
return Uni.createFrom().completionStage(secretsManagerAsyncClient.getSecretValue(generateGetSecretValueRequest())) | ||
.onItem().transform(r -> r.secretString()); | ||
} | ||
@PUT | ||
@Path("/{name}") | ||
@Consumes(MediaType.TEXT_PLAIN) | ||
public Uni<Void> createSecret(@PathParam("name") String name, String value) { | ||
return Uni.createFrom().completionStage(secretsManagerAsyncClient.createSecret(generateCreateSecretRequest(name, secret))) | ||
.onItem().transform(r -> null); | ||
} | ||
} | ||
---- | ||
|
||
Note that the `SecretsManagerAsyncClient` behaves just like the `SecretsManagerClient`, but returns `CompletionStage` objects which we use to create `Uni` instances, and then transform the emitted item. | ||
|
||
To enable the asynchronous client, we also need to add the Netty HTTP client dependency to the `pom.xml`: | ||
|
||
[source,xml] | ||
---- | ||
<dependency> | ||
<groupId>software.amazon.awssdk</groupId> | ||
<artifactId>netty-nio-client</artifactId> | ||
</dependency> | ||
---- | ||
|
||
== Configuration Reference | ||
|
||
include::{generated-dir}/config/quarkus-amazon-secretsmanager.adoc[opts=optional, leveloffset=+1] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.