-
Notifications
You must be signed in to change notification settings - Fork 151
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add sample showing AWS encryption SDK (#700)
Add sample showing AWS encryption SDK
- Loading branch information
1 parent
5c96864
commit 56fb8d7
Showing
6 changed files
with
294 additions
and
1 deletion.
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
91 changes: 91 additions & 0 deletions
91
.../java/io/temporal/samples/keymanagementencryption/awsencryptionsdk/EncryptedPayloads.java
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,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); | ||
} | ||
} |
162 changes: 162 additions & 0 deletions
162
.../main/java/io/temporal/samples/keymanagementencryption/awsencryptionsdk/KeyringCodec.java
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,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; | ||
} | ||
} |
28 changes: 28 additions & 0 deletions
28
...ain/java/io/temporal/samples/keymanagementencryption/awsencryptionsdk/README.md
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,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 | ||
``` |