Kafka Audit Service: Error during sensitive data hiding 34/111034/1
authorJulien Fontaine <julien.fontaine@bell.ca>
Fri, 7 Aug 2020 13:51:39 +0000 (09:51 -0400)
committerJulien Fontaine <julien.fontaine@bell.ca>
Fri, 7 Aug 2020 13:51:39 +0000 (09:51 -0400)
Changed behaviour during sensitive data hiding.
Instead of looking for every node template defined in the CBA we only check for the ones declared for the current executed workflow.
This prevent the issue when you have two independant workflows defined in the same CBA and one of those has a node template using a get_input expression for template-prefix in a component-resource-resolution. If you execute the other workflow audit service was still trying to resolve this expression which was making it fail.

Issue-ID: CCSDK-2636
Signed-off-by: Julien Fontaine <julien.fontaine@bell.ca>
Change-Id: Ib232c26ebf930917e19e7970e1a56c15e676888f

ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/BluePrintConstants.kt
ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/selfservice/api/KafkaPublishAuditService.kt

index cb78f41..76662d4 100644 (file)
@@ -242,4 +242,6 @@ object BluePrintConstants {
     const val PROPERTY_CLUSTER_CONFIG_FILE = "CLUSTER_CONFIG_FILE"
 
     const val NODE_TEMPLATE_TYPE_COMPONENT_RESOURCE_RESOLUTION = "component-resource-resolution"
+    const val NODE_TEMPLATE_TYPE_DG = "dg-generic"
+    const val PROPERTY_DG_DEPENDENCY_NODE_TEMPLATE = "dependency-node-templates"
 }
index fca7398..12cadfa 100644 (file)
@@ -17,6 +17,7 @@
 package org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api
 
 import com.fasterxml.jackson.databind.JsonNode
+import com.fasterxml.jackson.databind.node.ArrayNode
 import com.fasterxml.jackson.databind.node.ObjectNode
 import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ExecutionServiceInput
 import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ExecutionServiceOutput
@@ -27,6 +28,8 @@ import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants
 import org.onap.ccsdk.cds.controllerblueprints.core.asJsonPrimitive
 import org.onap.ccsdk.cds.controllerblueprints.core.common.ApplicationConstants
 import org.onap.ccsdk.cds.controllerblueprints.core.interfaces.BluePrintCatalogService
+import org.onap.ccsdk.cds.controllerblueprints.core.service.BluePrintContext
+import org.onap.ccsdk.cds.controllerblueprints.core.service.BluePrintRuntimeService
 import org.onap.ccsdk.cds.controllerblueprints.core.service.PropertyAssignmentService
 import org.onap.ccsdk.cds.controllerblueprints.core.utils.BluePrintMetadataUtils
 import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils
