Skip to content

Commit bb9aef9

Browse files
authored
Merge pull request #24 from data-integrations/CDAP-16613-deterministic-encrypt
(CDAP-16613) Deterministic encrypt
2 parents fb1f9d8 + 51f4bdf commit bb9aef9

File tree

5 files changed

+382
-4
lines changed

5 files changed

+382
-4
lines changed

src/main/java/io/cdap/plugin/dlp/SensitiveRecordDecrypt.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import com.google.cloud.dlp.v2.DlpServiceSettings;
2323
import com.google.common.annotations.VisibleForTesting;
2424
import com.google.privacy.dlp.v2.ContentItem;
25+
import com.google.privacy.dlp.v2.CryptoDeterministicConfig;
2526
import com.google.privacy.dlp.v2.CryptoReplaceFfxFpeConfig;
2627
import com.google.privacy.dlp.v2.CustomInfoType;
2728
import com.google.privacy.dlp.v2.DeidentifyConfig;
@@ -165,16 +166,26 @@ public void transform(StructuredRecord structuredRecord, Emitter<StructuredRecor
165166
for (InfoTypeTransformations.InfoTypeTransformation infoTypeTransformation : fieldTransformation
166167
.getInfoTypeTransformations().getTransformationsList()) {
167168

168-
// Only CryptoReplaceFfxFpeConfig has a surrogate type so target that config, no other configs should be
169-
// possible in this transform since they are not included in the widget json list of options
169+
// Only CryptoReplaceFfxFpeConfig and CryptoDeterministicConfig have a surrogate type so target those configs,
170+
// no other configs should be possible in this transform since they are not included in the widget json list
171+
// of options
170172
CryptoReplaceFfxFpeConfig cryptoReplaceFfxFpeConfig = infoTypeTransformation.getPrimitiveTransformation()
171173
.getCryptoReplaceFfxFpeConfig();
172-
if (cryptoReplaceFfxFpeConfig != null) {
174+
if (cryptoReplaceFfxFpeConfig.hasCryptoKey()) {
173175
CustomInfoType customInfoType = CustomInfoType.newBuilder()
174176
.setInfoType(cryptoReplaceFfxFpeConfig.getSurrogateInfoType())
175177
.setSurrogateType(CustomInfoType.SurrogateType.newBuilder().build()).build();
176178
configBuilder.addCustomInfoTypes(customInfoType);
177179
}
180+
181+
CryptoDeterministicConfig cryptoDeterministicConfig = infoTypeTransformation.getPrimitiveTransformation()
182+
.getCryptoDeterministicConfig();
183+
if (cryptoDeterministicConfig.hasCryptoKey()) {
184+
CustomInfoType customInfoType = CustomInfoType.newBuilder()
185+
.setInfoType(cryptoDeterministicConfig.getSurrogateInfoType())
186+
.setSurrogateType(CustomInfoType.SurrogateType.newBuilder().build()).build();
187+
configBuilder.addCustomInfoTypes(customInfoType);
188+
}
178189
}
179190
}
180191
configBuilder.setMinLikelihood(Likelihood.POSSIBLE);
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Copyright © 2020 Cask Data, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
17+
package io.cdap.plugin.dlp.configs;
18+
19+
import com.google.api.client.util.Strings;
20+
import com.google.gson.Gson;
21+
import com.google.privacy.dlp.v2.CryptoDeterministicConfig;
22+
import com.google.privacy.dlp.v2.CryptoHashConfig;
23+
import com.google.privacy.dlp.v2.CryptoKey;
24+
import com.google.privacy.dlp.v2.FieldId;
25+
import com.google.privacy.dlp.v2.InfoType;
26+
import com.google.privacy.dlp.v2.PrimitiveTransformation;
27+
import io.cdap.cdap.api.data.schema.Schema;
28+
import io.cdap.cdap.etl.api.FailureCollector;
29+
30+
import java.util.Arrays;
31+
import java.util.List;
32+
import java.util.regex.Pattern;
33+
34+
/**
35+
* Implementing the DlpTransformConfig interface for the DLP {@link CryptoDeterministicConfig}
36+
*/
37+
public class CryptoDeterministicTransformationConfig implements DlpTransformConfig {
38+
39+
private static final String INFO_TYPE_NAME_REGEX = "[a-zA-Z0-9_]{1,64}";
40+
private String key;
41+
private String name;
42+
private String wrappedKey;
43+
private String cryptoKeyName;
44+
private CryptoKeyHelper.KeyType keyType;
45+
private final Schema.Type[] supportedTypes = new Schema.Type[]{Schema.Type.STRING};
46+
private String surrogateInfoTypeName;
47+
private String context;
48+
private static final Gson gson = new Gson();
49+
50+
@Override
51+
public PrimitiveTransformation toPrimitiveTransform() {
52+
CryptoKey cryptoKey = CryptoKeyHelper.createKey(keyType, name, key, cryptoKeyName, wrappedKey);
53+
InfoType surrogateType = InfoType.newBuilder().setName(surrogateInfoTypeName).build();
54+
55+
CryptoDeterministicConfig.Builder configBuilder = CryptoDeterministicConfig.newBuilder();
56+
configBuilder.setCryptoKey(cryptoKey).setSurrogateInfoType(surrogateType);
57+
58+
if (!Strings.isNullOrEmpty(context)) {
59+
configBuilder.setContext(FieldId.newBuilder().setName(context).build());
60+
}
61+
62+
return PrimitiveTransformation.newBuilder().setCryptoDeterministicConfig(configBuilder).build();
63+
}
64+
65+
@Override
66+
public void validate(FailureCollector collector, String widgetName, ErrorConfig errorConfig) {
67+
CryptoKeyHelper.validateKey(collector, widgetName, errorConfig, keyType, name, key, cryptoKeyName, wrappedKey);
68+
69+
if (Strings.isNullOrEmpty(surrogateInfoTypeName)) {
70+
errorConfig.setNestedTransformPropertyId("surrogateInfoTypeName");
71+
collector.addFailure("Surrogate Type Name is a required field.", "Please provide a valid value.")
72+
.withConfigElement(widgetName, gson.toJson(errorConfig));
73+
} else {
74+
Pattern pattern = Pattern.compile(INFO_TYPE_NAME_REGEX);
75+
boolean isValidSurrogateName = pattern.matcher(surrogateInfoTypeName).matches();
76+
if (!isValidSurrogateName) {
77+
errorConfig.setNestedTransformPropertyId("surrogateInfoTypeName");
78+
collector.addFailure(String
79+
.format("Value of '%s' is not valid for Surrogate Type Name", surrogateInfoTypeName),
80+
"Name can only contain upper/lowercase letters, numbers or underscores and" +
81+
"it must be between 1 and 64 characters long.")
82+
.withConfigElement(widgetName, gson.toJson(errorConfig));
83+
}
84+
}
85+
}
86+
87+
@Override
88+
public List<Schema.Type> getSupportedTypes() {
89+
return Arrays.asList(supportedTypes);
90+
}
91+
}

src/main/java/io/cdap/plugin/dlp/configs/DlpFieldTransformationConfigCodec.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public class DlpFieldTransformationConfigCodec implements JsonDeserializer<DlpFi
3838
put("CRYPTO_HASH", CryptoHashTransformationConfig.class);
3939
put("DATE_SHIFT", DateShiftTransformationConfig.class);
4040
put("FORMAT_PRESERVING_ENCRYPTION", CryptoReplaceFfxFpeTransformationConfig.class);
41+
put("DETERMINISTIC_ENCRYPTION", CryptoDeterministicTransformationConfig.class);
4142
}};
4243

