Skip to content

Commit

Permalink
Add sample showing AWS encryption SDK (#700)
Browse files Browse the repository at this point in the history
Add sample showing AWS encryption SDK
  • Loading branch information
Quinn-With-Two-Ns authored Nov 4, 2024
1 parent 5c96864 commit 56fb8d7
Show file tree
Hide file tree
Showing 6 changed files with 294 additions and 1 deletion.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,11 @@ See the README.md file in each main sample directory for cut/paste Gradle comman

- [**Set up OpenTracing and/or OpenTelemetry with Jaeger**](/core/src/main/java/io/temporal/samples/tracing): Demonstrates how to set up OpenTracing and/or OpenTelemetry and view traces using Jaeger.

#### Encryption Support

- [**Encrypted Payloads**](/core/src/main/java/io/temporal/samples/encryptedpayloads): Demonstrates how to use simple codec to encrypt and decrypt payloads.

- [**AWS Encryption SDK**](/core/src/main/java/io/temporal/samples/keymanagementencryption/awsencryptionsdk): Demonstrates how to use the AWS Encryption SDK to encrypt and decrypt payloads with AWS KMS.

<!-- @@@SNIPEND -->

Expand Down
7 changes: 7 additions & 0 deletions core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ dependencies {
implementation group: 'net.thisptr', name: 'jackson-jq', version: '1.0.0-preview.20240207'
implementation group: 'commons-cli', name: 'commons-cli', version: '1.9.0'

// Used in AWS Encryption SDK sample
implementation group: 'com.amazonaws', name: 'aws-encryption-sdk-java', version: '3.0.1'
implementation("software.amazon.cryptography:aws-cryptographic-material-providers:1.0.2")
implementation(platform("software.amazon.awssdk:bom:2.20.91"))
implementation("software.amazon.awssdk:kms")
implementation("software.amazon.awssdk:dynamodb")

// we don't update it to 2.1.0 because 2.1.0 requires Java 11
implementation 'com.codingrodent:jackson-json-crypto:1.1.0'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ public String getGreeting(String name) {
}

/** Simple activity implementation, that concatenates two strings. */
static class GreetingActivitiesImpl implements GreetingActivities {
public static class GreetingActivitiesImpl implements GreetingActivities {
private static final Logger log = LoggerFactory.getLogger(GreetingActivitiesImpl.class);

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Copyright (c) 2020 Temporal Technologies, Inc. All Rights Reserved
*
* Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Modifications copyright (C) 2017 Uber Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"). You may not
* use this file except in compliance with the License. A copy of the License is
* located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

package io.temporal.samples.keymanagementencryption.awsencryptionsdk;

import io.temporal.client.WorkflowClient;
import io.temporal.client.WorkflowClientOptions;
import io.temporal.client.WorkflowOptions;
import io.temporal.common.converter.CodecDataConverter;
import io.temporal.common.converter.DefaultDataConverter;
import io.temporal.samples.hello.HelloActivity;
import io.temporal.serviceclient.WorkflowServiceStubs;
import io.temporal.worker.Worker;
import io.temporal.worker.WorkerFactory;
import java.util.Collections;
import software.amazon.cryptography.materialproviders.IKeyring;
import software.amazon.cryptography.materialproviders.MaterialProviders;
import software.amazon.cryptography.materialproviders.model.CreateAwsKmsMultiKeyringInput;
import software.amazon.cryptography.materialproviders.model.MaterialProvidersConfig;

public class EncryptedPayloads {

static final String TASK_QUEUE = "EncryptedPayloads";

public static void main(String[] args) {
// Configure your keyring. In this sample we are configuring a basic AWS KMS keyring, but the
// AWS encryption SDK has multiple options depending on your use case.
//
// See more here:
// https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/which-keyring.html
String generatorKey = System.getenv("AWS_KEY_ARN");

final MaterialProviders materialProviders =
MaterialProviders.builder()
.MaterialProvidersConfig(MaterialProvidersConfig.builder().build())
.build();
// Create the AWS KMS keyring
final CreateAwsKmsMultiKeyringInput keyringInput =
CreateAwsKmsMultiKeyringInput.builder().generator(generatorKey).build();
final IKeyring kmsKeyring = materialProviders.CreateAwsKmsMultiKeyring(keyringInput);
// gRPC stubs wrapper that talks to the local docker instance of temporal service.
WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs();
// client that can be used to start and signal workflows
WorkflowClient client =
WorkflowClient.newInstance(
service,
WorkflowClientOptions.newBuilder()
.setDataConverter(
new CodecDataConverter(
DefaultDataConverter.newDefaultInstance(),
// Create our encryption codec
Collections.singletonList(new KeyringCodec(kmsKeyring))))
.build());

// worker factory that can be used to create workers for specific task queues
WorkerFactory factory = WorkerFactory.newInstance(client);
// Worker that listens on a task queue and hosts both workflow and activity implementations.
Worker worker = factory.newWorker(TASK_QUEUE);
// Register the workflows and activities
worker.registerWorkflowImplementationTypes(HelloActivity.GreetingWorkflowImpl.class);
worker.registerActivitiesImplementations(new HelloActivity.GreetingActivitiesImpl());
// Start listening to the workflow and activity task queues.
factory.start();

// Start a workflow execution.
HelloActivity.GreetingWorkflow workflow =
client.newWorkflowStub(
HelloActivity.GreetingWorkflow.class,
WorkflowOptions.newBuilder().setTaskQueue(TASK_QUEUE).build());
// Execute a workflow waiting for it to complete.
String greeting = workflow.getGreeting("My Secret Friend");
System.out.println(greeting);
System.exit(0);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
/*
* Copyright (c) 2020 Temporal Technologies, Inc. All Rights Reserved
*
* Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Modifications copyright (C) 2017 Uber Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"). You may not
* use this file except in compliance with the License. A copy of the License is
* located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

package io.temporal.samples.keymanagementencryption.awsencryptionsdk;

import com.amazonaws.encryptionsdk.AwsCrypto;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import io.temporal.api.common.v1.Payload;
import io.temporal.common.converter.EncodingKeys;
import io.temporal.payload.codec.PayloadCodec;
import io.temporal.payload.context.ActivitySerializationContext;
import io.temporal.payload.context.HasWorkflowSerializationContext;
import io.temporal.payload.context.SerializationContext;
import io.temporal.workflow.unsafe.WorkflowUnsafe;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.jetbrains.annotations.NotNull;
import software.amazon.cryptography.materialproviders.IKeyring;

/**
* KeyringCodec is a {@link PayloadCodec} that encrypts and decrypts payloads using the AWS
* Encryption SDK. It uses the provided {@link IKeyring} to encrypt and decrypt payloads. It can
* optionally support using a {@link SerializationContext}.
*/
class KeyringCodec implements PayloadCodec {
// Metadata encoding key for the AWS Encryption SDK
static final ByteString METADATA_ENCODING =
ByteString.copyFrom("awsencriptionsdk/binary/encrypted", StandardCharsets.UTF_8);

private final AwsCrypto crypto;
private final IKeyring kmsKeyring;
private final boolean useSerializationContext;
@Nullable private final SerializationContext serializationContext;

/**
* Constructs a new KeyringCodec with the provided {@link IKeyring}. The codec will not use a
* {@link SerializationContext}.
*
* @param kmsKeyring the keyring to use for encryption and decryption.
*/
public KeyringCodec(IKeyring kmsKeyring) {
this.crypto = AwsCrypto.standard();
this.kmsKeyring = kmsKeyring;
this.useSerializationContext = false;
this.serializationContext = null;
}

/**
* Constructs a new KeyringCodec with the provided {@link IKeyring}.
*
* @param crypto the AWS Crypto object to use for encryption and decryption.
* @param kmsKeyring the keyring to use for encryption and decryption.
* @param useSerializationContext whether to use a {@link SerializationContext} for encoding and
* decoding payloads.
*/
public KeyringCodec(AwsCrypto crypto, IKeyring kmsKeyring, boolean useSerializationContext) {
this.crypto = crypto;
this.kmsKeyring = kmsKeyring;
this.useSerializationContext = useSerializationContext;
this.serializationContext = null;
}

private KeyringCodec(
AwsCrypto crypto, IKeyring kmsKeyring, SerializationContext serializationContext) {
this.crypto = crypto;
this.kmsKeyring = kmsKeyring;
this.useSerializationContext = true;
this.serializationContext = serializationContext;
}

@NotNull
@Override
public List<Payload> encode(@NotNull List<Payload> payloads) {
// Disable deadlock detection for encoding payloads because this may make a network call
// to encrypt the data.
return WorkflowUnsafe.deadlockDetectorOff(
() -> payloads.stream().map(this::encodePayload).collect(Collectors.toList()));
}

@NotNull
@Override
public List<Payload> decode(@NotNull List<Payload> payloads) {
// Disable deadlock detection for decoding payloads because this may make a network call
// to decrypt the data.
return WorkflowUnsafe.deadlockDetectorOff(
() -> payloads.stream().map(this::decodePayload).collect(Collectors.toList()));
}

@NotNull
@Override
public PayloadCodec withContext(@Nonnull SerializationContext context) {
if (!useSerializationContext) {
return this;
}
return new KeyringCodec(crypto, kmsKeyring, context);
}

private Map<String, String> getEncryptionContext() {
// If we are not using a serialization context, return an empty map
// There may not be a serialization context if certain cases, such as when the codec is used
// for encoding/decoding payloads for a Nexus operation.
if (!useSerializationContext
|| serializationContext == null
|| !(serializationContext instanceof HasWorkflowSerializationContext)) {
return Collections.emptyMap();
}
String workflowId = ((HasWorkflowSerializationContext) serializationContext).getWorkflowId();
String activityType = null;
if (serializationContext instanceof ActivitySerializationContext) {
activityType = ((ActivitySerializationContext) serializationContext).getActivityType();
}
String signature = activityType != null ? workflowId + activityType : workflowId;
return Collections.singletonMap("signature", signature);
}

private Payload encodePayload(Payload payload) {
byte[] plaintext = payload.toByteArray();
byte[] ciphertext =
crypto.encryptData(kmsKeyring, plaintext, getEncryptionContext()).getResult();
return Payload.newBuilder()
.setData(ByteString.copyFrom(ciphertext))
.putMetadata(EncodingKeys.METADATA_ENCODING_KEY, METADATA_ENCODING)
.build();
}

private Payload decodePayload(Payload payload) {
if (METADATA_ENCODING.equals(
payload.getMetadataOrDefault(EncodingKeys.METADATA_ENCODING_KEY, null))) {
byte[] ciphertext = payload.getData().toByteArray();
byte[] plaintext =
crypto.decryptData(kmsKeyring, ciphertext, getEncryptionContext()).getResult();
try {
return Payload.parseFrom(plaintext);
} catch (InvalidProtocolBufferException e) {
throw new RuntimeException(e);
}
}
return payload;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
## AWS Encryption SDK Sample

This sample demonstrates how a user can leverage the [AWS Encryption](https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/java.html) SDK to build a `PayloadCodec` to encrypt and decrypt payloads using [AWS KMS](https://aws.amazon.com/kms/) and envelope encryption.

### About the AWS Encryption SDK:

>The AWS Encryption SDK is a client-side encryption library designed to make it easy for everyone to encrypt and decrypt data using industry standards and best practices. It enables you to focus on the core functionality of your application, rather than on how to best encrypt and decrypt your data. The AWS Encryption SDK is provided free of charge under the Apache 2.0 license.
For more details please see [Amazons Documentation](https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/introduction.html)

### Choosing a Key Ring

This sample uses am [AWS KMS keyring](https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-kms-keyring.html). This approach is convenient as you don't need to manage or secure your own keys. One drawback of this approach is it will require a call to KMS every time you need encrypt or decrypt data. If this is a concern you may want to consider using an [AWS KMS Hierarchical keyring](https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-hierarchical-keyring.html).

Note: You can also use the AWS Encryption SDK without any AWS services using the raw keyrings.

For more details please see [Amazons Documentation on choosing a key ring](https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/which-keyring.html).

### Running this sample

Make sure your AWS account credentials are up-to-date and can access KMS.

Export the following environment variables
- `AWS_KEY_ARN`: Your AWS key ARN.

```bash
./gradlew -q execute -PmainClass=io.temporal.samples.keymanagementencryption.awsencryptionsdk.EncryptedPayloads
```

0 comments on commit 56fb8d7

Please sign in to comment.