Add encrypt/decrypt handling for datatypes 55/140355/4
authorrameshiyer27 <ramesh.murugan.iyer@est.tech>
Wed, 26 Feb 2025 14:36:51 +0000 (14:36 +0000)
committerRamesh Murugan Iyer <ramesh.murugan.iyer@est.tech>
Wed, 5 Mar 2025 12:24:09 +0000 (12:24 +0000)
Issue-ID:POLICY-5134
Signed-off-by: rameshiyer27 <ramesh.murugan.iyer@est.tech>
Change-Id: Ibbde12b23f58d51ff84b75a3c970f7eed7eb1a96

runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/parameters/AcmParameters.java
runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/utils/EncryptionUtils.java
runtime-acm/src/main/resources/application.yaml
runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/main/utils/EncryptionUtilTest.java
runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/util/CommonTestData.java
runtime-acm/src/test/resources/providers/AcDefinitionEncryptTest.yaml
runtime-acm/src/test/resources/providers/AcInstantiateEncryptTest.json

index 554edea..becad02 100644 (file)
@@ -35,8 +35,6 @@ public class AcmParameters {
 
     private String toscaCompositionName = "org.onap.policy.clamp.acm.AutomationComposition";
 
-    private String passPhrase;
-
-    private String salt;
+    private boolean enableEncryption = false;
 
 }
index 565adb0..f7988ea 100644 (file)
@@ -29,7 +29,12 @@ import java.security.SecureRandom;
 import java.security.spec.InvalidKeySpecException;
 import java.util.ArrayList;
 import java.util.Base64;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.UUID;
 import javax.crypto.BadPaddingException;
 import javax.crypto.Cipher;
 import javax.crypto.IllegalBlockSizeException;
@@ -44,7 +49,14 @@ import org.onap.policy.clamp.common.acm.exception.AutomationCompositionRuntimeEx
 import org.onap.policy.clamp.models.acm.concepts.AutomationComposition;
 import org.onap.policy.clamp.models.acm.concepts.AutomationCompositionDefinition;
 import org.onap.policy.clamp.models.acm.concepts.AutomationCompositionElement;
+import org.onap.policy.models.tosca.authorative.concepts.ToscaDataType;
+import org.onap.policy.models.tosca.authorative.concepts.ToscaNodeTemplate;
+import org.onap.policy.models.tosca.authorative.concepts.ToscaNodeType;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaProperty;
+import org.onap.policy.models.tosca.authorative.concepts.ToscaSchemaDefinition;
+import org.onap.policy.models.tosca.authorative.concepts.ToscaTopologyTemplate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Component;
 
 /**
@@ -62,7 +74,9 @@ public class EncryptionUtils {
     private static final int IV_LENGTH = 12;
     private static final SecureRandom SECURE_RANDOM = new SecureRandom();
     private final String passPhrase;
-    private final String salt;
+    private final boolean encryptionEnabled;
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(EncryptionUtils.class);
 
 
     private static byte[] generateIV() {
@@ -76,8 +90,8 @@ public class EncryptionUtils {
      * @param acRuntimeParameterGroup acRuntimeParameterGroup
      */
     public EncryptionUtils(AcRuntimeParameterGroup acRuntimeParameterGroup) {
-        this.passPhrase = acRuntimeParameterGroup.getAcmParameters().getPassPhrase();
-        this.salt = acRuntimeParameterGroup.getAcmParameters().getSalt();
+        this.passPhrase = UUID.nameUUIDFromBytes("encrypt".getBytes()).toString();
+        this.encryptionEnabled = acRuntimeParameterGroup.getAcmParameters().isEnableEncryption();
     }
 
     /**
@@ -85,7 +99,7 @@ public class EncryptionUtils {
      * @return boolean result
      */
     public boolean encryptionEnabled() {
-        return passPhrase != null && salt != null;
+        return encryptionEnabled;
     }
 
 
