diff options
7 files changed, 172 insertions, 35 deletions
diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/parameters/AcmParameters.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/parameters/AcmParameters.java index 554edea32..becad02df 100644 --- a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/parameters/AcmParameters.java +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/parameters/AcmParameters.java @@ -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; } diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/utils/EncryptionUtils.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/utils/EncryptionUtils.java index 565adb02d..f7988ea1a 100644 --- a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/utils/EncryptionUtils.java +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/utils/EncryptionUtils.java @@ -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"); } diff --git a/runtime-acm/src/main/resources/application.yaml b/runtime-acm/src/main/resources/application.yaml index 247cd2420..59d1aa5df 100644 --- a/runtime-acm/src/main/resources/application.yaml +++ b/runtime-acm/src/main/resources/application.yaml @@ -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: diff --git a/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/main/utils/EncryptionUtilTest.java b/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/main/utils/EncryptionUtilTest.java index ae6066ae5..7ad8b7169 100644 --- a/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/main/utils/EncryptionUtilTest.java +++ b/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/main/utils/EncryptionUtilTest.java @@ -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); }); } diff --git a/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/util/CommonTestData.java b/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/util/CommonTestData.java index 0392596ce..4762681a0 100644 --- a/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/util/CommonTestData.java +++ b/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/util/CommonTestData.java @@ -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; } diff --git a/runtime-acm/src/test/resources/providers/AcDefinitionEncryptTest.yaml b/runtime-acm/src/test/resources/providers/AcDefinitionEncryptTest.yaml index 0e9372876..416af183f 100644 --- a/runtime-acm/src/test/resources/providers/AcDefinitionEncryptTest.yaml +++ b/runtime-acm/src/test/resources/providers/AcDefinitionEncryptTest.yaml @@ -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 diff --git a/runtime-acm/src/test/resources/providers/AcInstantiateEncryptTest.json b/runtime-acm/src/test/resources/providers/AcInstantiateEncryptTest.json index acf2a9b16..896a8b6c9 100644 --- a/runtime-acm/src/test/resources/providers/AcInstantiateEncryptTest.json +++ b/runtime-acm/src/test/resources/providers/AcInstantiateEncryptTest.json @@ -12,7 +12,7 @@ }, "description": "Starter Automation Composition Element for the Demo", "properties": { - "credential": "mycred1", + "secret": "mysecret1", "password": "mypass1", "baseUrl": "http://address:30800", "httpHeaders": { @@ -37,9 +37,15 @@ "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" } ] } |