Merge "Merge live repo with design changes ccsdk-2309"
[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.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.BluePrintProcessorException
27 import org.onap.ccsdk.cds.controllerblueprints.core.common.ApplicationConstants
28 import org.onap.ccsdk.cds.controllerblueprints.core.interfaces.BluePrintCatalogService
29 import org.onap.ccsdk.cds.controllerblueprints.core.utils.BluePrintMetadataUtils
30 import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils
31 import org.onap.ccsdk.cds.controllerblueprints.core.utils.PropertyDefinitionUtils
32 import org.onap.ccsdk.cds.controllerblueprints.resource.dict.ResourceAssignment
33 import org.slf4j.LoggerFactory
34 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
35 import org.springframework.stereotype.Service
36 import javax.annotation.PostConstruct
37
38 /**
39  * Audit service used to produce execution service input and output message
40  * sent into dedicated kafka topics.
41  */
42 @ConditionalOnProperty(
43         name = ["blueprintsprocessor.messageproducer.self-service-api.audit.kafkaEnable"],
44         havingValue = "true"
45 )
46 @Service
47 class KafkaPublishAuditService(
48     private val bluePrintMessageLibPropertyService: BluePrintMessageLibPropertyService,
49     private val blueprintsProcessorCatalogService: BluePrintCatalogService
50 ) : PublishAuditService {
51
52     private var inputInstance: BlueprintMessageProducerService? = null
53     private var outputInstance: BlueprintMessageProducerService? = null
54
55     private lateinit var correlationUUID: String
56
57     private val log = LoggerFactory.getLogger(KafkaPublishAuditService::class.toString())
58
59     companion object {
60         const val INPUT_SELECTOR = "self-service-api.audit.request"
61         const val OUTPUT_SELECTOR = "self-service-api.audit.response"
62     }
63
64     @PostConstruct
65     private fun init() {
66         log.info("Kakfa audit service is enabled")
67     }
68
69     /**
70      * Publish execution input into a kafka topic.
71      * The correlation UUID is used to link the input to its output.
72      * Sensitive data within the request are hidden.
73      */
74     override suspend fun publish(executionServiceInput: ExecutionServiceInput) {
75         this.correlationUUID = executionServiceInput.correlationUUID
76         val secureExecutionServiceInput = hideSensitiveData(executionServiceInput)
77         this.inputInstance = this.getInputInstance(INPUT_SELECTOR)
78         this.inputInstance!!.sendMessage(secureExecutionServiceInput)
79     }
80
81     /**
82      * Publish execution output into a kafka topic.
83      * The correlation UUID is used to link the output to its input.
84      * A correlation UUID is added to link the input to its output.
85      */
86     override fun publish(executionServiceOutput: ExecutionServiceOutput) {
87         executionServiceOutput.correlationUUID = this.correlationUUID
88         this.outputInstance = this.getOutputInstance(OUTPUT_SELECTOR)
89         this.outputInstance!!.sendMessage(executionServiceOutput)
90     }
91
92     /**
93      * Return the input kafka producer instance using a selector.
94      */
95     private fun getInputInstance(selector: String): BlueprintMessageProducerService = inputInstance ?: createInstance(selector)
96
97     /**
98      * Return the output kafka producer instance using a selector.
99      */
100     private fun getOutputInstance(selector: String): BlueprintMessageProducerService = outputInstance ?: createInstance(selector)
101
102     /**
103      * Create a kafka producer instance.
104      */
105     private fun createInstance(selector: String): BlueprintMessageProducerService {
106         log.info(
107                 "Setting up message producer($selector)..."
108         )
109         return try {
110             bluePrintMessageLibPropertyService
111                     .blueprintMessageProducerService(selector)
112         } catch (e: Exception) {
113             throw BluePrintProcessorException("failed to create producer service ${e.message}")
114         }
115     }
116
117     /**
118      * Hide sensitive data in the request.
119      * Sensitive data are declared in the resource resolution mapping using
120      * the property metadata "log-protect" set to true.
121      */
122     private suspend fun hideSensitiveData(
123         executionServiceInput: ExecutionServiceInput
124     ): ExecutionServiceInput {
125
126         var clonedExecutionServiceInput = ExecutionServiceInput().apply {
127             correlationUUID = executionServiceInput.correlationUUID
128             commonHeader = executionServiceInput.commonHeader
129             actionIdentifiers = executionServiceInput.actionIdentifiers
130             payload = executionServiceInput.payload
131         }
132
133         val blueprintName = clonedExecutionServiceInput.actionIdentifiers.blueprintName
134         val workflowName = clonedExecutionServiceInput.actionIdentifiers.actionName
135
136         if (blueprintName == "default") return clonedExecutionServiceInput
137
138         if (clonedExecutionServiceInput.payload
139                         .path("$workflowName-request").has("$workflowName-properties")) {
140
141             /** Retrieving sensitive input parameters */
142             val requestId = clonedExecutionServiceInput.commonHeader.requestId
143             val blueprintVersion = clonedExecutionServiceInput.actionIdentifiers.blueprintVersion
144
145             val basePath = blueprintsProcessorCatalogService.getFromDatabase(blueprintName, blueprintVersion)
146
147             val blueprintRuntimeService = BluePrintMetadataUtils.getBluePrintRuntime(requestId, basePath.toString())
148             val blueprintContext = blueprintRuntimeService.bluePrintContext()
149
150             val nodeTemplateName = blueprintContext.workflowFirstStepNodeTemplate(workflowName)
151             val interfaceName = blueprintContext.nodeTemplateFirstInterfaceName(nodeTemplateName)
152             val operationName = blueprintContext.nodeTemplateFirstInterfaceFirstOperationName(nodeTemplateName)
153
154             val propertyAssignments: MutableMap<String, JsonNode> =
155                     blueprintContext.nodeTemplateInterfaceOperationInputs(nodeTemplateName, interfaceName, operationName)
156                             ?: hashMapOf()
157
158             val artifactPrefixNamesNode = propertyAssignments[ResourceResolutionConstants.INPUT_ARTIFACT_PREFIX_NAMES]
159             val artifactPrefixNames = JacksonUtils.getListFromJsonNode(artifactPrefixNamesNode!!, String::class.java)
160
161             /** Storing mapping entries with metadata log-protect set to true */
162             val sensitiveParameters: List<String> = artifactPrefixNames
163                     .map { "$it-mapping" }
164                     .map { blueprintRuntimeService.resolveNodeTemplateArtifact(nodeTemplateName, it) }
165                     .flatMap { JacksonUtils.getListFromJson(it, ResourceAssignment::class.java) }
166                     .filter { PropertyDefinitionUtils.hasLogProtect(it.property) }
167                     .map { it.name }
168
169             /** Hiding sensitive input parameters from the request */
170             var workflowProperties: ObjectNode = clonedExecutionServiceInput.payload
171                     .path("$workflowName-request")
172                     .path("$workflowName-properties") as ObjectNode
173
174             sensitiveParameters.forEach { sensitiveParameter ->
175                 if (workflowProperties.has(sensitiveParameter)) {
176                     workflowProperties.remove(sensitiveParameter)
177                     workflowProperties.put(sensitiveParameter, ApplicationConstants.LOG_REDACTED)
178                 }
179             }
180         }
181
182         return clonedExecutionServiceInput
183     }
184 }