Skip to content

Commit

Permalink
[538] Encryption of relation records
Browse files Browse the repository at this point in the history
  • Loading branch information
aaron-gary committed Feb 5, 2025
1 parent 7e6706d commit 5a20526
Show file tree
Hide file tree
Showing 13 changed files with 413 additions and 7 deletions.
6 changes: 6 additions & 0 deletions foundation/foundation-mda/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,12 @@
<groupId>net.masterthought</groupId>
<artifactId>cucumber-reporting</artifactId>
</dependency>
<dependency>
<groupId>com.boozallen.aissemble</groupId>
<artifactId>foundation-encryption-policy-java</artifactId>
<version>${version.aissemble}</version>
</dependency>

</dependencies>

</project>
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
*/

import com.boozallen.aiops.mda.generator.util.PipelineUtils;
import com.boozallen.aiops.mda.metamodel.AissembleModelInstanceRepository;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.StringUtils;
Expand Down Expand Up @@ -106,6 +107,14 @@ public List<RecordField> getFields() {
return wrapped.getFields();
}

/**
* {@inheritDoc}
*/
@Override
public List<String> getFieldIds(AissembleModelInstanceRepository metadataRepo) {
return wrapped.getFieldIds(metadataRepo);
}

/**
* {@inheritDoc}
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -440,4 +440,21 @@ public List<FileStore> getDecoratedFileStores() {
public boolean isGeneric() {
return "generic".equalsIgnoreCase(getType());
}

/**
* Whether or not the step has an inbound native collection type defined
* and also contains relations.
*
* @return true if the step has an inbound native collection type defined and has relations.
*/
public boolean hasInboundNativeCollectionTypeAndRelations() {
boolean isNative = getInbound() != null && getInbound().getNativeCollectionType() != null;
boolean hasRelations = false;
if(getInbound().getRecordType() != null) {
Record thisRecord = getInbound().getRecordType().getRecordType();
hasRelations = thisRecord.getRelations() != null && !thisRecord.getRelations().isEmpty();
}

return isNative && hasRelations;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
* #L%
*/

import com.boozallen.aiops.mda.metamodel.AissembleModelInstanceRepository;
import org.technologybrewery.fermenter.mda.metamodel.element.NamespacedMetamodel;

import java.util.Collection;
Expand Down Expand Up @@ -49,6 +50,15 @@ public interface Record extends NamespacedMetamodel {
*/
List<RecordField> getFields();


/**
* Returns the fields ids contained in this field container.
*
* @return a list of field ids
*/
List<String> getFieldIds(AissembleModelInstanceRepository metadataRepo);


/**
* Returns the list of supported Frameworks (e.g. PySpark).
* @return list of supported Frameworks.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
*/

import com.boozallen.aiops.mda.ManualActionNotificationService;
import com.boozallen.aiops.mda.metamodel.AissembleModelInstanceRepository;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
Expand All @@ -20,6 +21,7 @@

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
* Represents a record instance.
Expand Down Expand Up @@ -91,6 +93,49 @@ public List<RecordField> getFields() {
return fields;
}

/**
* {@inheritDoc}
*/
@Override
public List<String> getFieldIds(AissembleModelInstanceRepository metadataRepo) {
List<String> fieldIds = new ArrayList<>();
for(RecordField recordField: this.getFields()) {
fieldIds.add(recordField.getName());
}

List<String> relatedFieldIds = getFieldIdsFromRelations(metadataRepo);
fieldIds.addAll(relatedFieldIds);

return fieldIds;
}

/***
* This method gets the fully qualified field ids from relations (dot notation).
* For example: FieldBParent.FieldB
* @param metadataRepo the configured metadataRepop
* @return a list of fully qualified field names
*/
private List<String> getFieldIdsFromRelations(AissembleModelInstanceRepository metadataRepo) {
List<String> fieldIds = new ArrayList<>();
List<Relation> relations = getRelations();
for(Relation relation: relations) {
String relationPackage = relation.getPackage();
String relationName = relation.getName();
Record relatedRecord = metadataRepo.getRecord(relationPackage, relationName);

if(relatedRecord != null) {

List<String> updatedList = relatedRecord.getFieldIds(metadataRepo).stream()
.map(s -> relation.getColumn() + "." + s) // Prepend the prefix
.collect(Collectors.toList());

fieldIds.addAll(updatedList);
}
}

return fieldIds;
}

/**
* Adds a field to this instance.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,16 @@ public void setDocumentation(String documentation) {
this.documentation = documentation;
}

/**
* Sets the documentation value.
*
* @param documentation
* documentation text
*/
public void setColumn(String column) {
this.column = column;
}

/**
* Sets the multiplicity value.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package com.boozallen.aiops.mda.metamodel.element.util;

/*-
* #%L
* aiSSEMBLE::Foundation::MDA
* %%
* Copyright (C) 2021 Booz Allen
* %%
* This software package is licensed under the Booz Allen Public License. All Rights Reserved.
* #L%
*/

import com.boozallen.aissemble.data.encryption.policy.json.EncryptionPolicyInput;

import java.util.Collections;
import java.util.List;

/**
* Helps with encryption of nested fields
*/
public class RecordFieldEncryptionUtil {

public RecordFieldEncryptionUtil() {
}

/***
* This method constructs a full Spark transform of the given fields and ensures
* fields are wrapped in an encryption UDF wrapper when a field requires encryption
* @param encryptionPolicyInput A list of record fields
* @return the full transform statement
*/
public String generateFieldEncryptionTransform(List<String> fieldIds, EncryptionPolicyInput encryptionPolicyInput) {
Collections.sort(fieldIds);
StringBuilder sb = new StringBuilder();
sb.append("transform(data, x -> struct(");
Boolean nested = false;

for(String aFieldId: fieldIds) {
if(aFieldId.contains(".")){
if(!nested) {
nested = true;
sb.append("struct(");
}
expandFieldNotation(aFieldId, encryptionPolicyInput, sb);

} else {
if(nested) {
nested = false;
sb.append(")");
}
expandFieldNotation(aFieldId, encryptionPolicyInput, sb);
}
}
if(nested) {
sb.append(")");
}

sb.append(" as data))");

String transformStatement = sb.toString();
transformStatement = transformStatement.replaceAll(", \\)", ")");

return transformStatement;
}

/***
* This method expands a field into a Spark transform notation
* for example: the field monotonic_time becomes x.monotonic_time as monotonic_time
* or in the case where the field needs to be encrypted
* encryptUDF(x.monotonic_time, "AES_ENCRYPT") as monotonic_time
* @param aFieldId
* @param encryptionPolicyInput contains the encrypt fields and encrypt algorithm
* @param sb
*/
private void expandFieldNotation(String aFieldId, EncryptionPolicyInput encryptionPolicyInput, StringBuilder sb) {
sb.append(checkFieldForEncryption(aFieldId, encryptionPolicyInput));
sb.append(" as ");
if(aFieldId.contains(".")) {
String fieldName = getFieldFromFullyQualifiedPath(aFieldId);
sb.append(fieldName);
} else {
sb.append(aFieldId);
}
sb.append(", ");
}

/***
* This method checks if a field requires encryption. If it does need encryption it will be
* wrapped in a UDF syntax. for example: encryptUDF(x.data.ssn, "AES_ENCRYPT")
* @param aFieldId the field name (including the dot path if it is a nested field)
* @param encryptionPolicyInput contains the encrypt fields and encrypt algorithm
* @return
*/
private String checkFieldForEncryption(String aFieldId, EncryptionPolicyInput encryptionPolicyInput) {
assert encryptionPolicyInput.getEncryptAlgorithm() != null;
StringBuilder sb = new StringBuilder();

if(encryptionPolicyInput.getEncryptFields() != null && encryptionPolicyInput.getEncryptFields().contains(aFieldId)) {
sb.append("encryptUDF(x.");
sb.append(aFieldId);
sb.append(",");
sb.append("\"" + encryptionPolicyInput.getEncryptAlgorithm().toString() + "\")");
} else {
sb.append("x.");
sb.append(aFieldId);
}

return sb.toString();
}

/***
* Gets the field name from a dot path field representation
* for example data.ssn --> ssn
* @param fullyQualifiedPath full path to the field using dot notation where the field is nested
* @return the field name
*/
private String getFieldFromFullyQualifiedPath(String fullyQualifiedPath) {
int lastDotIndex = fullyQualifiedPath.lastIndexOf(".");

return fullyQualifiedPath.substring(lastDotIndex + 1);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,17 @@
# If that property is missing then we should ignore the policy.
if encrypt_policy.encryptPhase:
if self.step_phase.lower() == encrypt_policy.encryptPhase.lower():

#if (${step.hasInboundNativeCollectionTypeAndRelations()})
raise NotImplementedError("Encryption of records that contain relations is not yet supported.")
#else
encrypt_fields = encrypt_policy.encryptFields
input_fields = self.get_fields_list(inbound)
field_intersection = list(set(encrypt_fields) & set(input_fields))

return_payload = self.apply_encryption_to_dataset(inbound, field_intersection, encrypt_policy.encryptAlgorithm)
else:
${step.capitalizedName}Base.logger.info('Encryption policy does not apply to this phase: ' + self.step_phase)
#end

return return_payload
#end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ ${step.encryptionSignature} {
if(!policies.isEmpty()) {
for(EncryptionPolicy encryptionPolicy: policies.values()) {
if(stepPhase.equalsIgnoreCase(encryptionPolicy.getEncryptPhase())){
#if (${step.hasInboundNativeCollectionTypeAndRelations()})
throw new UnsupportedOperationException("Encryption of records that contain relations is not yet supported.");
#else
List<String> encryptFields = encryptionPolicy.getEncryptFields();
#if (${step.hasMessagingInbound()})
## Input is a message
Expand Down Expand Up @@ -101,6 +104,7 @@ ${step.encryptionSignature} {
#end
#end
}
#end
}
}
}
Expand Down
Loading

0 comments on commit 5a20526

Please sign in to comment.