Skip to content

Commit 2bccda4

Browse files
committed
docs(dataconverter): clarify security guidance
Signed-off-by: “Kevin” <kevlar_ksb@yahoo.com>
1 parent a5d80e6 commit 2bccda4

4 files changed

Lines changed: 29 additions & 26 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ These samples demonstrate various capabilities of Java Cadence client and server
2929

3030
* **Custom Workflow Controls** ([`com.uber.cadence.samples.query`](src/main/java/com/uber/cadence/samples/query/)) — workflow queries that return **markdown** for Cadence Web (Markdoc buttons that **signal** workflows or **start** new workflows). **Requires Cadence Web v4.0.14+.** Copy-paste run instructions: [query samples README](src/main/java/com/uber/cadence/samples/query/README.md).
3131

32-
* **DataConverter Samples** ([`com.uber.cadence.samples.dataconverter`](src/main/java/com/uber/cadence/samples/dataconverter/)) — three production-ready custom `DataConverter` patterns (gzip compression, AES-256-GCM encryption, and S3 / claim-check offload) that transparently transform every workflow input, output, and activity parameter. Copy-paste run instructions: [dataconverter samples README](src/main/java/com/uber/cadence/samples/dataconverter/README.md).
32+
* **DataConverter Samples** ([`com.uber.cadence.samples.dataconverter`](src/main/java/com/uber/cadence/samples/dataconverter/)) — three custom `DataConverter` patterns (gzip compression, AES-256-GCM encryption, and BlobStore / S3 claim-check offload) that transparently transform every workflow input, output, and activity parameter. Copy-paste run instructions: [dataconverter samples README](src/main/java/com/uber/cadence/samples/dataconverter/README.md).
3333

3434
## Get the Samples
3535

src/main/java/com/uber/cadence/samples/dataconverter/EncryptedDataConverterWorkflow.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@
2525