@@ -40,6 +43,13 @@ import javax.annotation.PostConstruct
 /**
  * Audit service used to produce execution service input and output message
  * sent into dedicated kafka topics.
+ *
+ * @param bluePrintMessageLibPropertyService Service used to instantiate audit service producers
+ * @param blueprintsProcessorCatalogService Service used to get the base path of the current CBA executed
+ *
+ * @property inputInstance Request Kakfa Producer instance
+ * @property outputInstance Response Kakfa Producer instance
+ * @property log Audit Service logger
  */
 @ConditionalOnProperty(
         name = ["blueprintsprocessor.messageproducer.self-service-api.audit.kafkaEnable"],
@@ -68,9 +78,11 @@ class KafkaPublishAuditService(
      * Publish execution input into a kafka topic.
      * The correlation UUID is used to link the input to its output.
      * Sensitive data within the request are hidden.
+     * @param executionServiceInput Audited BP request
      */
     override suspend fun publishExecutionInput(executionServiceInput: ExecutionServiceInput) {
         val secureExecutionServiceInput = hideSensitiveData(executionServiceInput)
+        val key = secureExecutionServiceInput.actionIdentifiers.blueprintName
         try {
             this.inputInstance = this.getInputInstance(INPUT_SELECTOR)
             this.inputInstance!!.sendMessage(secureExecutionServiceInput)
@@ -86,32 +98,38 @@ class KafkaPublishAuditService(
      * Publish execution output into a kafka topic.
      * The correlation UUID is used to link the output to its input.
      * A correlation UUID is added to link the input to its output.
+     * @param correlationUUID UUID used to link the audited response to its audited request
+     * @param executionServiceOutput Audited BP response
      */
     override suspend fun publishExecutionOutput(correlationUUID: String, executionServiceOutput: ExecutionServiceOutput) {
         executionServiceOutput.correlationUUID = correlationUUID
+        val key = executionServiceOutput.actionIdentifiers.blueprintName
         try {
             this.outputInstance = this.getOutputInstance(OUTPUT_SELECTOR)
             this.outputInstance!!.sendMessage(executionServiceOutput)
         } catch (e: Exception) {
             var errMsg =
-                if (e.message != null) "ERROR : $e"
-                else "ERROR : Failed to send execution request to Kafka."
+                    if (e.message != null) "ERROR : $e"
+                    else "ERROR : Failed to send execution request to Kafka."
             log.error(errMsg)
         }
     }
 
     /**
-     * Return the input kafka producer instance using a selector.
+     * Return the input kafka producer instance using a [selector] if not already instantiated.
+     * @param selector Selector to retrieve request kafka producer configuration
      */
     private fun getInputInstance(selector: String): BlueprintMessageProducerService = inputInstance ?: createInstance(selector)
 
     /**
-     * Return the output kafka producer instance using a selector.
+     * Return the output kafka producer instance using a [selector] if not already instantiated.
+     * @param selector Selector to retrieve response kafka producer configuration
      */
     private fun getOutputInstance(selector: String): BlueprintMessageProducerService = outputInstance ?: createInstance(selector)
 
     /**
-     * Create a kafka producer instance.
+     * Create a kafka producer instance using a [selector].
+     * @param selector Selector to retrieve kafka producer configuration
      */
     private fun createInstance(selector: String): BlueprintMessageProducerService {
         log.info("Setting up message producer($selector)...")
@@ -119,9 +137,10 @@ class KafkaPublishAuditService(
     }
 
     /**
-     * Hide sensitive data in the request.
+     * Hide sensitive data in the [executionServiceInput].
      * Sensitive data are declared in the resource resolution mapping using
      * the property metadata "log-protect" set to true.
+     * @param executionServiceInput BP Execution Request where data needs to be hidden
      */
     private suspend fun hideSensitiveData(
         executionServiceInput: ExecutionServiceInput
@@ -153,60 +172,105 @@ class KafkaPublishAuditService(
                 val blueprintRuntimeService = BluePrintMetadataUtils.getBluePrintRuntime(requestId, basePath.toString())
                 val blueprintContext = blueprintRuntimeService.bluePrintContext()
 
-                /** Looking for node templates defined as component-resource-resolution */
-                val nodeTemplates = blueprintContext.nodeTemplates()
-                nodeTemplates!!.forEach { nodeTemplate ->
-                    val nodeTemplateName = nodeTemplate.key
-                    val nodeTemplateType = blueprintContext.nodeTemplateByName(nodeTemplateName).type
-                    if (nodeTemplateType == BluePrintConstants.NODE_TEMPLATE_TYPE_COMPONENT_RESOURCE_RESOLUTION) {
-                        val interfaceName = blueprintContext.nodeTemplateFirstInterfaceName(nodeTemplateName)
-                        val operationName = blueprintContext.nodeTemplateFirstInterfaceFirstOperationName(nodeTemplateName)
-
-                        val propertyAssignments: MutableMap<String, JsonNode> =
-                                blueprintContext.nodeTemplateInterfaceOperationInputs(nodeTemplateName, interfaceName, operationName)
-                                        ?: hashMapOf()
-
-                        /** Getting values define in artifact-prefix-names */
-                        val input = executionServiceInput.payload.get("$workflowName-request")
-                        blueprintRuntimeService.assignWorkflowInputs(workflowName, input)
-                        val artifactPrefixNamesNode = propertyAssignments[ResourceResolutionConstants.INPUT_ARTIFACT_PREFIX_NAMES]
-                        val propertyAssignmentService = PropertyAssignmentService(blueprintRuntimeService)
-                        val artifactPrefixNamesNodeValue = propertyAssignmentService.resolveAssignmentExpression(
-                                BluePrintConstants.MODEL_DEFINITION_TYPE_NODE_TEMPLATE,
-                                nodeTemplateName,
-                                ResourceResolutionConstants.INPUT_ARTIFACT_PREFIX_NAMES,
-                                artifactPrefixNamesNode!!)
-
-                        val artifactPrefixNames = JacksonUtils.getListFromJsonNode(artifactPrefixNamesNodeValue!!, String::class.java)
-
-                        /** Storing mapping entries with metadata log-protect set to true */
-                        val sensitiveParameters: List<String> = artifactPrefixNames
-                                .map { "$it-mapping" }
-                                .map { blueprintRuntimeService.resolveNodeTemplateArtifact(nodeTemplateName, it) }
-                                .flatMap { JacksonUtils.getListFromJson(it, ResourceAssignment::class.java) }
-                                .filter { PropertyDefinitionUtils.hasLogProtect(it.property) }
-                                .map { it.name }
-
-                        /** Hiding sensitive input parameters from the request */
-                        var workflowProperties: ObjectNode = clonedExecutionServiceInput.payload
-                                .path("$workflowName-request")
-                                .path("$workflowName-properties") as ObjectNode
-
-                        sensitiveParameters.forEach { sensitiveParameter ->
-                            if (workflowProperties.has(sensitiveParameter)) {
-                                workflowProperties.replace(sensitiveParameter, ApplicationConstants.LOG_REDACTED.asJsonPrimitive())
-                            }
+                val workflowSteps = blueprintContext.workflowByName(workflowName).steps
+                checkNotNull(workflowSteps) { "Failed to get step(s) for workflow($workflowName)" }
+                workflowSteps.forEach { step ->
+                    val nodeTemplateName = step.value.target
+                    checkNotNull(nodeTemplateName) { "Failed to get node template target for workflow($workflowName), step($step)" }
+                    val nodeTemplate = blueprintContext.nodeTemplateByName(nodeTemplateName)
+
+                    /** We need to check in his Node Template Dependencies is case of a Node Template DG */
+                    if (nodeTemplate.type == BluePrintConstants.NODE_TEMPLATE_TYPE_DG) {
+                        val dependencyNodeTemplate = nodeTemplate.properties?.get(BluePrintConstants.PROPERTY_DG_DEPENDENCY_NODE_TEMPLATE) as ArrayNode
+                        dependencyNodeTemplate.forEach { dependencyNodeTemplateName ->
+                            clonedExecutionServiceInput = hideSensitiveDataFromResourceResolution(
+                                    blueprintRuntimeService,
+                                    blueprintContext,
+                                    clonedExecutionServiceInput,
+                                    workflowName,
+                                    dependencyNodeTemplateName.asText()
+                            )
                         }
+                    } else {
+                        clonedExecutionServiceInput = hideSensitiveDataFromResourceResolution(
+                                blueprintRuntimeService,
+                                blueprintContext,
+                                clonedExecutionServiceInput,
+                                workflowName,
+                                nodeTemplateName
+                        )
                     }
                 }
             }
         } catch (e: Exception) {
-        val errMsg = "ERROR : Couldn't hide sensitive data in the execution request."
-        log.error(errMsg, e)
-        clonedExecutionServiceInput.payload.replace(
-                "$workflowName-request",
-                "$errMsg $e".asJsonPrimitive())
+            val errMsg = "ERROR : Couldn't hide sensitive data in the execution request."
+            log.error(errMsg, e)
+            clonedExecutionServiceInput.payload.replace(
+                    "$workflowName-request",
+                    "$errMsg $e".asJsonPrimitive())
         }
         return clonedExecutionServiceInput
     }
+
+    /**
+     * Hide sensitive data in [executionServiceInput] if the given [nodeTemplateName] is a
+     * resource resolution component.
+     * @param blueprintRuntimeService Current blueprint runtime service
+     * @param blueprintContext Current blueprint runtime context
+     * @param executionServiceInput BP Execution Request where data needs to be hidden
+     * @param workflowName Current workflow being executed
+     * @param nodeTemplateName Node template to check for sensitive data
+     * @return [executionServiceInput] with sensitive inputs replaced by a generic string
+     */
+    private suspend fun hideSensitiveDataFromResourceResolution(
+        blueprintRuntimeService: BluePrintRuntimeService<MutableMap<String, JsonNode>>,
+        blueprintContext: BluePrintContext,
+        executionServiceInput: ExecutionServiceInput,
+        workflowName: String,
+        nodeTemplateName: String
+    ): ExecutionServiceInput {
+
+        val nodeTemplate = blueprintContext.nodeTemplateByName(nodeTemplateName)
+        if (nodeTemplate.type == BluePrintConstants.NODE_TEMPLATE_TYPE_COMPONENT_RESOURCE_RESOLUTION) {
+            val interfaceName = blueprintContext.nodeTemplateFirstInterfaceName(nodeTemplateName)
+            val operationName = blueprintContext.nodeTemplateFirstInterfaceFirstOperationName(nodeTemplateName)
+
+            val propertyAssignments: MutableMap<String, JsonNode> =
+                    blueprintContext.nodeTemplateInterfaceOperationInputs(nodeTemplateName, interfaceName, operationName)
+                            ?: hashMapOf()
+
+            /** Getting values define in artifact-prefix-names */
+            val input = executionServiceInput.payload.get("$workflowName-request")
+            blueprintRuntimeService.assignWorkflowInputs(workflowName, input)
+            val artifactPrefixNamesNode = propertyAssignments[ResourceResolutionConstants.INPUT_ARTIFACT_PREFIX_NAMES]
+            val propertyAssignmentService = PropertyAssignmentService(blueprintRuntimeService)
+            val artifactPrefixNamesNodeValue = propertyAssignmentService.resolveAssignmentExpression(
+                    BluePrintConstants.MODEL_DEFINITION_TYPE_NODE_TEMPLATE,
+                    nodeTemplateName,
+                    ResourceResolutionConstants.INPUT_ARTIFACT_PREFIX_NAMES,
+                    artifactPrefixNamesNode!!)
+
+            val artifactPrefixNames = JacksonUtils.getListFromJsonNode(artifactPrefixNamesNodeValue!!, String::class.java)
+
+            /** Storing mapping entries with metadata log-protect set to true */
+            val sensitiveParameters: List<String> = artifactPrefixNames
+                    .map { "$it-mapping" }
+                    .map { blueprintRuntimeService.resolveNodeTemplateArtifact(nodeTemplateName, it) }
+                    .flatMap { JacksonUtils.getListFromJson(it, ResourceAssignment::class.java) }
+                    .filter { PropertyDefinitionUtils.hasLogProtect(it.property) }
+                    .map { it.name }
+
+            /** Hiding sensitive input parameters from the request */
+            var workflowProperties: ObjectNode = executionServiceInput.payload
+                    .path("$workflowName-request")
+                    .path("$workflowName-properties") as ObjectNode
+
+            sensitiveParameters.forEach { sensitiveParameter ->
+                if (workflowProperties.has(sensitiveParameter)) {
+                    workflowProperties.replace(sensitiveParameter, ApplicationConstants.LOG_REDACTED.asJsonPrimitive())
+                }
+            }
+        }
+        return executionServiceInput
+    }
 }