Skip to content

Commit b9a4301

Browse files
committed
SES + KMS guides
1 parent d1c9e1c commit b9a4301

File tree

2 files changed

+508
-0
lines changed

2 files changed

+508
-0
lines changed
Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
////
2+
This guide is maintained in the main Quarkus repository
3+
and pull requests should be submitted there:
4+
https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc
5+
////
6+
= Quarkus - Amazon KMS Client
7+
:extension-status: preview
8+
9+
include::./attributes.adoc[]
10+
11+
Amazon Key Management Service (KMS) is a service that allows you to create and control the keys used to encrypt or digitally sign your data.
12+
Using KMS, you can create and manage cryptographic keys and control their use across a wide range of AWS services and in your application.
13+
14+
You can find more information about KMS at https://aws.amazon.com/kms/[the Amazon KMS website].
15+
16+
NOTE: The KMS extension is based on https://docs.aws.amazon.com/sdk-for-java/v2/developer-guide/welcome.html[AWS Java SDK 2.x].
17+
It's a major rewrite of the 1.x code base that offers two programming models (Blocking & Async).
18+
19+
include::./status-include.adoc[]
20+
21+
The Quarkus extension supports two programming models:
22+
23+
* Blocking access using URL Connection HTTP client (by default) or the Apache HTTP Client
24+
* 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.
25+
26+
In this guide, we see how you can get your REST services to use KMS locally and on AWS.
27+
28+
== Prerequisites
29+
30+
To complete this guide, you need:
31+
32+
* JDK 1.8+ installed with `JAVA_HOME` configured appropriately
33+
* an IDE
34+
* Apache Maven {maven-version}
35+
* An AWS Account to access the KMS service
36+
* Docker for your system to run KMS locally for testing purposes
37+
38+
=== Set up KMS locally
39+
40+
The easiest way to start working with KMS is to run a local instance as a container.
41+
42+
[source,shell,subs="verbatim,attributes"]
43+
----
44+
docker run --rm --name local-kms 8011:4599 -e SERVICES=kms -e START_WEB=0 -d localstack/localstack:0.11.1
45+
----
46+
This starts a KMS instance that is accessible on port `8011`.
47+
48+
Create an AWS profile for your local instance using AWS CLI:
49+
[source,shell,subs="verbatim,attributes"]
50+
----
51+
$ aws configure --profile localstack
52+
AWS Access Key ID [None]: test-key
53+
AWS Secret Access Key [None]: test-secret
54+
Default region name [None]: us-east-1
55+
Default output format [None]:
56+
----
57+
58+
=== Create a KMS master key
59+
60+
Create a KMS master key queue using AWS CLI and store in `MASTER_KEY_ARN` environment variable.
61+
62+
[source,shell,subs="verbatim,attributes"]
63+
----
64+
MASTER_KEY_ARN=`aws kms create-key --profile localstack --endpoint-url=http://localhost:8011 | cut -f3`
65+
----
66+
Generate a key data as 256-bit symnmetric key (AES 256)
67+
[source,shell,subs="verbatim,attributes"]
68+
----
69+
aws kms generate-data-key --key-id $MASTER_KEY_ARN --key-spec AES_256 --profile localstack --endpoint-url=http://localhost:8011
70+
----
71+
72+
Or, if you want to use your AWS account create a key using your default profile
73+
[source,shell,subs="verbatim,attributes"]
74+
----
75+
MASTER_KEY_ARN=`aws kms create-key | cut -f3`
76+
aws kms generate-data-key --key-id $MASTER_KEY_ARN --key-spec AES_256
77+
----
78+
79+
== Solution
80+
The application built here allows to encrypt and decrypt text messages using a master key created on AWS KMS.
81+
82+
We recommend that you follow the instructions in the next sections and create the application step by step.
83+
However, you can go right to the completed example.
84+
85+
Clone the Git repository: `git clone {quickstarts-clone-url}`, or download an {quickstarts-archive-url}[archive].
86+
87+
The solution is located in the `amazon-kms-quickstart` {quickstarts-tree-url}/amazon-kms-quickstart[directory].
88+
89+
== Creating the Maven project
90+
91+
First, we need a new project. Create a new project with the following command:
92+
93+
[source,shell,subs=attributes+]
94+
----
95+
mvn io.quarkus:quarkus-maven-plugin:{quarkus-version}:create \
96+
-DprojectGroupId=org.acme \
97+
-DprojectArtifactId=amazon-kms-quickstart \
98+
-DclassName="org.acme.kms.QuarkusKmsSyncResource" \
99+
-Dpath="/sync" \
100+
-Dextensions="resteasy-jsonb,amazon-kms,resteasy-mutiny"
101+
cd amazon-kms-quickstart
102+
----
103+
104+
This command generates a Maven structure importing the RESTEasy/JAX-RS, Mutiny and Amazon KMS Client extensions.
105+
After this, the `amazon-kms` extension has been added to your `pom.xml` as well as the Mutiny support for RESTEasy.
106+
107+
== Creating JSON REST service
108+
109+
In this example, we will create an application that allows to encrypt and decrypt text message provided in the request.
110+
The example application will demonstrate the two programming models supported by the extension.
111+
112+
Lets create a `org.acme.kms.QuarkusKmsSyncResource` that will provide an API to encrypt and decrypt message using the synchronous client.
113+
114+
[source,java]
115+
----
116+
package org.acme.kms;
117+
118+
import javax.inject.Inject;
119+
import javax.ws.rs.Consumes;
120+
import javax.ws.rs.POST;
121+
import javax.ws.rs.Path;
122+
import javax.ws.rs.Produces;
123+
import javax.ws.rs.core.MediaType;
124+
import org.apache.commons.codec.binary.Base64;
125+
import org.eclipse.microprofile.config.inject.ConfigProperty;
126+
import software.amazon.awssdk.core.SdkBytes;
127+
import software.amazon.awssdk.services.kms.KmsClient;
128+
import software.amazon.awssdk.services.kms.model.DecryptResponse;
129+
130+
@Path("/sync")
131+
@Produces(MediaType.TEXT_PLAIN)
132+
@Consumes(MediaType.TEXT_PLAIN)
133+
public class QuarkusKmsSyncResource {
134+
135+
@Inject
136+
KmsClient kms;
137+
138+
@ConfigProperty(name = "key.arn")
139+
String keyArn;
140+
141+
@POST
142+
@Path("/encrypt")
143+
public String encrypt(String data) {
144+
SdkBytes encryptedBytes = kms.encrypt(req -> req.keyId(keyArn).plaintext(SdkBytes.fromUtf8String(data))).ciphertextBlob();
145+
146+
return Base64.encodeBase64String(encryptedBytes.asByteArray());
147+
}
148+
149+
@POST
150+
@Path("/decrypt")
151+
public String decrypt(String data) {
152+
SdkBytes encryptedData = SdkBytes.fromByteArray(Base64.decodeBase64(data.getBytes()));
153+
DecryptResponse decrypted = kms.decrypt(req -> req.keyId(keyArn).ciphertextBlob(encryptedData));
154+
155+
return decrypted.plaintext().asUtf8String();
156+
}
157+
}
158+
----
159+
An encrypted message is in the form of a bytes array. To return it to the user we need to encode it as Base64 string in the `encrypt` endpoint.
160+
On the `decrypt` endpoint we need to decode from the Base64 string back to the bytes array before sending it out to the KMS client.
161+
162+
== Configuring KMS clients
163+
164+
Both KMS clients (sync and async) are configurable via the `application.properties` file that can be provided in the `src/main/resources` directory.
165+
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
166+
you need to add a URL connection client dependency to the `pom.xml` file:
167+
168+
[source,xml]
169+
----
170+
<dependency>
171+
<groupId>software.amazon.awssdk</groupId>
172+
<artifactId>url-connection-client</artifactId>
173+
</dependency>
174+
----
175+
176+
If you want to use Apache HTTP client instead, configure it as follows:
177+
[source,properties]
178+
----
179+
quarkus.kms.sync-client.type=apache
180+
----
181+
182+
And add the following dependency to the application `pom.xml`:
183+
[source,xml]
184+
----
185+
<dependency>
186+
<groupId>software.amazon.awssdk</groupId>
187+
<artifactId>apache-client</artifactId>
188+
</dependency>
189+
----
190+
191+
If you're going to use a local KMS instance, configure it as follows:
192+
193+
[source,properties]
194+
----
195+
quarkus.kms.endpoint-override=http://localhost:8011
196+
197+
quarkus.kms.aws.region=us-east-1
198+
quarkus.kms.aws.credentials.type=static
199+
quarkus.kms.aws.credentials.static-provider.access-key-id=test-key
200+
quarkus.kms.aws.credentials.static-provider.secret-access-key=test-secret
201+
----
202+
203+
- `quarkus.kms.aws.region` - It's required by the client, but since you're using a local KMS instance use `us-east-1` as it's a default region of localstack's KMS.
204+
- `quarkus.kms.aws.credentials.type` - Set `static` credentials provider with any values for `access-key-id` and `secret-access-key`
205+
- `quarkus.kms.endpoint-override` - Override the KMS client to use a local instance instead of an AWS service
206+
207+
If you want to work with an AWS account, you can simply remove or comment out all Amazon KMS related properties. By default, the KMS client extension
208+
will use the `default` credentials provider chain that looks for credentials in this order:
209+
- Java System Properties - `aws.accessKeyId` and `aws.secretKey`
210+
* Environment Variables - `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`
211+
* Credential profiles file at the default location (`~/.aws/credentials`) shared by all AWS SDKs and the AWS CLI
212+
* Credentials delivered through the Amazon EC2 container service if the `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` environment variable is set and the security manager has permission to access the variable,
213+
* Instance profile credentials delivered through the Amazon EC2 metadata service
214+
215+
And the region from your AWS CLI profile will be used.
216+
217+
== Next steps
218+
219+
=== Packaging
220+
221+
Packaging your application is as simple as `./mvnw clean package`.
222+
It can be run with `java -Dkey.arn=$MASTER_KEY_ARN -jar target/amazon-kms-quickstart-1.0-SNAPSHOT-runner.jar`.
223+
224+
With GraalVM installed, you can also create a native executable binary: `./mvnw clean package -Dnative`.
225+
Depending on your system, that will take some time.
226+
227+
=== Going asynchronous
228+
229+
Thanks to the AWS SDK v2.x used by the Quarkus extension, you can use the asynchronous programming model out of the box.
230+
231+
Create a `org.acme.kms.QuarkusKmsAsyncResource` REST resource that will be similar to our `QuarkusKmsSyncResource` but using an asynchronous programming model.
232+
233+
[source,java]
234+
----
235+
package org.acme.kms;
236+
237+
import io.smallrye.mutiny.Uni;
238+
import javax.inject.Inject;
239+
import javax.ws.rs.Consumes;
240+
import javax.ws.rs.POST;
241+
import javax.ws.rs.Path;
242+
import javax.ws.rs.Produces;
243+
import javax.ws.rs.core.MediaType;
244+
import org.apache.commons.codec.binary.Base64;
245+
import org.eclipse.microprofile.config.inject.ConfigProperty;
246+
import software.amazon.awssdk.core.SdkBytes;
247+
import software.amazon.awssdk.services.kms.KmsAsyncClient;
248+
import software.amazon.awssdk.services.kms.model.DecryptResponse;
249+
import software.amazon.awssdk.services.kms.model.EncryptResponse;
250+
251+
@Path("/async")
252+
@Produces(MediaType.TEXT_PLAIN)
253+
@Consumes(MediaType.TEXT_PLAIN)
254+
public class QuarkusKmsAsyncResource {
255+
256+
@Inject
257+
KmsAsyncClient kms;
258+
259+
@ConfigProperty(name = "key.arn")
260+
String keyArn;
261+
262+
@POST
263+
@Path("/encrypt")
264+
public Uni<String> encrypt(String data) {
265+
return Uni.createFrom().completionStage(kms.encrypt(req -> req.keyId(keyArn).plaintext(SdkBytes.fromUtf8String(data))))
266+
.onItem().apply(EncryptResponse::ciphertextBlob)
267+
.onItem().apply(blob -> Base64.encodeBase64String(blob.asByteArray()));
268+
}
269+
270+
@POST
271+
@Path("/decrypt")
272+
public Uni<String> decrypt(String data) {
273+
return Uni.createFrom().item(SdkBytes.fromByteArray(Base64.decodeBase64(data.getBytes())))
274+
.onItem().produceCompletionStage(msg -> kms.decrypt(req -> req.keyId(keyArn).ciphertextBlob(msg)))
275+
.onItem().apply(DecryptResponse::plaintext)
276+
.onItem().apply(SdkBytes::asUtf8String);
277+
}
278+
}
279+
----
280+
We create `Uni` instances from the `CompletionStage` objects returned by the asynchronous KMS client, and then transform the emitted item.
281+
282+
And we need to add the Netty HTTP client dependency to the `pom.xml`:
283+
284+
[source,xml]
285+
----
286+
<dependency>
287+
<groupId>software.amazon.awssdk</groupId>
288+
<artifactId>netty-nio-client</artifactId>
289+
</dependency>
290+
----
291+
292+
== Configuration Reference
293+
294+
include::{generated-dir}/config/quarkus-amazon-kms.adoc[opts=optional, leveloffset=+1]

0 commit comments

Comments
 (0)