diff --git a/adapter/src/main/kotlin/de/fraunhofer/iem/spha/adapter/tools/trufflehog/TrufflehogAdapter.kt b/adapter/src/main/kotlin/de/fraunhofer/iem/spha/adapter/tools/trufflehog/TrufflehogAdapter.kt new file mode 100644 index 0000000..ad934e6 --- /dev/null +++ b/adapter/src/main/kotlin/de/fraunhofer/iem/spha/adapter/tools/trufflehog/TrufflehogAdapter.kt @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2024 Fraunhofer IEM. All rights reserved. + * + * Licensed under the MIT license. See LICENSE file in the project root for details. + * + * SPDX-License-Identifier: MIT + * License-Filename: LICENSE + */ + +package de.fraunhofer.iem.spha.adapter.tools.trufflehog + +import de.fraunhofer.iem.spha.adapter.AdapterResult +import de.fraunhofer.iem.spha.model.adapter.trufflehog.TrufflehogDto +import de.fraunhofer.iem.spha.model.adapter.trufflehog.TrufflehogReportDto +import de.fraunhofer.iem.spha.model.kpi.KpiId +import de.fraunhofer.iem.spha.model.kpi.RawValueKpi +import io.github.oshai.kotlinlogging.KotlinLogging +import java.io.InputStream +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.decodeFromJsonElement +import kotlinx.serialization.json.decodeFromStream + +object TrufflehogAdapter { + private val logger = KotlinLogging.logger {} + private val jsonParser = Json { + ignoreUnknownKeys = true + explicitNulls = false + } + + @OptIn(ExperimentalSerializationApi::class) + fun dtoFromJson(jsonData: InputStream): List { + val rawResult = jsonParser.decodeFromStream(jsonData) + return rawResult.results.mapNotNull { + try { + jsonParser.decodeFromJsonElement(it) + } catch (e: Exception) { + logger.warn { "Decoding of trufflehog result failed for $it with ${e.message}" } + null + } + } + } + + fun transformDataToKpi(data: TrufflehogReportDto): Collection { + return transformDataToKpi(listOf(data)) + } + + fun transformDataToKpi(data: Collection): Collection { + return data.map { + val score = if (it.verifiedSecrets > 0) 0 else 100 + AdapterResult.Success.Kpi(RawValueKpi(score = score, kind = KpiId.SECRETS)) + } + } +} diff --git a/adapter/src/test/kotlin/de/fraunhofer/iem/spha/adapter/tools/trufflehog/TrufflehogAdapterTest.kt b/adapter/src/test/kotlin/de/fraunhofer/iem/spha/adapter/tools/trufflehog/TrufflehogAdapterTest.kt new file mode 100644 index 0000000..7e01e7a --- /dev/null +++ b/adapter/src/test/kotlin/de/fraunhofer/iem/spha/adapter/tools/trufflehog/TrufflehogAdapterTest.kt @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2024 Fraunhofer IEM. All rights reserved. + * + * Licensed under the MIT license. See LICENSE file in the project root for details. + * + * SPDX-License-Identifier: MIT + * License-Filename: LICENSE + */ + +package de.fraunhofer.iem.spha.adapter.tools.trufflehog + +import de.fraunhofer.iem.spha.adapter.AdapterResult +import java.nio.file.Files +import kotlin.io.path.Path +import kotlin.test.Test +import kotlin.test.assertEquals +import org.junit.jupiter.api.assertDoesNotThrow +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource + +class TrufflehogAdapterTest { + @ParameterizedTest + @ValueSource( + strings = + [ + "{}", // No schema + "{\"SchemaVersion\": 3}", // Not supported schema + ] + ) + fun testInvalidJson(input: String) { + input.byteInputStream().use { + assertThrows { TrufflehogAdapter.dtoFromJson(it) } + } + } + + @ParameterizedTest + @ValueSource(strings = ["{\"results\": []}"]) + fun testEmptyDto(input: String) { + input.byteInputStream().use { + val dto = TrufflehogAdapter.dtoFromJson(it) + assertEquals(0, dto.count()) + } + } + + @Test + fun testResultDto() { + Files.newInputStream(Path("src/test/resources/trufflehog-no-result.json")).use { + val dto = assertDoesNotThrow { TrufflehogAdapter.dtoFromJson(it) } + + val kpis = assertDoesNotThrow { TrufflehogAdapter.transformDataToKpi(dto) } + + assertEquals(1, kpis.size) + + kpis.forEach { assert(it is AdapterResult.Success) } + + assertEquals(100, (kpis.first() as AdapterResult.Success.Kpi).rawValueKpi.score) + } + } + + @Test + fun testResultResultDto() { + Files.newInputStream(Path("src/test/resources/trufflehog.json")).use { + val dto = assertDoesNotThrow { TrufflehogAdapter.dtoFromJson(it) } + + val kpis = assertDoesNotThrow { TrufflehogAdapter.transformDataToKpi(dto) } + + assertEquals(1, kpis.size) + + kpis.forEach { assert(it is AdapterResult.Success) } + + assertEquals(0, (kpis.first() as AdapterResult.Success.Kpi).rawValueKpi.score) + } + } +} diff --git a/adapter/src/test/resources/trufflehog-no-result.json b/adapter/src/test/resources/trufflehog-no-result.json new file mode 100644 index 0000000..ee56478 --- /dev/null +++ b/adapter/src/test/resources/trufflehog-no-result.json @@ -0,0 +1,95 @@ +{ + "results": [ + { + "level": "info-0", + "ts": "2024-11-19T13:14:04Z", + "logger": "trufflehog", + "msg": "running source", + "source_manager_worker_id": "JrTPL", + "with_units": true + }, + { + "level": "info-0", + "ts": "2024-11-19T13:14:04Z", + "logger": "trufflehog", + "msg": "scanning repo", + "source_manager_worker_id": "JrTPL", + "unit_kind": "dir", + "unit": "/tmp/", + "repo": "https://github.com/fraunhofer-iem/spha-demo", + "head": "2f6d0a9ccb3211eff95126a7e36c68063c2432ce" + }, + { + "SourceMetadata": { + "Data": { + "Git": { + "commit": "4a88901aaf9889a0672013bd4edfe8a99e290ab6", + "file": "src/main/java/com/example/demo/VulnerableJavaAppApplication.java", + "email": "Jan-Niclas Struewer \u003cj.n.struewer@gmail.com\u003e", + "repository": "https://github.com/fraunhofer-iem/spha-demo", + "timestamp": "2024-11-19 11:54:10 +0000", + "line": 9 + } + } + }, + "SourceID": 1, + "SourceType": 16, + "SourceName": "trufflehog - git", + "DetectorType": 895, + "DetectorName": "MongoDB", + "DetectorDescription": "MongoDB is a NoSQL database that uses a document-oriented data model. MongoDB credentials can be used to access and manipulate the database.", + "DecoderName": "PLAIN", + "Verified": false, + "VerificationError": "context deadline exceeded", + "Raw": "***agenda-live.mongo.cosmos.azure.com:10255/?appName=%40agenda-live\u0026maxIdleTimeMS=120000\u0026replicaSet=globaldb\u0026retryWrites=false\u0026ssl=true", + "RawV2": "", + "Redacted": "", + "ExtraData": { + "rotation_guide": "https://howtorotate.com/docs/tutorials/mongo/" + }, + "StructuredData": null + }, + { + "level": "info-0", + "ts": "2024-11-19T13:14:06Z", + "logger": "trufflehog", + "msg": "finished scanning", + "chunks": 63, + "bytes": 31905, + "verified_secrets": 0, + "unverified_secrets": 2, + "scan_duration": "2.020861236s", + "trufflehog_version": "3.83.7" + }, + { + "SourceMetadata": { + "Data": { + "Git": { + "commit": "4a88901aaf9889a0672013bd4edfe8a99e290ab6", + "file": "src/main/resources/application.properties", + "email": "Jan-Niclas Struewer \u003cj.n.struewer@gmail.com\u003e", + "repository": "https://github.com/fraunhofer-iem/spha-demo", + "timestamp": "2024-11-19 11:54:10 +0000", + "line": 1 + } + } + }, + "SourceID": 1, + "SourceType": 16, + "SourceName": "trufflehog - git", + "DetectorType": 895, + "DetectorName": "MongoDB", + "DetectorDescription": "MongoDB is a NoSQL database that uses a document-oriented data model. MongoDB credentials can be used to access and manipulate the database.", + "DecoderName": "PLAIN", + "Verified": false, + "VerificationError": "context deadline exceeded", + "Raw": "***agenda-live.mongo.cosmos.azure.com:10255/?appName=%40agenda-live\u0026maxIdleTimeMS=120000\u0026replicaSet=globaldb\u0026retryWrites=false\u0026ssl=true", + "RawV2": "", + "Redacted": "", + "ExtraData": { + "rotation_guide": "https://howtorotate.com/docs/tutorials/mongo/" + }, + "StructuredData": null + } + ] +} \ No newline at end of file diff --git a/adapter/src/test/resources/trufflehog.json b/adapter/src/test/resources/trufflehog.json new file mode 100644 index 0000000..dfe4163 --- /dev/null +++ b/adapter/src/test/resources/trufflehog.json @@ -0,0 +1,95 @@ +{ + "results": [ + { + "level": "info-0", + "ts": "2024-11-19T13:14:04Z", + "logger": "trufflehog", + "msg": "running source", + "source_manager_worker_id": "JrTPL", + "with_units": true + }, + { + "level": "info-0", + "ts": "2024-11-19T13:14:04Z", + "logger": "trufflehog", + "msg": "scanning repo", + "source_manager_worker_id": "JrTPL", + "unit_kind": "dir", + "unit": "/tmp/", + "repo": "https://github.com/fraunhofer-iem/spha-demo", + "head": "2f6d0a9ccb3211eff95126a7e36c68063c2432ce" + }, + { + "SourceMetadata": { + "Data": { + "Git": { + "commit": "4a88901aaf9889a0672013bd4edfe8a99e290ab6", + "file": "src/main/java/com/example/demo/VulnerableJavaAppApplication.java", + "email": "Jan-Niclas Struewer \u003cj.n.struewer@gmail.com\u003e", + "repository": "https://github.com/fraunhofer-iem/spha-demo", + "timestamp": "2024-11-19 11:54:10 +0000", + "line": 9 + } + } + }, + "SourceID": 1, + "SourceType": 16, + "SourceName": "trufflehog - git", + "DetectorType": 895, + "DetectorName": "MongoDB", + "DetectorDescription": "MongoDB is a NoSQL database that uses a document-oriented data model. MongoDB credentials can be used to access and manipulate the database.", + "DecoderName": "PLAIN", + "Verified": false, + "VerificationError": "context deadline exceeded", + "Raw": "***agenda-live.mongo.cosmos.azure.com:10255/?appName=%40agenda-live\u0026maxIdleTimeMS=120000\u0026replicaSet=globaldb\u0026retryWrites=false\u0026ssl=true", + "RawV2": "", + "Redacted": "", + "ExtraData": { + "rotation_guide": "https://howtorotate.com/docs/tutorials/mongo/" + }, + "StructuredData": null + }, + { + "level": "info-0", + "ts": "2024-11-19T13:14:06Z", + "logger": "trufflehog", + "msg": "finished scanning", + "chunks": 63, + "bytes": 31905, + "verified_secrets": 1, + "unverified_secrets": 2, + "scan_duration": "2.020861236s", + "trufflehog_version": "3.83.7" + }, + { + "SourceMetadata": { + "Data": { + "Git": { + "commit": "4a88901aaf9889a0672013bd4edfe8a99e290ab6", + "file": "src/main/resources/application.properties", + "email": "Jan-Niclas Struewer \u003cj.n.struewer@gmail.com\u003e", + "repository": "https://github.com/fraunhofer-iem/spha-demo", + "timestamp": "2024-11-19 11:54:10 +0000", + "line": 1 + } + } + }, + "SourceID": 1, + "SourceType": 16, + "SourceName": "trufflehog - git", + "DetectorType": 895, + "DetectorName": "MongoDB", + "DetectorDescription": "MongoDB is a NoSQL database that uses a document-oriented data model. MongoDB credentials can be used to access and manipulate the database.", + "DecoderName": "PLAIN", + "Verified": false, + "VerificationError": "context deadline exceeded", + "Raw": "***agenda-live.mongo.cosmos.azure.com:10255/?appName=%40agenda-live\u0026maxIdleTimeMS=120000\u0026replicaSet=globaldb\u0026retryWrites=false\u0026ssl=true", + "RawV2": "", + "Redacted": "", + "ExtraData": { + "rotation_guide": "https://howtorotate.com/docs/tutorials/mongo/" + }, + "StructuredData": null + } + ] +} \ No newline at end of file diff --git a/model/src/main/kotlin/de/fraunhofer/iem/spha/model/adapter/trufflehog/TrufflehogDto.kt b/model/src/main/kotlin/de/fraunhofer/iem/spha/model/adapter/trufflehog/TrufflehogDto.kt new file mode 100644 index 0000000..62d3469 --- /dev/null +++ b/model/src/main/kotlin/de/fraunhofer/iem/spha/model/adapter/trufflehog/TrufflehogDto.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2024 Fraunhofer IEM. All rights reserved. + * + * Licensed under the MIT license. See LICENSE file in the project root for details. + * + * SPDX-License-Identifier: MIT + * License-Filename: LICENSE + */ + +package de.fraunhofer.iem.spha.model.adapter.trufflehog + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonObject + +@Serializable data class TrufflehogDto(val results: List) + +@Serializable +data class TrufflehogReportDto( + val level: String?, + val ts: String?, + val logger: String?, + val msg: String?, + val chunks: Int?, + val bytes: Int?, + @SerialName("verified_secrets") val verifiedSecrets: Int, + @SerialName("unverified_secrets") val unverifiedSecrets: Int, + @SerialName("scan_duration") val scanDuration: String?, + @SerialName("trufflehog_version") val trufflehogVersion: String?, +)