2 * Copyright © 2020 Bell Canada
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api
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
44 * Audit service used to produce execution service input and output message
45 * sent into dedicated kafka topics.
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
50 * @property inputInstance Request Kakfa Producer instance
51 * @property outputInstance Response Kakfa Producer instance
52 * @property log Audit Service logger
54 @ConditionalOnProperty(
55 name = ["blueprintsprocessor.messageproducer.self-service-api.audit.kafkaEnable"],
59 class KafkaPublishAuditService(
60 private val bluePrintMessageLibPropertyService: BlueprintMessageLibPropertyService,
61 private val blueprintsProcessorCatalogService: BlueprintCatalogService
62 ) : PublishAuditService {
64 private var inputInstance: BlueprintMessageProducerService? = null
65 private var outputInstance: BlueprintMessageProducerService? = null
66 private val log = LoggerFactory.getLogger(KafkaPublishAuditService::class.toString())
70 const val INPUT_SELECTOR = "self-service-api.audit.request"
71 const val OUTPUT_SELECTOR = "self-service-api.audit.response"
76 log.info("Kakfa audit service is enabled")
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
85 override suspend fun publishExecutionInput(executionServiceInput: ExecutionServiceInput) {
86 val secureExecutionServiceInput = hideSensitiveData(executionServiceInput)
87 val key = secureExecutionServiceInput.actionIdentifiers.blueprintName
89 this.inputInstance = this.getInputInstance(INPUT_SELECTOR)
90 this.inputInstance!!.sendMessage(key, secureExecutionServiceInput)
91 } catch (e: Exception) {
93 if (e.message != null) "ERROR : ${e.message}"
94 else "ERROR : Failed to send execution request to Kafka."
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
106 override suspend fun publishExecutionOutput(correlationUUID: String, executionServiceOutput: ExecutionServiceOutput) {
107 executionServiceOutput.correlationUUID = correlationUUID
108 val key = executionServiceOutput.actionIdentifiers.blueprintName
110 this.outputInstance = this.getOutputInstance(OUTPUT_SELECTOR)
111 this.outputInstance!!.sendMessage(key, executionServiceOutput)
112 } catch (e: Exception) {
114 if (e.message != null) "ERROR : $e"
115 else "ERROR : Failed to send execution request to Kafka."
121 * Return the input kafka producer instance using a [selector] if not already instantiated.
122 * @param selector Selector to retrieve request kafka producer configuration
124 private fun getInputInstance(selector: String): BlueprintMessageProducerService = inputInstance ?: createInstance(selector)
127 * Return the output kafka producer instance using a [selector] if not already instantiated.
128 * @param selector Selector to retrieve response kafka producer configuration
130 private fun getOutputInstance(selector: String): BlueprintMessageProducerService = outputInstance ?: createInstance(selector)
133 * Create a kafka producer instance using a [selector].
134 * @param selector Selector to retrieve kafka producer configuration
136 private fun createInstance(selector: String): BlueprintMessageProducerService {
137 log.info("Setting up message producer($selector)...")
138 return bluePrintMessageLibPropertyService.blueprintMessageProducerService(selector)
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
147 private suspend fun hideSensitiveData(
148 executionServiceInput: ExecutionServiceInput
149 ): ExecutionServiceInput {
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
159 val blueprintName = clonedExecutionServiceInput.actionIdentifiers.blueprintName
160 val workflowName = clonedExecutionServiceInput.actionIdentifiers.actionName
162 if (blueprintName == "default") return clonedExecutionServiceInput
165 if (clonedExecutionServiceInput.payload
166 .path("$workflowName-request").has("$workflowName-properties")
169 /** Retrieving sensitive input parameters */
170 val requestId = clonedExecutionServiceInput.commonHeader.requestId
171 val blueprintVersion = clonedExecutionServiceInput.actionIdentifiers.blueprintVersion
173 val basePath = blueprintsProcessorCatalogService.getFromDatabase(blueprintName, blueprintVersion)
175 val blueprintRuntimeService = BlueprintMetadataUtils.getBlueprintRuntime(requestId, basePath.toString())
176 val blueprintContext = blueprintRuntimeService.bluePrintContext()
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)
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,
193 clonedExecutionServiceInput,
195 dependencyNodeTemplateName.asText()
199 clonedExecutionServiceInput = hideSensitiveDataFromResourceResolution(
200 blueprintRuntimeService,
202 clonedExecutionServiceInput,
209 } catch (e: Exception) {
210 val errMsg = "ERROR : Couldn't hide sensitive data in the execution request."
212 clonedExecutionServiceInput.payload.replace(
213 "$workflowName-request",
214 "$errMsg $e".asJsonPrimitive()
217 return clonedExecutionServiceInput
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
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 {
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)
243 val propertyAssignments: MutableMap<String, JsonNode> =
244 blueprintContext.nodeTemplateInterfaceOperationInputs(nodeTemplateName, interfaceName, operationName)
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,
255 ResourceResolutionConstants.INPUT_ARTIFACT_PREFIX_NAMES,
256 artifactPrefixNamesNode!!
259 val artifactPrefixNames = JacksonUtils.getListFromJsonNode(artifactPrefixNamesNodeValue!!, String::class.java)
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) }
269 /** Hiding sensitive input parameters from the request */
270 var workflowProperties: ObjectNode = executionServiceInput.payload
271 .path("$workflowName-request")
272 .path("$workflowName-properties") as ObjectNode
274 sensitiveParameters.forEach { sensitiveParameter ->
275 if (workflowProperties.has(sensitiveParameter)) {
276 workflowProperties.replace(sensitiveParameter, ApplicationConstants.LOG_REDACTED.asJsonPrimitive())
280 return executionServiceInput