fca73981e970ca3a510725fea0874cddf7a1dda3
[ccsdk/cds.git] /
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.ObjectNode
21 import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ExecutionServiceInput
22 import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ExecutionServiceOutput
23 import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.ResourceResolutionConstants
24 import org.onap.ccsdk.cds.blueprintsprocessor.message.service.BluePrintMessageLibPropertyService
25 import org.onap.ccsdk.cds.blueprintsprocessor.message.service.BlueprintMessageProducerService
26 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants
27 import org.onap.ccsdk.cds.controllerblueprints.core.asJsonPrimitive
28 import org.onap.ccsdk.cds.controllerblueprints.core.common.ApplicationConstants
29 import org.onap.ccsdk.cds.controllerblueprints.core.interfaces.BluePrintCatalogService
30 import org.onap.ccsdk.cds.controllerblueprints.core.service.PropertyAssignmentService
31 import org.onap.ccsdk.cds.controllerblueprints.core.utils.BluePrintMetadataUtils
32 import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils
33 import org.onap.ccsdk.cds.controllerblueprints.core.utils.PropertyDefinitionUtils
34 import org.onap.ccsdk.cds.controllerblueprints.resource.dict.ResourceAssignment
35 import org.slf4j.LoggerFactory
36 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
37 import org.springframework.stereotype.Service
38 import javax.annotation.PostConstruct
39
40 /**
41  * Audit service used to produce execution service input and output message
42  * sent into dedicated kafka topics.
43  */
44 @ConditionalOnProperty(
45         name = ["blueprintsprocessor.messageproducer.self-service-api.audit.kafkaEnable"],
46         havingValue = "true"
47 )
48 @Service
49 class KafkaPublishAuditService(
50     private val bluePrintMessageLibPropertyService: BluePrintMessageLibPropertyService,
51     private val blueprintsProcessorCatalogService: BluePrintCatalogService
52 ) : PublishAuditService {
53     private var inputInstance: BlueprintMessageProducerService? = null
54     private var outputInstance: BlueprintMessageProducerService? = null
55     private val log = LoggerFactory.getLogger(KafkaPublishAuditService::class.toString())
56
57     companion object {
58         const val INPUT_SELECTOR = "self-service-api.audit.request"
59         const val OUTPUT_SELECTOR = "self-service-api.audit.response"
60     }
61
62     @PostConstruct
63     private fun init() {
64         log.info("Kakfa audit service is enabled")
65     }
66
67     /**
68      * Publish execution input into a kafka topic.
69      * The correlation UUID is used to link the input to its output.
70      * Sensitive data within the request are hidden.
71      */
72     override suspend fun publishExecutionInput(executionServiceInput: ExecutionServiceInput) {
73         val secureExecutionServiceInput = hideSensitiveData(executionServiceInput)
74         try {
75             this.inputInstance = this.getInputInstance(INPUT_SELECTOR)
76             this.inputInstance!!.sendMessage(secureExecutionServiceInput)
77         } catch (e: Exception) {
78             var errMsg =
79                     if (e.message != null) "ERROR : ${e.message}"
80                     else "ERROR : Failed to send execution request to Kafka."
81             log.error(errMsg)
82         }
83     }
84
85     /**
86      * Publish execution output into a kafka topic.
87      * The correlation UUID is used to link the output to its input.
88      * A correlation UUID is added to link the input to its output.
89      */
90     override suspend fun publishExecutionOutput(correlationUUID: String, executionServiceOutput: ExecutionServiceOutput) {
91         executionServiceOutput.correlationUUID = correlationUUID
92         try {
93             this.outputInstance = this.getOutputInstance(OUTPUT_SELECTOR)
94             this.outputInstance!!.sendMessage(executionServiceOutput)
95         } catch (e: Exception) {
96             var errMsg =
97                 if (e.message != null) "ERROR : $e"
98                 else "ERROR : Failed to send execution request to Kafka."
99             log.error(errMsg)
100         }
101     }
102
103     /**
104      * Return the input kafka producer instance using a selector.
105      */
106     private fun getInputInstance(selector: String): BlueprintMessageProducerService = inputInstance ?: createInstance(selector)
107
108     /**
109      * Return the output kafka producer instance using a selector.
110      */
111     private fun getOutputInstance(selector: String): BlueprintMessageProducerService = outputInstance ?: createInstance(selector)
112
113     /**
114      * Create a kafka producer instance.
115      */
116     private fun createInstance(selector: String): BlueprintMessageProducerService {
117         log.info("Setting up message producer($selector)...")
118         return bluePrintMessageLibPropertyService.blueprintMessageProducerService(selector)
119     }
120
121     /**
122      * Hide sensitive data in the request.
123      * Sensitive data are declared in the resource resolution mapping using
124      * the property metadata "log-protect" set to true.
125      */
126     private suspend fun hideSensitiveData(
127         executionServiceInput: ExecutionServiceInput
128     ): ExecutionServiceInput {
129
130         var clonedExecutionServiceInput = ExecutionServiceInput().apply {
131             correlationUUID = executionServiceInput.correlationUUID
132             commonHeader = executionServiceInput.commonHeader
133             actionIdentifiers = executionServiceInput.actionIdentifiers
134             payload = executionServiceInput.payload.deepCopy()
135             stepData = executionServiceInput.stepData
136         }
137
138         val blueprintName = clonedExecutionServiceInput.actionIdentifiers.blueprintName
139         val workflowName = clonedExecutionServiceInput.actionIdentifiers.actionName
140
141         if (blueprintName == "default") return clonedExecutionServiceInput
142
143         try {
144             if (clonedExecutionServiceInput.payload
145                             .path("$workflowName-request").has("$workflowName-properties")) {
146
147                 /** Retrieving sensitive input parameters */
148                 val requestId = clonedExecutionServiceInput.commonHeader.requestId
149                 val blueprintVersion = clonedExecutionServiceInput.actionIdentifiers.blueprintVersion
150
151                 val basePath = blueprintsProcessorCatalogService.getFromDatabase(blueprintName, blueprintVersion)
152
153                 val blueprintRuntimeService = BluePrintMetadataUtils.getBluePrintRuntime(requestId, basePath.toString())
154                 val blueprintContext = blueprintRuntimeService.bluePrintContext()
155
156                 /** Looking for node templates defined as component-resource-resolution */
157                 val nodeTemplates = blueprintContext.nodeTemplates()
158                 nodeTemplates!!.forEach { nodeTemplate ->
159                     val nodeTemplateName = nodeTemplate.key
160                     val nodeTemplateType = blueprintContext.nodeTemplateByName(nodeTemplateName).type
161                     if (nodeTemplateType == BluePrintConstants.NODE_TEMPLATE_TYPE_COMPONENT_RESOURCE_RESOLUTION) {
162                         val interfaceName = blueprintContext.nodeTemplateFirstInterfaceName(nodeTemplateName)
163                         val operationName = blueprintContext.nodeTemplateFirstInterfaceFirstOperationName(nodeTemplateName)
164
165                         val propertyAssignments: MutableMap<String, JsonNode> =
166                                 blueprintContext.nodeTemplateInterfaceOperationInputs(nodeTemplateName, interfaceName, operationName)
167                                         ?: hashMapOf()
168
169                         /** Getting values define in artifact-prefix-names */
170                         val input = executionServiceInput.payload.get("$workflowName-request")
171                         blueprintRuntimeService.assignWorkflowInputs(workflowName, input)
172                         val artifactPrefixNamesNode = propertyAssignments[ResourceResolutionConstants.INPUT_ARTIFACT_PREFIX_NAMES]
173                         val propertyAssignmentService = PropertyAssignmentService(blueprintRuntimeService)
174                         val artifactPrefixNamesNodeValue = propertyAssignmentService.resolveAssignmentExpression(
175                                 BluePrintConstants.MODEL_DEFINITION_TYPE_NODE_TEMPLATE,
176                                 nodeTemplateName,
177                                 ResourceResolutionConstants.INPUT_ARTIFACT_PREFIX_NAMES,
178                                 artifactPrefixNamesNode!!)
179
180                         val artifactPrefixNames = JacksonUtils.getListFromJsonNode(artifactPrefixNamesNodeValue!!, String::class.java)
181
182                         /** Storing mapping entries with metadata log-protect set to true */
183                         val sensitiveParameters: List<String> = artifactPrefixNames
184                                 .map { "$it-mapping" }
185                                 .map { blueprintRuntimeService.resolveNodeTemplateArtifact(nodeTemplateName, it) }
186                                 .flatMap { JacksonUtils.getListFromJson(it, ResourceAssignment::class.java) }
187                                 .filter { PropertyDefinitionUtils.hasLogProtect(it.property) }
188                                 .map { it.name }
189
190                         /** Hiding sensitive input parameters from the request */
191                         var workflowProperties: ObjectNode = clonedExecutionServiceInput.payload
192                                 .path("$workflowName-request")
193                                 .path("$workflowName-properties") as ObjectNode
194
195                         sensitiveParameters.forEach { sensitiveParameter ->
196                             if (workflowProperties.has(sensitiveParameter)) {
197                                 workflowProperties.replace(sensitiveParameter, ApplicationConstants.LOG_REDACTED.asJsonPrimitive())
198                             }
199                         }
200                     }
201                 }
202             }
203         } catch (e: Exception) {
204         val errMsg = "ERROR : Couldn't hide sensitive data in the execution request."
205         log.error(errMsg, e)
206         clonedExecutionServiceInput.payload.replace(
207                 "$workflowName-request",
208                 "$errMsg $e".asJsonPrimitive())
209         }
210         return clonedExecutionServiceInput
211     }
212 }