@@ -97,17 +111,32 @@ public class EncryptionUtils {
     public void findAndEncryptSensitiveData(AutomationCompositionDefinition acDefinition,
                                             AutomationComposition automationComposition) {
         try {
+            var acNodeTypes = Optional.ofNullable(acDefinition.getServiceTemplate().getNodeTypes()).map(Map::values)
+                    .orElse(Collections.emptyList());
+            var acDataTypes = Optional.ofNullable(acDefinition.getServiceTemplate().getDataTypes()).map(Map::values)
+                    .orElse(Collections.emptyList());
+            var nodeTemplates = Optional.ofNullable(acDefinition.getServiceTemplate().getToscaTopologyTemplate())
+                    .map(ToscaTopologyTemplate::getNodeTemplates)
+                    .map(Map::values).orElse(Collections.emptyList());
+
             for (var acInstanceElement: automationComposition.getElements().values()) {
-                var sensitiveProperties = findSensitiveElementFields(acDefinition, acInstanceElement);
+                var sensitiveProperties = filterSensitiveProperties(acInstanceElement, acNodeTypes, acDataTypes,
+                        nodeTemplates);
+                LOGGER.debug("Sensitive properties for the element {} : {}",
+                        acInstanceElement.getId(), sensitiveProperties);
                 for (var property : sensitiveProperties) {
                     var elementProperties = acInstanceElement.getProperties();
                     var sensitiveVal = elementProperties.get(property.getName());
-                    if (sensitiveVal instanceof String sensitiveStr && !sensitiveStr.startsWith(MARKER)) {
+                    if (sensitiveVal == null) {
+                        encryptNested(property, elementProperties);
+                    } else if (sensitiveVal instanceof String sensitiveStr && !sensitiveStr.startsWith(MARKER)) {
                         var encryptedVal = encrypt(sensitiveStr);
                         elementProperties.put(property.getName(), encryptedVal);
+                        LOGGER.debug("Property {} is successfully encrypted", property.getName());
                     }
                 }
             }
+
         } catch (Exception e) {
             throw new AutomationCompositionRuntimeException(Response.Status.fromStatusCode(500),
                     "Failed to encrypt instance field ", e);
@@ -127,6 +156,9 @@ public class EncryptionUtils {
                     if (propertyVal instanceof String propertyValStr && propertyValStr.startsWith(MARKER)) {
                         var decryptedVal = decrypt(propertyValStr);
                         acInstanceElement.getProperties().put(property.getKey(), decryptedVal);
+                        LOGGER.debug("Property {} is successfully decrypted", property.getKey());
+                    } else {
+                        decryptNested(propertyVal);
                     }
                 }
             }
@@ -136,34 +168,117 @@ public class EncryptionUtils {
         }
     }
 
+    private void decryptNested(Object propertyVal) throws InvalidAlgorithmParameterException, IllegalBlockSizeException,
+            NoSuchPaddingException, BadPaddingException, NoSuchAlgorithmException, InvalidKeySpecException,
+            InvalidKeyException {
+        if (propertyVal instanceof List<?> listVal) {
+            for (var listEntry : listVal) {
+                if (listEntry instanceof Map<?, ?> tempMap) {
+                    decryptNestedMap(tempMap);
+                }
+            }
+        } else if (propertyVal instanceof Map<?, ?> tempMap) {
+            decryptNestedMap(tempMap);
+        }
+    }
+
+    private void decryptNestedMap(Map<?, ?> tempMap) throws InvalidAlgorithmParameterException,
+            IllegalBlockSizeException, NoSuchPaddingException, BadPaddingException, NoSuchAlgorithmException,
+            InvalidKeySpecException, InvalidKeyException {
+        @SuppressWarnings("unchecked")
+        var nestedMap = (Map<Object, Object>) tempMap;
+        for (var prop : nestedMap.entrySet()) {
+            if (prop.getValue() instanceof String nestedStr && nestedStr.startsWith(MARKER)) {
+                var encryptedVal = decrypt(nestedStr);
+                nestedMap.put(prop.getKey(), encryptedVal);
+                LOGGER.debug("Property {} is successfully decrypted", prop.getKey());
+            }
+        }
+    }
+
+    private void encryptNested(ToscaProperty property, Map<?, ?> properties)
+            throws InvalidAlgorithmParameterException, IllegalBlockSizeException, NoSuchPaddingException,
+            BadPaddingException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException {
+        // Iterate over nested maps to check if the property exists inside them
+        for (var mapEntry : properties.entrySet()) {
+            if (mapEntry.getValue() instanceof List<?> listVal) {
+                for (var listEntry : listVal) {
+                    if (listEntry instanceof Map<?, ?> tempMap) {
+                        encryptNestedMaps(property, tempMap);
+                    }
+                }
+            } else if (mapEntry.getValue() instanceof Map<?, ?> tempMap) {
+                encryptNestedMaps(property, tempMap);
+            }
+        }
+
+    }
+
+    private void encryptNestedMaps(ToscaProperty property, Map<?, ?> tempMap) throws InvalidAlgorithmParameterException,
+            IllegalBlockSizeException, NoSuchPaddingException, BadPaddingException, NoSuchAlgorithmException,
+            InvalidKeySpecException, InvalidKeyException {
+        @SuppressWarnings("unchecked")
+        var nestedMap = (Map<Object, Object>) tempMap;
+        var nestedValue = nestedMap.get(property.getName());
+        if (nestedValue instanceof String nestedStr && !nestedStr.startsWith(MARKER)) {
+            var encryptedVal = encrypt(nestedStr);
+            nestedMap.put(property.getName(), encryptedVal);
+            LOGGER.debug("Property {} is successfully encrypted", property.getName());
+        }
+    }
+
 
-    private List<ToscaProperty> findSensitiveElementFields(AutomationCompositionDefinition acDefinition,
-                                                           AutomationCompositionElement acInstanceElement) {
+    private List<ToscaProperty> filterSensitiveProperties(AutomationCompositionElement acInstanceElement,
+                                                          Collection<ToscaNodeType> nodeTypes,
+                                                          Collection<ToscaDataType> dataTypes,
+                                                          Collection<ToscaNodeTemplate> nodeTemplates) {
 
         List<ToscaProperty> sensitiveProperties = new ArrayList<>();
 
         // Fetch the node template element
-        var acDefElementOpt = acDefinition.getServiceTemplate().getToscaTopologyTemplate().getNodeTemplates()
-                .values().stream().filter(acDefElement -> acDefElement.getName()
+        var acDefElementOpt = nodeTemplates.stream().filter(acDefElement -> acDefElement.getName()
                         .equals(acInstanceElement.getDefinition().getName())).findFirst();
 
         // Fetch node type
         if (acDefElementOpt.isPresent()) {
-            var toscaNodeTypeOpt = acDefinition.getServiceTemplate().getNodeTypes().values().stream()
-                    .filter(toscaNodeType -> toscaNodeType.getName()
+            var toscaNodeTypeOpt = nodeTypes.stream().filter(toscaNodeType -> toscaNodeType.getName()
                             .equals(acDefElementOpt.get().getType())).findFirst();
 
-            toscaNodeTypeOpt.ifPresent(toscaNodeType -> toscaNodeType.getProperties().values()
-                    .stream().filter(property -> property.getMetadata() != null
-                            && property.getMetadata().containsKey(SENSITIVE_METADATA))
-                    .forEach(sensitiveProperties::add));
+            if (toscaNodeTypeOpt.isPresent()) {
+                toscaNodeTypeOpt.get().getProperties().values().stream()
+                        .filter(this::isSensitiveMetadata)
+                        .forEach(sensitiveProperties::add);
+
+                for (var property : toscaNodeTypeOpt.get().getProperties().values()) {
+                    dataTypes.stream()
+                            .filter(datatype -> isDataTypeRef(property, datatype))
+                            .flatMap(dataType -> dataType.getProperties().values().stream())
+                            .filter(this::isSensitiveMetadata)
+                            .forEach(sensitiveProperties::add);
+                }
+            }
         }
         return sensitiveProperties;
     }
 
+    private boolean isSensitiveMetadata(ToscaProperty property) {
+        if (property.getMetadata() == null) {
+            return false;
+        }
+        var metadataValue = property.getMetadata().get(SENSITIVE_METADATA);
+        return "true".equals(metadataValue);
+    }
+
+    private boolean isDataTypeRef(ToscaProperty property, ToscaDataType dataType) {
+        var dataTypeName = dataType.getDefinedName();
+        var propertyEntity = Optional.ofNullable(property.getEntrySchema()).map(ToscaSchemaDefinition::getType);
+        return dataTypeName.equals(property.getType()) || dataTypeName.equals(propertyEntity.orElse(null));
+    }
+
 
     private SecretKey getSecretKey() throws NoSuchAlgorithmException, InvalidKeySpecException {
         var factory = SecretKeyFactory.getInstance(PBK_ALGORITHM);
+        var salt = "salt";
         var spec = new PBEKeySpec(passPhrase.toCharArray(), salt.getBytes(), 65536, 256);
         return new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
     }
index 247cd24..59d1aa5 100644 (file)
@@ -62,8 +62,7 @@ runtime:
   acmParameters:
     toscaElementName: org.onap.policy.clamp.acm.AutomationCompositionElement
     toscaCompositionName: org.onap.policy.clamp.acm.AutomationComposition
-    passPhrase: 1234AbCEncryptionPassPhrase
-    salt: runtimeFixedSalt
+    enableEncryption: true
 
 management:
   endpoints:
index ae6066a..7ad8b71 100644 (file)
@@ -53,16 +53,18 @@ class EncryptionUtilTest {
     void testEncryptAcInstanceProperties() {
         var automationComposition =
                 InstantiationUtils.getAutomationCompositionFromResource(INSTANTIATE_JSON, "Crud");
-        var encryptionUtils = new EncryptionUtils(CommonTestData.getEncryptionParamaterGroup());
+        var encryptionUtils = new EncryptionUtils(CommonTestData.getEncryptionParameterGroup());
         assertTrue(encryptionUtils.encryptionEnabled());
         assertDoesNotThrow(()
                 -> {
             assert automationComposition != null;
             encryptionUtils.findAndEncryptSensitiveData(acDefinition, automationComposition);
         });
+
+        var encryptionUtil2 = new EncryptionUtils(CommonTestData.getEncryptionParameterGroup());
         assertDoesNotThrow(() -> {
             assert automationComposition != null;
-            encryptionUtils.findAndDecryptSensitiveData(automationComposition);
+            encryptionUtil2.findAndDecryptSensitiveData(automationComposition);
         });
     }
 
index 0392596..4762681 100644 (file)
@@ -170,10 +170,9 @@ public class CommonTestData {
      *
      * @return a new AutomationCompositionDefinition
      */
-    public static AcRuntimeParameterGroup getEncryptionParamaterGroup() {
+    public static AcRuntimeParameterGroup getEncryptionParameterGroup() {
         var acRuntimeParameterGroup = getTestParamaterGroup();
-        acRuntimeParameterGroup.getAcmParameters().setSalt("randomSalt");
-        acRuntimeParameterGroup.getAcmParameters().setPassPhrase("randomPhrase");
+        acRuntimeParameterGroup.getAcmParameters().setEnableEncryption(true);
         return acRuntimeParameterGroup;
     }
 
index 0e93728..416af18 100644 (file)
@@ -27,6 +27,25 @@ data_types:
       version:
         type: string
         required: true
+  org.onap.datatypes.policy.clamp.acm.httpAutomationCompositionElement.ConfigurationEntity:
+    version: 1.0.0
+    derived_from: tosca.datatypes.Root
+    properties:
+      configurationEntityId:
+        type: onap.datatypes.ToscaConceptIdentifier
+        required: true
+        description: The name and version of a Configuration Entity to be handled
+          by the HTTP Automation Composition Element
+      restSequence:
+        type: list
+        entry_schema:
+          type: org.onap.datatypes.policy.clamp.acm.httpAutomationCompositionElement.RestRequest
+          type_version: 1.0.0
+        description: A sequence of REST commands to send to the REST endpoint
+      k8s-secret:
+        type: String
+        metadata:
+          sensitive: true
 
 node_types:
   org.onap.policy.clamp.acm.Participant:
@@ -90,7 +109,7 @@ node_types:
         type: String
         metadata:
           sensitive: true
-      credential:
+      secret:
         type: String
         metadata:
           sensitive: true
@@ -106,11 +125,9 @@ node_types:
         description: HTTP headers to send on REST requests
       configurationEntities:
         type: map
-        required: true
         entry_schema:
-          type: map
-        metadata:
-          sensitive: true
+          type: org.onap.datatypes.policy.clamp.acm.httpAutomationCompositionElement.ConfigurationEntity
+        required: true
         description: The configuration entities the Automation Composition Element is managing and their associated REST requests
 
 
index acf2a9b..896a8b6 100644 (file)
@@ -12,7 +12,7 @@
       },
       "description": "Starter Automation Composition Element for the Demo",
       "properties": {
-        "credential": "mycred1",
+        "secret": "mysecret1",
         "password": "mypass1",
         "baseUrl": "http://address:30800",
         "httpHeaders": {
                 "expectedResponse": 201
               }
             ],
+            "k8s-secret": "valueToEncrypt",
             "myParameterToUpdate": "text1"
           }
-        ]
+        ],
+        "customProperty": {
+          "name": "test",
+          "k8s-secret": "customValueToEncrypt"
+        }
+
       }
     },
     "709c62b3-8918-41b9-a747-d21eb79c6c35": {
@@ -52,7 +58,7 @@
       "properties": {
         "baseUrl": "http://address:30801",
         "password": "mypass2",
-        "credential": "mycred2",
+        "secret": "secret2",
         "httpHeaders": {
           "Content-Type": "application/json",
           "Authorization": "Basic YWNtVXNlcjp6YiFYenRHMzQ="
@@ -75,7 +81,8 @@
                 "expectedResponse": 201
               }
             ],
-            "myParameterToUpdate": "text2"
+            "myParameterToUpdate": "text2",
+            "k8s-secret": "valueToEncrypt2"
           }
         ]
       }