Skip to content

Commit

Permalink
Merge pull request #24 from data-integrations/CDAP-16613-deterministi…
Browse files Browse the repository at this point in the history
…c-encrypt

(CDAP-16613) Deterministic encrypt
  • Loading branch information
MEseifan authored Apr 22, 2020
2 parents fb1f9d8 + 51f4bdf commit bb9aef9
Show file tree
Hide file tree
Showing 5 changed files with 382 additions and 4 deletions.
17 changes: 14 additions & 3 deletions src/main/java/io/cdap/plugin/dlp/SensitiveRecordDecrypt.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.google.cloud.dlp.v2.DlpServiceSettings;
import com.google.common.annotations.VisibleForTesting;
import com.google.privacy.dlp.v2.ContentItem;
import com.google.privacy.dlp.v2.CryptoDeterministicConfig;
import com.google.privacy.dlp.v2.CryptoReplaceFfxFpeConfig;
import com.google.privacy.dlp.v2.CustomInfoType;
import com.google.privacy.dlp.v2.DeidentifyConfig;
Expand Down Expand Up @@ -165,16 +166,26 @@ public void transform(StructuredRecord structuredRecord, Emitter<StructuredRecor
for (InfoTypeTransformations.InfoTypeTransformation infoTypeTransformation : fieldTransformation
.getInfoTypeTransformations().getTransformationsList()) {

// Only CryptoReplaceFfxFpeConfig has a surrogate type so target that config, no other configs should be
// possible in this transform since they are not included in the widget json list of options
// Only CryptoReplaceFfxFpeConfig and CryptoDeterministicConfig have a surrogate type so target those configs,
// no other configs should be possible in this transform since they are not included in the widget json list
// of options
CryptoReplaceFfxFpeConfig cryptoReplaceFfxFpeConfig = infoTypeTransformation.getPrimitiveTransformation()
.getCryptoReplaceFfxFpeConfig();
if (cryptoReplaceFfxFpeConfig != null) {
if (cryptoReplaceFfxFpeConfig.hasCryptoKey()) {
CustomInfoType customInfoType = CustomInfoType.newBuilder()
.setInfoType(cryptoReplaceFfxFpeConfig.getSurrogateInfoType())
.setSurrogateType(CustomInfoType.SurrogateType.newBuilder().build()).build();
configBuilder.addCustomInfoTypes(customInfoType);
}

CryptoDeterministicConfig cryptoDeterministicConfig = infoTypeTransformation.getPrimitiveTransformation()
.getCryptoDeterministicConfig();
if (cryptoDeterministicConfig.hasCryptoKey()) {
CustomInfoType customInfoType = CustomInfoType.newBuilder()
.setInfoType(cryptoDeterministicConfig.getSurrogateInfoType())
.setSurrogateType(CustomInfoType.SurrogateType.newBuilder().build()).build();
configBuilder.addCustomInfoTypes(customInfoType);
}
}
}
configBuilder.setMinLikelihood(Likelihood.POSSIBLE);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Copyright © 2020 Cask Data, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License 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.cdap.plugin.dlp.configs;

import com.google.api.client.util.Strings;
import com.google.gson.Gson;
import com.google.privacy.dlp.v2.CryptoDeterministicConfig;
import com.google.privacy.dlp.v2.CryptoHashConfig;
import com.google.privacy.dlp.v2.CryptoKey;
import com.google.privacy.dlp.v2.FieldId;
import com.google.privacy.dlp.v2.InfoType;
import com.google.privacy.dlp.v2.PrimitiveTransformation;
import io.cdap.cdap.api.data.schema.Schema;
import io.cdap.cdap.etl.api.FailureCollector;

import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;

/**
* Implementing the DlpTransformConfig interface for the DLP {@link CryptoDeterministicConfig}
*/
public class CryptoDeterministicTransformationConfig implements DlpTransformConfig {

private static final String INFO_TYPE_NAME_REGEX = "[a-zA-Z0-9_]{1,64}";
private String key;
private String name;
private String wrappedKey;
private String cryptoKeyName;
private CryptoKeyHelper.KeyType keyType;
private final Schema.Type[] supportedTypes = new Schema.Type[]{Schema.Type.STRING};
private String surrogateInfoTypeName;
private String context;
private static final Gson gson = new Gson();

@Override
public PrimitiveTransformation toPrimitiveTransform() {
CryptoKey cryptoKey = CryptoKeyHelper.createKey(keyType, name, key, cryptoKeyName, wrappedKey);
InfoType surrogateType = InfoType.newBuilder().setName(surrogateInfoTypeName).build();

CryptoDeterministicConfig.Builder configBuilder = CryptoDeterministicConfig.newBuilder();
configBuilder.setCryptoKey(cryptoKey).setSurrogateInfoType(surrogateType);

if (!Strings.isNullOrEmpty(context)) {
configBuilder.setContext(FieldId.newBuilder().setName(context).build());
}

return PrimitiveTransformation.newBuilder().setCryptoDeterministicConfig(configBuilder).build();
}

@Override
public void validate(FailureCollector collector, String widgetName, ErrorConfig errorConfig) {
CryptoKeyHelper.validateKey(collector, widgetName, errorConfig, keyType, name, key, cryptoKeyName, wrappedKey);

if (Strings.isNullOrEmpty(surrogateInfoTypeName)) {
errorConfig.setNestedTransformPropertyId("surrogateInfoTypeName");
collector.addFailure("Surrogate Type Name is a required field.", "Please provide a valid value.")
.withConfigElement(widgetName, gson.toJson(errorConfig));
} else {
Pattern pattern = Pattern.compile(INFO_TYPE_NAME_REGEX);
boolean isValidSurrogateName = pattern.matcher(surrogateInfoTypeName).matches();
if (!isValidSurrogateName) {
errorConfig.setNestedTransformPropertyId("surrogateInfoTypeName");
collector.addFailure(String
.format("Value of '%s' is not valid for Surrogate Type Name", surrogateInfoTypeName),
"Name can only contain upper/lowercase letters, numbers or underscores and" +
"it must be between 1 and 64 characters long.")
.withConfigElement(widgetName, gson.toJson(errorConfig));
}
}
}

@Override
public List<Schema.Type> getSupportedTypes() {
return Arrays.asList(supportedTypes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public class DlpFieldTransformationConfigCodec implements JsonDeserializer<DlpFi
put("CRYPTO_HASH", CryptoHashTransformationConfig.class);
put("DATE_SHIFT", DateShiftTransformationConfig.class);
put("FORMAT_PRESERVING_ENCRYPTION", CryptoReplaceFfxFpeTransformationConfig.class);
put("DETERMINISTIC_ENCRYPTION", CryptoDeterministicTransformationConfig.class);
}};

@Override
Expand Down
138 changes: 138 additions & 0 deletions widgets/SensitiveRecordDecrypt-transform.json
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,144 @@
]
}
]
},
{
"label": "Deterministic Encryption",
"name": "DETERMINISTIC_ENCRYPTION",
"supportedTypes": [
"string"
],
"options": [
{
"name": "keyType",
"widget-type": "select",
"label": "Crypto Key Type",
"widget-attributes": {
"description": "Type of key to use for the cryptographic hash.",
"default": "TRANSIENT",
"values": [
{
"value": "TRANSIENT",
"label": "Transient"
},
{
"value": "UNWRAPPED",
"label": "Unwrapped Key"
},
{
"value": "KMS_WRAPPED",
"label": "KMS Wrapped Key"
}
]
}
},
{
"name": "name",
"widget-type": "textbox",
"label": "Transient Key Name",
"widget-attributes": {
"macro": "true",
"placeholder": "Transient key name",
"description": "Optional name for transient key that will be generated"
}
},
{
"name": "key",
"widget-type": "textbox",
"label": "Unwrapped Key",
"widget-attributes": {
"macro": "true",
"placeholder": "Unwrapped key",
"description": "Base64 encoded key to be used for cryptographic hash. Key must be must be 16, 24 or 32 bytes long."
}
},
{
"name": "wrappedKey",
"widget-type": "textbox",
"label": "Wrapped Key",
"widget-attributes": {
"macro": "true",
"placeholder": "Wrapped key",
"description": "Wrapped key to be unwrapped using KMS key"
}
},
{
"name": "cryptoKeyName",
"widget-type": "textbox",
"label": "KMS Resource ID",
"widget-attributes": {
"macro": "true",
"placeholder": "projects/.../locations/.../keyRings/.../cryptoKeys/...",
"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."
}
},
{
"name": "surrogateInfoTypeName",
"widget-type": "textbox",
"label": "Surrogate Type Name",
"widget-attributes": {
"macro": "true",
"placeholder": "Surrogate Type Name",
"required": "true",
"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."
}
},
{
"widget-type": "input-field-selector",
"label": "Context",
"name": "context",
"widget-attributes": {
"allowedTypes": [
"string"
],
"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."
}
}
],
"filters": [
{
"name": "transient key rules",
"condition": {
"property": "keyType",
"operator": "equal to",
"value": "TRANSIENT"
},
"show": [
{
"name": "name"
}
]
},
{
"name": "unwrapped key rules",
"condition": {
"property": "keyType",
"operator": "equal to",
"value": "UNWRAPPED"
},
"show": [
{
"name": "key"
}
]
},
{
"name": "kms wrapped key rules",
"condition": {
"property": "keyType",
"operator": "equal to",
"value": "KMS_WRAPPED"
},
"show": [
{
"name": "wrappedKey"
},
{
"name": "cryptoKeyName"
}
]
}
]
}
]
}
Expand Down
Loading

0 comments on commit bb9aef9

Please sign in to comment.