4344
@Override

widgets/SensitiveRecordDecrypt-transform.json

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,144 @@
272272
]
273273
}
274274
]
275+
},
276+
{
277+
"label": "Deterministic Encryption",
278+
"name": "DETERMINISTIC_ENCRYPTION",
279+
"supportedTypes": [
280+
"string"
281+
],
282+
"options": [
283+
{
284+
"name": "keyType",
285+
"widget-type": "select",
286+
"label": "Crypto Key Type",
287+
"widget-attributes": {
288+
"description": "Type of key to use for the cryptographic hash.",
289+
"default": "TRANSIENT",
290+
"values": [
291+
{
292+
"value": "TRANSIENT",
293+
"label": "Transient"
294+
},
295+
{
296+
"value": "UNWRAPPED",
297+
"label": "Unwrapped Key"
298+
},
299+
{
300+
"value": "KMS_WRAPPED",
301+
"label": "KMS Wrapped Key"
302+
}
303+
]
304+
}
305+
},
306+
{
307+
"name": "name",
308+
"widget-type": "textbox",
309+
"label": "Transient Key Name",
310+
"widget-attributes": {
311+
"macro": "true",
312+
"placeholder": "Transient key name",
313+
"description": "Optional name for transient key that will be generated"
314+
}
315+
},
316+
{
317+
"name": "key",
318+
"widget-type": "textbox",
319+
"label": "Unwrapped Key",
320+
"widget-attributes": {
321+
"macro": "true",
322+
"placeholder": "Unwrapped key",
323+
"description": "Base64 encoded key to be used for cryptographic hash. Key must be must be 16, 24 or 32 bytes long."
324+
}
325+
},
326+
{
327+
"name": "wrappedKey",
328+
"widget-type": "textbox",
329+
"label": "Wrapped Key",
330+
"widget-attributes": {
331+
"macro": "true",
332+
"placeholder": "Wrapped key",
333+
"description": "Wrapped key to be unwrapped using KMS key"
334+
}
335+
},
336+
{
337+
"name": "cryptoKeyName",
338+
"widget-type": "textbox",
339+
"label": "KMS Resource ID",
340+
"widget-attributes": {
341+
"macro": "true",
342+
"placeholder": "projects/.../locations/.../keyRings/.../cryptoKeys/...",
343+
"description": "Resource ID of the key stored in Key Management Service that will be used to unwrap the wrapped key. The key version should be removed from the resource ID, the primary version will be used."
344+
}
345+
},
346+
{
347+
"name": "surrogateInfoTypeName",
348+
"widget-type": "textbox",
349+
"label": "Surrogate Type Name",
350+
"widget-attributes": {
351+
"macro": "true",
352+
"placeholder": "Surrogate Type Name",
353+
"required": "true",
354+
"description": "The custom infoType name to annotate the surrogate with. This annotation will be applied to the surrogate by prefixing it with the name of the custom infoType followed by the number of characters comprising the surrogate."
355+
}
356+
},
357+
{
358+
"widget-type": "input-field-selector",
359+
"label": "Context",
360+
"name": "context",
361+
"widget-attributes": {
362+
"allowedTypes": [
363+
"string"
364+
],
365+
"description": "(Optional) Provide additional field to be used as Context. If the primary value is the same but the Context field value is different then two different encrypted values will be generated."
366+
}
367+
}
368+
],
369+
"filters": [
370+
{
371+
"name": "transient key rules",
372+
"condition": {
373+
"property": "keyType",
374+
"operator": "equal to",
375+
"value": "TRANSIENT"
376+
},
377+
"show": [
378+
{
379+
"name": "name"
380+
}
381+
]
382+
},
383+
{
384+
"name": "unwrapped key rules",
385+
"condition": {
386+
"property": "keyType",
387+
"operator": "equal to",
388+
"value": "UNWRAPPED"
389+
},
390+
"show": [
391+
{
392+
"name": "key"
393+
}
394+
]
395+
},
396+
{
397+
"name": "kms wrapped key rules",
398+
"condition": {
399+
"property": "keyType",
400+
"operator": "equal to",
401+
"value": "KMS_WRAPPED"
402+
},
403+
"show": [
404+
{
405+
"name": "wrappedKey"
406+
},
407+
{
408+
"name": "cryptoKeyName"
409+
}
410+
]
411+
}
412+
]
275413
}
276414
]
277415
}

0 commit comments

Comments
 (0)