2626
/**
2727
* Demonstrates AES-256-GCM encryption as a Cadence {@code DataConverter}. Every workflow input,
28-
* output, and activity parameter is encrypted before being written to Cadence history. Without
29-
* the key, the data is opaque to anyone browsing workflow history — including Cadence operators.
28+
* output, and activity parameter is encrypted before being written to Cadence history. Without the
29+
* key, payloads in workflow history are unreadable to anyone browsing history — including Cadence
30+
* operators. Application logs, metrics, and search attributes are not encrypted by a DataConverter.
3031
*
3132
* <p>The workflow takes no inputs and builds its own sensitive payload internally so it can be
3233
* started from the Cadence CLI without bundling the encryption key into the caller.
@@ -78,9 +79,10 @@ public static SensitiveCustomerRecord createSensitiveCustomerRecord() {
7879
public interface WorkflowIface {
7980

8081
@WorkflowMethod(
81-
name = DataConverterConstants.ENCRYPTION_WORKFLOW_TYPE,
82-
executionStartToCloseTimeoutSeconds = 60,
83-
taskList = DataConverterConstants.TASK_LIST_ENCRYPTION)
82+
name = DataConverterConstants.ENCRYPTION_WORKFLOW_TYPE,
83+
executionStartToCloseTimeoutSeconds = 60,
84+
taskList = DataConverterConstants.TASK_LIST_ENCRYPTION
85+
)
8486
SensitiveCustomerRecord run();
8587
}
8688

src/main/java/com/uber/cadence/samples/dataconverter/EncryptedJsonDataConverter.java

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,14 @@
3232
* AES-256-GCM.
3333
*
3434
* <p>Every workflow input, output, and activity parameter is encrypted before being written to
35-
* Cadence history. Without the key, the data stored by the Cadence server — including any operator
36-
* browsing workflow history — is completely opaque.
35+
* Cadence history. Without the key, payloads stored by the Cadence server are unreadable to
36+
* operators browsing workflow history. Logs, metrics, and search attributes are separate disclosure
37+
* surfaces and must be handled separately.
3738
*
38-
* <p>Output layout: {@code nonce(12 bytes) || ciphertext+tag(16 bytes)}. The random nonce means
39-
* the same plaintext produces different ciphertext on every call, preventing replay detection by
40-
* an attacker who observes Cadence history. The GCM authentication tag ensures any ciphertext
41-
* tampering is detected at decode time.
39+
* <p>Output layout: {@code nonce(12 bytes) || ciphertext || tag(16 bytes)}. The random nonce means
40+
* the same plaintext produces different ciphertext on every call, which preserves semantic security
41+
* for repeated payloads. The GCM authentication tag ensures any ciphertext tampering is detected at
42+
* decode time.
4243
*/
4344
public final class EncryptedJsonDataConverter implements DataConverter {
4445

@@ -51,8 +52,8 @@ public final class EncryptedJsonDataConverter implements DataConverter {
5152
private final SecureRandom random = new SecureRandom();
5253

5354
/**
54-
* @param keyBytes 32-byte AES-256 key. The caller is responsible for sourcing this from a
55-
* secrets manager in production; see {@link EncryptionKeyLoader}.
55+
* @param keyBytes 32-byte AES-256 key. The caller is responsible for sourcing this from a secrets
56+
* manager in production; see {@link EncryptionKeyLoader}.
5657
* @throws IllegalArgumentException if the key is not 32 bytes.
5758
*/
5859
public EncryptedJsonDataConverter(byte[] keyBytes) {

src/main/java/com/uber/cadence/samples/dataconverter/README.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# DataConverter Samples
22

3-
Three production-ready patterns for custom `DataConverter` implementations in the Cadence Java client: **compression**, **encryption**, and **S3 / claim-check offload**. A `DataConverter` controls how every workflow input, output, and activity parameter is serialized before it is written to Cadence history — making it the right place to add compression, encryption, or external offloading without changing any workflow or activity code.
3+
Three practical patterns for custom `DataConverter` implementations in the Cadence Java client: **compression**, **encryption**, and **BlobStore / S3 claim-check offload**. A `DataConverter` controls how every workflow input, output, and activity parameter is serialized before it is written to Cadence history — making it the right place to add compression, encryption, or external offloading without changing any workflow or activity code.
44

55
## What is a DataConverter?
66

@@ -60,7 +60,7 @@ Run **one** of the starters per sample run. Each starts a new workflow execution
6060
./gradlew -q execute -PmainClass=com.uber.cadence.samples.dataconverter.EncryptionStarter
6161
```
6262

63-
**S3 offload** — claim-check pattern:
63+
**S3 offload** — claim-check pattern with a zero-config local `BlobStore`:
6464

6565
```bash
6666
./gradlew -q execute -PmainClass=com.uber.cadence.samples.dataconverter.S3OffloadStarter
@@ -72,21 +72,21 @@ You can also start any of the three from the Cadence CLI; the commands are print
7272

7373
## Compression Sample
7474

75-
`CompressedDataConverterWorkflow` demonstrates gzip-over-JSON compression. For repetitive JSON data this typically achieves 60–80% size reduction, lowering storage cost and bandwidth for large workflow payloads. The converter is implemented in [`CompressedJsonDataConverter.java`](CompressedJsonDataConverter.java) — it wraps `JsonDataConverter.getInstance()` and post-processes the resulting bytes through `java.util.zip.GZIP*Stream`.
75+
`CompressedDataConverterWorkflow` demonstrates gzip-over-JSON compression. For repetitive JSON data this typically achieves 60–80% size reduction, lowering storage cost and bandwidth for large workflow payloads. The converter is implemented in [`CompressedJsonDataConverter.java`](CompressedJsonDataConverter.java) — it wraps `JsonDataConverter.getInstance()`, post-processes the resulting bytes through `java.util.zip.GZIP*Stream`, and caps decompressed output to avoid unbounded memory growth on malformed input.
7676

7777
- **Task list:** `data-compression`
78-
- **Workflow type:** `CompressionDataConverterWorkflow`
78+
- **Workflow type:** `CompressedDataConverterWorkflow`
7979

8080
---
8181

8282
## Encryption Sample
8383

84-
`EncryptedDataConverterWorkflow` demonstrates AES-256-GCM encryption. Every workflow input, output, and activity parameter is encrypted before being written to Cadence history. Without the key, the data stored by the Cadence server — including any operators browsing workflow history — is completely opaque.
84+
`EncryptedDataConverterWorkflow` demonstrates AES-256-GCM encryption. Every workflow input, output, and activity parameter is encrypted before being written to Cadence history. Without the key, payloads stored by the Cadence server are unreadable to operators browsing workflow history. Logs, metrics, search attributes, and application output are separate disclosure surfaces.
8585

8686
The sample uses a `SensitiveCustomerRecord` containing realistic PII and PHI fields (name, email, SSN, credit card, medical notes) to make the use case concrete.
8787

8888
- **Task list:** `data-encryption`
89-
- **Workflow type:** `EncryptionDataConverterWorkflow`
89+
- **Workflow type:** `EncryptedDataConverterWorkflow`
9090

9191
### Encryption key
9292

@@ -101,23 +101,23 @@ export CADENCE_ENCRYPTION_KEY=$(openssl rand -hex 32)
101101
102102
### How AES-256-GCM works
103103

104-
- `toData`: JSON-encode arguments → generate a 12-byte random nonce → `Cipher.doFinal` with `AES/GCM/NoPadding` → return `nonce || ciphertext+tag`.
104+
- `toData`: JSON-encode arguments → generate a 12-byte random nonce → `Cipher.doFinal` with `AES/GCM/NoPadding` → return `nonce || ciphertext || tag`.
105105
- `fromData` / `fromDataArray`: split nonce from input → `Cipher.doFinal` (decrypt) → JSON-decode.
106106

107-
The GCM authentication tag (16 bytes) ensures any ciphertext tampering is detected. The random nonce means the same plaintext produces different ciphertext on every call, preventing replay detection by an attacker observing Cadence history.
107+
The GCM authentication tag (16 bytes) ensures any ciphertext tampering is detected. The random nonce means the same plaintext produces different ciphertext on every call, which preserves semantic security for repeated payloads.
108108

109109
---
110110

111111
## S3 Offload Sample (claim-check pattern)
112112

113-
`S3OffloadDataConverterWorkflow` demonstrates the *claim-check* pattern: payloads larger than a configurable threshold are stored in an external [`BlobStore`](BlobStore.java) and only a small reference (a few dozen bytes) travels through Cadence workflow history. This solves Cadence's per-payload size limits (~2 MB) for workflows that pass very large datasets between the workflow and its activities.
113+
`S3OffloadDataConverterWorkflow` demonstrates the *claim-check* pattern: payloads larger than a configurable threshold are stored in an external [`BlobStore`](BlobStore.java) and only a small reference (a few dozen bytes) travels through Cadence workflow history. The runnable sample uses [`LocalFsBlobStore`](LocalFsBlobStore.java) so it works without cloud credentials; the same abstraction can be backed by S3 in production. This solves Cadence's per-payload size limits (~2 MB) for workflows that pass very large datasets between the workflow and its activities.
114114

115115
- **Task list:** `data-s3`
116116
- **Workflow type:** `S3OffloadDataConverterWorkflow`
117117

118118
### How it works
119119

120-
- `toData`: JSON-encode → if `len(json) > thresholdBytes`, upload to `BlobStore` under a SHA-256 key and return `0x01 || {"__s3_ref":"<bucket>/<sha256hex>"}`. Otherwise return `0x00 || json` inline.
120+
- `toData`: JSON-encode → if `len(json) > thresholdBytes`, upload to `BlobStore` under a SHA-256 key and return `0x01 || {"s3Ref":"<bucket>/<sha256hex>"}`. Otherwise return `0x00 || json` inline.
121121
- `fromData` / `fromDataArray`: read prefix byte → if `0x01`, fetch from `BlobStore` and decode; if `0x00`, decode inline.
122122

123123
SHA-256-of-payload is used as the key so `toData` is idempotent across Cadence workflow replays. Using a fresh UUID per call would write a new orphaned blob on every replay.
@@ -150,9 +150,9 @@ You can also point the SDK at [LocalStack](https://localstack.cloud/) or [MinIO]
150150
|---------|----------|
151151
| **Compression** | Large repetitive JSON payloads; reducing storage cost without confidentiality requirements |
152152
| **Encryption** | PII, PHI, secrets, or any data that must be unreadable in Cadence history |
153-
| **S3 Offload** | Payloads approaching Cadence's size limits; binary or non-JSON data; cost-conscious archival |
153+
| **BlobStore / S3 Offload** | Payloads approaching Cadence's size limits; binary or non-JSON data; cost-conscious archival |
154154

155-
Patterns can be composed: encrypt-then-compress, or encrypt-then-offload to S3 for maximum security and minimum history size.
155+
Patterns can be composed, but order matters. Compress before encrypting when size reduction is a goal; encrypt before offloading when the external store should only receive ciphertext.
156156

157157
## Source layout
158158

0 commit comments

Comments
 (0)