Renaming Files having BluePrint to have Blueprint
[ccsdk/cds.git] / ms / blueprintsprocessor / modules / inbounds / selfservice-api / src / main / kotlin / org / onap / ccsdk / cds / blueprintsprocessor / selfservice / api / KafkaPublishAuditService.kt
1 /*
2  * Copyright © 2020 Bell Canada
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api
18
19 import com.fasterxml.jackson.databind.JsonNode
20 import com.fasterxml.jackson.databind.node.ArrayNode
21 import com.fasterxml.jackson.databind.node.ObjectNode
22 import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ExecutionServiceInput
23 import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ExecutionServiceOutput
24 import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.ResourceResolutionConstants
25 import org.onap.ccsdk.cds.blueprintsprocessor.message.service.BlueprintMessageLibPropertyService
26 import org.onap.ccsdk.cds.blueprintsprocessor.message.service.BlueprintMessageProducerService
27 import org.onap.ccsdk.cds.controllerblueprints.core.BlueprintConstants
28 import org.onap.ccsdk.cds.controllerblueprints.core.asJsonPrimitive
29 import org.onap.ccsdk.cds.controllerblueprints.core.common.ApplicationConstants
30 import org.onap.ccsdk.cds.controllerblueprints.core.interfaces.BlueprintCatalogService
31 import org.onap.ccsdk.cds.controllerblueprints.core.service.BlueprintContext
32 import org.onap.ccsdk.cds.controllerblueprints.core.service.BlueprintRuntimeService
33 import org.onap.ccsdk.cds.controllerblueprints.core.service.PropertyAssignmentService
34 import org.onap.ccsdk.cds.controllerblueprints.core.utils.BlueprintMetadataUtils
35 import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils
36 import org.onap.ccsdk.cds.controllerblueprints.core.utils.PropertyDefinitionUtils
37 import org.onap.ccsdk.cds.controllerblueprints.resource.dict.ResourceAssignment
38 import org.slf4j.LoggerFactory
39 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
40 import org.springframework.stereotype.Service
41 import javax.annotation.PostConstruct
42
43 /**
44  * Audit service used to produce execution service input and output message
45  * sent into dedicated kafka topics.
46  *
47  * @param bluePrintMessageLibPropertyService Service used to instantiate audit service producers
48  * @param blueprintsProcessorCatalogService Service used to get the base path of the current CBA executed
49  *
50  * @property inputInstance Request Kakfa Producer instance
51  * @property outputInstance Response Kakfa Producer instance
52  * @property log Audit Service logger
53  */
54 @ConditionalOnProperty(
55     name = ["blueprintsprocessor.messageproducer.self-service-api.audit.kafkaEnable"],
56     havingValue = "true"
57 )
58 @Service
59 class KafkaPublishAuditService(
60     private val bluePrintMessageLibPropertyService: BlueprintMessageLibPropertyService,
61     private val blueprintsProcessorCatalogService: BlueprintCatalogService
62 ) : PublishAuditService {
63
64     private var inputInstance: BlueprintMessageProducerService? = null
65     private var outputInstance: BlueprintMessageProducerService? = null
66     private val log = LoggerFactory.getLogger(KafkaPublishAuditService::class.toString())
67
68     companion object {
69
70         const val INPUT_SELECTOR = "self-service-api.audit.request"
71         const val OUTPUT_SELECTOR = "self-service-api.audit.response"
72     }
73
74     @PostConstruct
75     private fun init() {
76         log.info("Kakfa audit service is enabled")
77     }
78
79     /**
80      * Publish execution input into a kafka topic.
81      * The correlation UUID is used to link the input to its output.
82      * Sensitive data within the request are hidden.
83      * @param executionServiceInput Audited BP request
84      */
85     override suspend fun publishExecutionInput(executionServiceInput: ExecutionServiceInput) {
86         val secureExecutionServiceInput = hideSensitiveData(executionServiceInput)
87         val key = secureExecutionServiceInput.actionIdentifiers.blueprintName
88         try {
89             this.inputInstance = this.getInputInstance(INPUT_SELECTOR)
90             this.inputInstance!!.sendMessage(key, secureExecutionServiceInput)
91         } catch (e: Exception) {
92             var errMsg =
93                 if (e.message != null) "ERROR : ${e.message}"
94                 else "ERROR : Failed to send execution request to Kafka."
95             log.error(errMsg)
96         }
97     }
98
99     /**
100      * Publish execution output into a kafka topic.
101      * The correlation UUID is used to link the output to its input.
102      * A correlation UUID is added to link the input to its output.
103      * @param correlationUUID UUID used to link the audited response to its audited request
104      * @param executionServiceOutput Audited BP response
105      */
106     override suspend fun publishExecutionOutput(correlationUUID: String, executionServiceOutput: ExecutionServiceOutput) {
107         executionServiceOutput.correlationUUID = correlationUUID
108         val key = executionServiceOutput.actionIdentifiers.blueprintName
109         try {
110             this.outputInstance = this.getOutputInstance(OUTPUT_SELECTOR)
111             this.outputInstance!!.sendMessage(key, executionServiceOutput)
112         } catch (e: Exception) {
113             var errMsg =
114                 if (e.message != null) "ERROR : $e"
115                 else "ERROR : Failed to send execution request to Kafka."
116             log.error(errMsg)
117         }
118     }
119
120     /**
121      * Return the input kafka producer instance using a [selector] if not already instantiated.
122      * @param selector Selector to retrieve request kafka producer configuration
123      */
124     private fun getInputInstance(selector: String): BlueprintMessageProducerService = inputInstance ?: createInstance(selector)
125
126     /**
127      * Return the output kafka producer instance using a [selector] if not already instantiated.
128      * @param selector Selector to retrieve response kafka producer configuration
129      */
130     private fun getOutputInstance(selector: String): BlueprintMessageProducerService = outputInstance ?: createInstance(selector)
131
132     /**
133      * Create a kafka producer instance using a [selector].
134      * @param selector Selector to retrieve kafka producer configuration
135      */
136     private fun createInstance(selector: String): BlueprintMessageProducerService {
137         log.info("Setting up message producer($selector)...")
138         return bluePrintMessageLibPropertyService.blueprintMessageProducerService(selector)
139     }
140
141     /**
142      * Hide sensitive data in the [executionServiceInput].
143      * Sensitive data are declared in the resource resolution mapping using
144      * the property metadata "log-protect" set to true.
145      * @param executionServiceInput BP Execution Request where data needs to be hidden
146      */
147     private suspend fun hideSensitiveData(
148         executionServiceInput: ExecutionServiceInput
149     ): ExecutionServiceInput {
150
151         var clonedExecutionServiceInput = ExecutionServiceInput().apply {
152             correlationUUID = executionServiceInput.correlationUUID
153             commonHeader = executionServiceInput.commonHeader
154             actionIdentifiers = executionServiceInput.actionIdentifiers
155             payload = executionServiceInput.payload.deepCopy()
156             stepData = executionServiceInput.stepData
157         }
158
159         val blueprintName = clonedExecutionServiceInput.actionIdentifiers.blueprintName
160         val workflowName = clonedExecutionServiceInput.actionIdentifiers.actionName
161
162         if (blueprintName == "default") return clonedExecutionServiceInput
163
164         try {
165             if (clonedExecutionServiceInput.payload
166                 .path("$workflowName-request").has("$workflowName-properties")
167             ) {
168
169                 /** Retrieving sensitive input parameters */
170                 val requestId = clonedExecutionServiceInput.commonHeader.requestId
171                 val blueprintVersion = clonedExecutionServiceInput.actionIdentifiers.blueprintVersion
172
173                 val basePath = blueprintsProcessorCatalogService.getFromDatabase(blueprintName, blueprintVersion)
174
175                 val blueprintRuntimeService = BlueprintMetadataUtils.getBlueprintRuntime(requestId, basePath.toString())
176                 val blueprintContext = blueprintRuntimeService.bluePrintContext()
177
178                 val workflowSteps = blueprintContext.workflowByName(workflowName).steps
179                 checkNotNull(workflowSteps) { "Failed to get step(s) for workflow($workflowName)" }
180                 workflowSteps.forEach { step ->
181                     val nodeTemplateName = step.value.target
182                     checkNotNull(nodeTemplateName) { "Failed to get node template target for workflow($workflowName), step($step)" }
183                     val nodeTemplate = blueprintContext.nodeTemplateByName(nodeTemplateName)
184
185                     /** We need to check in his Node Template Dependencies is case of a Node Template DG */
186                     if (nodeTemplate.type == BlueprintConstants.NODE_TEMPLATE_TYPE_DG) {
187                         val dependencyNodeTemplate =
188                             nodeTemplate.properties?.get(BlueprintConstants.PROPERTY_DG_DEPENDENCY_NODE_TEMPLATE) as ArrayNode
189                         dependencyNodeTemplate.forEach { dependencyNodeTemplateName ->
190                             clonedExecutionServiceInput = hideSensitiveDataFromResourceResolution(
191                                 blueprintRuntimeService,
192                                 blueprintContext,
193                                 clonedExecutionServiceInput,
194                                 workflowName,
195                                 dependencyNodeTemplateName.asText()
196                             )
197                         }
198                     } else {
199                         clonedExecutionServiceInput = hideSensitiveDataFromResourceResolution(
200                             blueprintRuntimeService,
201                             blueprintContext,
202                             clonedExecutionServiceInput,
203                             workflowName,
204                             nodeTemplateName
205                         )
206                     }
207                 }
208             }
209         } catch (e: Exception) {
210             val errMsg = "ERROR : Couldn't hide sensitive data in the execution request."
211             log.error(errMsg, e)
212             clonedExecutionServiceInput.payload.replace(
213                 "$workflowName-request",
214                 "$errMsg $e".asJsonPrimitive()
215             )
216         }
217         return clonedExecutionServiceInput
218     }
219
220     /**
221      * Hide sensitive data in [executionServiceInput] if the given [nodeTemplateName] is a
222      * resource resolution component.
223      * @param blueprintRuntimeService Current blueprint runtime service
224      * @param blueprintContext Current blueprint runtime context
225      * @param executionServiceInput BP Execution Request where data needs to be hidden
226      * @param workflowName Current workflow being executed
227      * @param nodeTemplateName Node template to check for sensitive data
228      * @return [executionServiceInput] with sensitive inputs replaced by a generic string
229      */
230     private suspend fun hideSensitiveDataFromResourceResolution(
231         blueprintRuntimeService: BlueprintRuntimeService<MutableMap<String, JsonNode>>,
232         blueprintContext: BlueprintContext,
233         executionServiceInput: ExecutionServiceInput,
234         workflowName: String,
235         nodeTemplateName: String
236     ): ExecutionServiceInput {
237
238         val nodeTemplate = blueprintContext.nodeTemplateByName(nodeTemplateName)
239         if (nodeTemplate.type == BlueprintConstants.NODE_TEMPLATE_TYPE_COMPONENT_RESOURCE_RESOLUTION) {
240             val interfaceName = blueprintContext.nodeTemplateFirstInterfaceName(nodeTemplateName)
241             val operationName = blueprintContext.nodeTemplateFirstInterfaceFirstOperationName(nodeTemplateName)
242
243             val propertyAssignments: MutableMap<String, JsonNode> =
244                 blueprintContext.nodeTemplateInterfaceOperationInputs(nodeTemplateName, interfaceName, operationName)
245                     ?: hashMapOf()
246
247             /** Getting values define in artifact-prefix-names */
248             val input = executionServiceInput.payload.get("$workflowName-request")
249             blueprintRuntimeService.assignWorkflowInputs(workflowName, input)
250             val artifactPrefixNamesNode = propertyAssignments[ResourceResolutionConstants.INPUT_ARTIFACT_PREFIX_NAMES]
251             val propertyAssignmentService = PropertyAssignmentService(blueprintRuntimeService)
252             val artifactPrefixNamesNodeValue = propertyAssignmentService.resolveAssignmentExpression(
253                 BlueprintConstants.MODEL_DEFINITION_TYPE_NODE_TEMPLATE,
254                 nodeTemplateName,
255                 ResourceResolutionConstants.INPUT_ARTIFACT_PREFIX_NAMES,
256                 artifactPrefixNamesNode!!
257             )
258
259             val artifactPrefixNames = JacksonUtils.getListFromJsonNode(artifactPrefixNamesNodeValue!!, String::class.java)
260
261             /** Storing mapping entries with metadata log-protect set to true */
262             val sensitiveParameters: List<String> = artifactPrefixNames
263                 .map { "$it-mapping" }
264                 .map { blueprintRuntimeService.resolveNodeTemplateArtifact(nodeTemplateName, it) }
265                 .flatMap { JacksonUtils.getListFromJson(it, ResourceAssignment::class.java) }
266                 .filter { PropertyDefinitionUtils.hasLogProtect(it.property) }
267                 .map { it.name }
268
269             /** Hiding sensitive input parameters from the request */
270             var workflowProperties: ObjectNode = executionServiceInput.payload
271                 .path("$workflowName-request")
272                 .path("$workflowName-properties") as ObjectNode
273
274             sensitiveParameters.forEach { sensitiveParameter ->
275                 if (workflowProperties.has(sensitiveParameter)) {
276                     workflowProperties.replace(sensitiveParameter, ApplicationConstants.LOG_REDACTED.asJsonPrimitive())
277                 }
278             }
279         }
280         return executionServiceInput
281     }
282 }