-
Notifications
You must be signed in to change notification settings - Fork 178
Add sample showing AWS encryption SDK #700
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Quinn-With-Two-Ns
merged 2 commits into
temporalio:main
from
Quinn-With-Two-Ns:aws-encryption-sdk
Nov 4, 2024
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 | ||
| ``` |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this call potentially slow (i.e. meaning ever makes a network call, even once lazily)? Should the deadlock detector be disabled or is it already disabled?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah good point, I added a line to disable the deadlock detector.