diff --git a/hooks/persistence-defectdojo/src/main/java/io/securecodebox/persistence/DefectDojoPersistenceProvider.java b/hooks/persistence-defectdojo/src/main/java/io/securecodebox/persistence/DefectDojoPersistenceProvider.java index 6e72c79814..cf8dcfffe6 100644 --- a/hooks/persistence-defectdojo/src/main/java/io/securecodebox/persistence/DefectDojoPersistenceProvider.java +++ b/hooks/persistence-defectdojo/src/main/java/io/securecodebox/persistence/DefectDojoPersistenceProvider.java @@ -5,18 +5,16 @@ import io.securecodebox.persistence.config.PersistenceProviderConfig; import io.securecodebox.persistence.defectdojo.config.DefectDojoConfig; -import io.securecodebox.persistence.defectdojo.models.ScanFile; import io.securecodebox.persistence.defectdojo.service.EndpointService; import io.securecodebox.persistence.mapping.DefectDojoFindingToSecureCodeBoxMapper; import io.securecodebox.persistence.models.Scan; +import io.securecodebox.persistence.service.ScanService; import io.securecodebox.persistence.service.KubernetesService; import io.securecodebox.persistence.service.S3Service; import io.securecodebox.persistence.strategies.VersionedEngagementsStrategy; -import io.securecodebox.persistence.util.ScanNameMapping; -import org.apache.commons.io.FilenameUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.net.URL; + import java.util.stream.Collectors; public class DefectDojoPersistenceProvider { @@ -34,29 +32,14 @@ public static void main(String[] args) throws Exception { var scan = new Scan(kubernetesService.getScanFromKubernetes()); scan.validate(); - var config = DefectDojoConfig.fromEnv(); LOG.info("Downloading Scan Result"); - String downloadUrl; - String scanType = scan.getSpec().getScanType(); - ScanNameMapping scanNameMapping = ScanNameMapping.bySecureCodeBoxScanType(scanType); - if (scanNameMapping == ScanNameMapping.GENERIC) { - LOG.debug("No explicit Parser specified for ScanType {}, using Findings JSON Scan Result", scanType); - downloadUrl = persistenceProviderConfig.getFindingDownloadUrl(); - } else { - LOG.debug("Explicit Parser is specified for ScanType {}, using Raw Scan Result", scanNameMapping.scanType); - downloadUrl = persistenceProviderConfig.getRawResultDownloadUrl(); - } - var scanResults = s3Service.downloadFile(downloadUrl); - LOG.info("Finished Downloading Scan Result"); - LOG.debug("Scan Result: {}", scanResults); + var scanResultFile = ScanService.downloadScan(scan, persistenceProviderConfig, s3Service); + var config = DefectDojoConfig.fromEnv(); LOG.info("Uploading Findings to DefectDojo at: {}", config.getUrl()); - var defectdojoImportStrategy = new VersionedEngagementsStrategy(); defectdojoImportStrategy.init(config); - var scanResultFile = new ScanFile(scanResults, FilenameUtils.getName(new URL(downloadUrl).getPath())); var defectDojoFindings = defectdojoImportStrategy.run(scan, scanResultFile); - LOG.info("Identified total Number of findings in DefectDojo: {}", defectDojoFindings.size()); if (persistenceProviderConfig.isReadAndWrite()) { @@ -66,7 +49,7 @@ public static void main(String[] args) throws Exception { LOG.info("Overwriting secureCodeBox findings with the findings from DefectDojo."); var findings = defectDojoFindings.stream() - .map(mapper::fromDefectDojoFining) + .map(mapper::fromDefectDojoFinding) .collect(Collectors.toList()); LOG.debug("Mapped Findings: {}", findings); diff --git a/hooks/persistence-defectdojo/src/main/java/io/securecodebox/persistence/mapping/DefectDojoFindingToSecureCodeBoxMapper.java b/hooks/persistence-defectdojo/src/main/java/io/securecodebox/persistence/mapping/DefectDojoFindingToSecureCodeBoxMapper.java index 7c9e3379ab..6c1094f87d 100644 --- a/hooks/persistence-defectdojo/src/main/java/io/securecodebox/persistence/mapping/DefectDojoFindingToSecureCodeBoxMapper.java +++ b/hooks/persistence-defectdojo/src/main/java/io/securecodebox/persistence/mapping/DefectDojoFindingToSecureCodeBoxMapper.java @@ -7,7 +7,7 @@ import io.securecodebox.persistence.defectdojo.config.DefectDojoConfig; import io.securecodebox.persistence.defectdojo.models.Endpoint; import io.securecodebox.persistence.defectdojo.service.EndpointService; -import io.securecodebox.persistence.models.Finding; +import io.securecodebox.persistence.models.SecureCodeBoxFinding; import org.springframework.web.util.UriComponentsBuilder; import java.util.HashMap; @@ -23,8 +23,8 @@ public DefectDojoFindingToSecureCodeBoxMapper(DefectDojoConfig config, EndpointS this.endpointService = endpointService; } - public Finding fromDefectDojoFining(io.securecodebox.persistence.defectdojo.models.Finding defectDojoFinding) { - var finding = new Finding(); + public SecureCodeBoxFinding fromDefectDojoFinding(io.securecodebox.persistence.defectdojo.models.Finding defectDojoFinding) { + var finding = new SecureCodeBoxFinding(); finding.setId(UUID.randomUUID().toString()); finding.setName(defectDojoFinding.getTitle()); @@ -45,16 +45,16 @@ public Finding fromDefectDojoFining(io.securecodebox.persistence.defectdojo.mode switch (defectDojoFinding.getSeverity()) { case Critical: case High: - finding.setSeverity(Finding.Severities.High); + finding.setSeverity(SecureCodeBoxFinding.Severities.High); break; case Medium: - finding.setSeverity(Finding.Severities.Medium); + finding.setSeverity(SecureCodeBoxFinding.Severities.Medium); break; case Low: - finding.setSeverity(Finding.Severities.Low); + finding.setSeverity(SecureCodeBoxFinding.Severities.Low); break; case Informational: - finding.setSeverity(Finding.Severities.Informational); + finding.setSeverity(SecureCodeBoxFinding.Severities.Informational); break; } diff --git a/hooks/persistence-defectdojo/src/main/java/io/securecodebox/persistence/mapping/SecureCodeBoxFindingsToDefectDojoMapper.java b/hooks/persistence-defectdojo/src/main/java/io/securecodebox/persistence/mapping/SecureCodeBoxFindingsToDefectDojoMapper.java new file mode 100644 index 0000000000..856507be54 --- /dev/null +++ b/hooks/persistence-defectdojo/src/main/java/io/securecodebox/persistence/mapping/SecureCodeBoxFindingsToDefectDojoMapper.java @@ -0,0 +1,97 @@ +package io.securecodebox.persistence.mapping; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import io.securecodebox.persistence.models.DefectDojoImportFinding; +import io.securecodebox.persistence.models.SecureCodeBoxFinding; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.URI; +import java.time.*; +import java.time.format.DateTimeFormatter; +import java.util.*; + +public class SecureCodeBoxFindingsToDefectDojoMapper { + private static final Logger LOG = LoggerFactory.getLogger(SecureCodeBoxFindingsToDefectDojoMapper.class); + private static final DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + private static final ObjectWriter prettyJSONPrinter = new ObjectMapper().writerWithDefaultPrettyPrinter(); + + /** + * Converts a SecureCodeBox Findings JSON String to a DefectDojo Findings JSON String. + * @param scbFindingsJson SecureCodeBox Findings JSON File as String + * @return DefectDojo Findings JSON File as String, compatible with the DefectDojo Generic JSON Parser + * @throws IOException + */ + public static String fromSecureCodeboxFindingsJson(String scbFindingsJson) throws IOException { + LOG.debug("Converting SecureCodeBox Findings to DefectDojo Findings"); + ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + List DefectDojoImportFindings = new ArrayList<>(); + List secureCodeBoxFindings = mapper.readValue(scbFindingsJson, new TypeReference<>() {}); + for (SecureCodeBoxFinding secureCodeBoxFinding : secureCodeBoxFindings){ + DefectDojoImportFindings.add(fromSecureCodeBoxFinding(secureCodeBoxFinding)); + } + // create the result where the format has to be {"findings": [finding1, findings2, ...]} + ObjectNode ddFindingJson = mapper.createObjectNode(); + ArrayNode arrayNode = mapper.valueToTree(DefectDojoImportFindings); + ddFindingJson.putArray("findings").addAll(arrayNode); + return ddFindingJson.toString(); + } + + /** + * Converts a SecureCodeBox Finding to a DefectDojo Finding, + * that can be imported by the DefectDojo Generic JSON Parser. + * @param secureCodeBoxFinding Finding in SecureCodeBox format. + * @return Finding in DefectDojo Format, compatible with the DefectDojo Generic JSON Parser + * @throws JsonProcessingException + */ + protected static DefectDojoImportFinding fromSecureCodeBoxFinding(SecureCodeBoxFinding secureCodeBoxFinding) throws JsonProcessingException { + //set basic info + DefectDojoImportFinding result = new DefectDojoImportFinding(); + result.setTitle(secureCodeBoxFinding.getName()); + result.setSeverity(capitalize(secureCodeBoxFinding.getSeverity().toString())); + result.setUniqueIdFromTool(secureCodeBoxFinding.getId()); + + // set Description as combination of finding description and finding attributes + String description = secureCodeBoxFinding.getDescription(); + if (secureCodeBoxFinding.getAttributes()!=null) { + String attributesJson = prettyJSONPrinter.writeValueAsString(secureCodeBoxFinding.getAttributes()); + description = description + "\n " + attributesJson; + } + result.setDescription(description); + + //set finding date + Instant instant; + if (secureCodeBoxFinding.getTimestamp() != null) { + instant = Instant.from(DateTimeFormatter.ISO_INSTANT.parse(secureCodeBoxFinding.getTimestamp())); + } + else { + instant = Instant.now(); + } + LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); + result.setDate(dtf.format(localDateTime)); + + //set finding location + try { + URI.create(secureCodeBoxFinding.getLocation()); + result.setEndpoints(Collections.singletonList(secureCodeBoxFinding.getLocation())); + } catch (IllegalArgumentException e) { + LOG.warn("Couldn't parse the secureCodeBox location, because it: {} s not a vailid uri: {}", e, secureCodeBoxFinding.getLocation()); + } + return result; + } + + private static String capitalize(String str) { + if(str == null || str.isEmpty()) { + return str; + } + + return str.substring(0, 1).toUpperCase() + str.substring(1); + } +} diff --git a/hooks/persistence-defectdojo/src/main/java/io/securecodebox/persistence/models/DefectDojoImportFinding.java b/hooks/persistence-defectdojo/src/main/java/io/securecodebox/persistence/models/DefectDojoImportFinding.java new file mode 100644 index 0000000000..073e826274 --- /dev/null +++ b/hooks/persistence-defectdojo/src/main/java/io/securecodebox/persistence/models/DefectDojoImportFinding.java @@ -0,0 +1,67 @@ +package io.securecodebox.persistence.models; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * DefectDojo JSON Import Format + * It is used to generate JSON that can be read by the DefectDojo Generic JSON Parser + */ +@Data +@NoArgsConstructor +@Builder +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class DefectDojoImportFinding { + + @JsonProperty + String title; + + @JsonProperty + String description; + + @JsonProperty + Boolean active; + + @JsonProperty() + Boolean verified; + + @JsonProperty + String severity; + + @JsonProperty + String impact; + + @JsonProperty + String date; + + @JsonProperty + String cve; + + @JsonProperty + Integer cwe; + + @JsonProperty + String cvssv3; + + @JsonProperty + List tags; + + @JsonProperty("unique_id_from_tool") + String uniqueIdFromTool; + + @JsonProperty("vuln_id_from_tool") + String vulnIdFromTool; + + @JsonProperty("endpoints") + List endpoints; + +} diff --git a/hooks/persistence-defectdojo/src/main/java/io/securecodebox/persistence/models/Finding.java b/hooks/persistence-defectdojo/src/main/java/io/securecodebox/persistence/models/SecureCodeBoxFinding.java similarity index 73% rename from hooks/persistence-defectdojo/src/main/java/io/securecodebox/persistence/models/Finding.java rename to hooks/persistence-defectdojo/src/main/java/io/securecodebox/persistence/models/SecureCodeBoxFinding.java index cd9b79a8f0..669f6dfd1a 100644 --- a/hooks/persistence-defectdojo/src/main/java/io/securecodebox/persistence/models/Finding.java +++ b/hooks/persistence-defectdojo/src/main/java/io/securecodebox/persistence/models/SecureCodeBoxFinding.java @@ -4,6 +4,7 @@ package io.securecodebox.persistence.models; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; import lombok.Builder; @@ -19,7 +20,8 @@ @Builder @NoArgsConstructor @AllArgsConstructor -public class Finding { +@JsonIgnoreProperties(ignoreUnknown = true) +public class SecureCodeBoxFinding { @JsonProperty String id; @JsonProperty @@ -35,16 +37,18 @@ public class Finding { @JsonProperty Severities severity; @JsonProperty + String timestamp; + @JsonProperty Map attributes; public enum Severities { - @JsonProperty("High") + @JsonProperty("HIGH") High, - @JsonProperty("Medium") + @JsonProperty("MEDIUM") Medium, - @JsonProperty("Low") + @JsonProperty("LOW") Low, - @JsonProperty("Informational") + @JsonProperty("INFORMATIONAL") Informational ; } diff --git a/hooks/persistence-defectdojo/src/main/java/io/securecodebox/persistence/service/KubernetesService.java b/hooks/persistence-defectdojo/src/main/java/io/securecodebox/persistence/service/KubernetesService.java index 6dfa442a40..2fc9d0a59a 100644 --- a/hooks/persistence-defectdojo/src/main/java/io/securecodebox/persistence/service/KubernetesService.java +++ b/hooks/persistence-defectdojo/src/main/java/io/securecodebox/persistence/service/KubernetesService.java @@ -13,7 +13,7 @@ import io.securecodebox.models.V1ScanStatusFindings; import io.securecodebox.models.V1ScanStatusFindingsSeverities; import io.securecodebox.persistence.exceptions.DefectDojoPersistenceException; -import io.securecodebox.persistence.models.Finding; +import io.securecodebox.persistence.models.SecureCodeBoxFinding; import okhttp3.Protocol; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -78,35 +78,35 @@ public V1Scan getScanFromKubernetes() throws IOException { return response.getObject(); } - public void updateScanInKubernetes(List findings) throws IOException { + public void updateScanInKubernetes(List secureCodeBoxFindings) throws IOException { LOG.debug("Refetching the scan to minimize possibility to write conflicts"); var scan = this.getScanFromKubernetes(); Objects.requireNonNull(scan.getStatus(), "Scan status field is not set, this should have been previously set by the Operator and Parser.") - .setFindings(recalculateFindingStats(findings)); + .setFindings(recalculateFindingStats(secureCodeBoxFindings)); LOG.info("Updating Scan metadata"); scanApi.updateStatus(scan, V1Scan::getStatus); LOG.debug("Updated Scan metadata"); } - static V1ScanStatusFindings recalculateFindingStats(List findings) { + static V1ScanStatusFindings recalculateFindingStats(List secureCodeBoxFindings) { var stats = new V1ScanStatusFindings(); - stats.setCount((long) findings.size()); - stats.setCategories(recalculateFindingCategoryStats(findings)); - stats.setSeverities(recalculateFindingSeverityStats(findings)); + stats.setCount((long) secureCodeBoxFindings.size()); + stats.setCategories(recalculateFindingCategoryStats(secureCodeBoxFindings)); + stats.setSeverities(recalculateFindingSeverityStats(secureCodeBoxFindings)); return stats; } - private static V1ScanStatusFindingsSeverities recalculateFindingSeverityStats(List findings) { + private static V1ScanStatusFindingsSeverities recalculateFindingSeverityStats(List secureCodeBoxFindings) { var severities = new V1ScanStatusFindingsSeverities(); severities.setInformational(0L); severities.setLow(0L); severities.setMedium(0L); severities.setHigh(0L); - for (var finding: findings) { + for (var finding: secureCodeBoxFindings) { switch (finding.getSeverity()) { case High: severities.setHigh(severities.getHigh() + 1L); @@ -125,9 +125,9 @@ private static V1ScanStatusFindingsSeverities recalculateFindingSeverityStats(Li return severities; } - private static HashMap recalculateFindingCategoryStats(List findings) { + private static HashMap recalculateFindingCategoryStats(List secureCodeBoxFindings) { var categories = new HashMap(); - for (var finding: findings) { + for (var finding: secureCodeBoxFindings) { if (categories.containsKey(finding.getCategory())) { categories.put(finding.getCategory(), categories.get(finding.getCategory()) + 1); } else { diff --git a/hooks/persistence-defectdojo/src/main/java/io/securecodebox/persistence/service/S3Service.java b/hooks/persistence-defectdojo/src/main/java/io/securecodebox/persistence/service/S3Service.java index 4d75f50e3e..30d5ad519a 100644 --- a/hooks/persistence-defectdojo/src/main/java/io/securecodebox/persistence/service/S3Service.java +++ b/hooks/persistence-defectdojo/src/main/java/io/securecodebox/persistence/service/S3Service.java @@ -5,7 +5,7 @@ package io.securecodebox.persistence.service; import com.fasterxml.jackson.databind.ObjectMapper; -import io.securecodebox.persistence.models.Finding; +import io.securecodebox.persistence.models.SecureCodeBoxFinding; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -18,9 +18,9 @@ public class S3Service { private static final Logger LOG = LoggerFactory.getLogger(S3Service.class); - public void overwriteFindings(String url, List findings) throws IOException, InterruptedException { + public void overwriteFindings(String url, List secureCodeBoxFindings) throws IOException, InterruptedException { ObjectMapper mapper = new ObjectMapper(); - var findingJson = mapper.writeValueAsString(findings); + var findingJson = mapper.writeValueAsString(secureCodeBoxFindings); LOG.info("Uploading Findings to S3"); diff --git a/hooks/persistence-defectdojo/src/main/java/io/securecodebox/persistence/service/ScanService.java b/hooks/persistence-defectdojo/src/main/java/io/securecodebox/persistence/service/ScanService.java new file mode 100644 index 0000000000..3d5864d5dd --- /dev/null +++ b/hooks/persistence-defectdojo/src/main/java/io/securecodebox/persistence/service/ScanService.java @@ -0,0 +1,38 @@ +package io.securecodebox.persistence.service; + +import io.securecodebox.persistence.config.PersistenceProviderConfig; +import io.securecodebox.persistence.defectdojo.models.ScanFile; +import io.securecodebox.persistence.mapping.SecureCodeBoxFindingsToDefectDojoMapper; +import io.securecodebox.persistence.models.Scan; +import io.securecodebox.persistence.util.ScanNameMapping; +import org.apache.commons.io.FilenameUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.URL; + +public class ScanService { + private static final Logger LOG = LoggerFactory.getLogger(ScanService.class); + + public static ScanFile downloadScan(Scan scan, PersistenceProviderConfig ppConfig, S3Service s3Service) throws IOException, InterruptedException { + String downloadUrl; + String scanResults; + String scanType = scan.getSpec().getScanType(); + ScanNameMapping scanNameMapping = ScanNameMapping.bySecureCodeBoxScanType(scanType); + if (scanNameMapping == ScanNameMapping.GENERIC) { + LOG.debug("No explicit Parser specified for ScanType {}, using Findings JSON Scan Result", scanType); + downloadUrl = ppConfig.getFindingDownloadUrl(); + var findingsJSON = s3Service.downloadFile(downloadUrl); + scanResults = SecureCodeBoxFindingsToDefectDojoMapper.fromSecureCodeboxFindingsJson(findingsJSON); + } else { + LOG.debug("Explicit Parser is specified for ScanType {}, using Raw Scan Result", scanNameMapping.scanType); + downloadUrl = ppConfig.getRawResultDownloadUrl(); + scanResults = s3Service.downloadFile(downloadUrl); + } + LOG.info("Finished Downloading Scan Result"); + LOG.debug("Scan Result: {}", scanResults); + + return new ScanFile(scanResults, FilenameUtils.getName(new URL(downloadUrl).getPath())); + } +} diff --git a/hooks/persistence-defectdojo/src/main/java/io/securecodebox/persistence/strategies/VersionedEngagementsStrategy.java b/hooks/persistence-defectdojo/src/main/java/io/securecodebox/persistence/strategies/VersionedEngagementsStrategy.java index 17e6f18079..3726b93083 100644 --- a/hooks/persistence-defectdojo/src/main/java/io/securecodebox/persistence/strategies/VersionedEngagementsStrategy.java +++ b/hooks/persistence-defectdojo/src/main/java/io/securecodebox/persistence/strategies/VersionedEngagementsStrategy.java @@ -82,7 +82,7 @@ public List run(Scan scan, ScanFile scanResultFile) throws Exception { var testId = this.createTest(scan, engagementId, userId); - LOG.debug("Uploading Scan Report (RawResults) to DefectDojo"); + LOG.debug("Uploading Scan Report to DefectDojo"); ScanType scanType = ScanNameMapping.bySecureCodeBoxScanType(scan.getSpec().getScanType()).scanType; TestType testType = testTypeService.searchUnique(TestType.builder().name(scanType.getTestType()).build()).orElseThrow(() -> new DefectDojoPersistenceException("Could not find test type '" + scanType.getTestType() + "' in DefectDojo API. DefectDojo might be running in an unsupported version.")); @@ -96,7 +96,7 @@ public List run(Scan scan, ScanFile scanResultFile) throws Exception { testType.getId() ); - LOG.info("Uploaded Scan Report (RawResults) as testID {} to DefectDojo", testId); + LOG.info("Uploaded Scan Report as testID {} to DefectDojo", testId); return findingService.search(Map.of("test", String.valueOf(testId))); } diff --git a/hooks/persistence-defectdojo/src/main/java/io/securecodebox/persistence/util/ScanNameMapping.java b/hooks/persistence-defectdojo/src/main/java/io/securecodebox/persistence/util/ScanNameMapping.java index 319867473c..530efc0c50 100644 --- a/hooks/persistence-defectdojo/src/main/java/io/securecodebox/persistence/util/ScanNameMapping.java +++ b/hooks/persistence-defectdojo/src/main/java/io/securecodebox/persistence/util/ScanNameMapping.java @@ -16,7 +16,7 @@ public enum ScanNameMapping { TRIVY("trivy", ScanType.TRIVY_SCAN), GITLEAKS("gitleaks", ScanType.GITLEAKS_SCAN), NIKTO("nikto", ScanType.NIKTO_SCAN), - GENERIC(null, ScanType.SECURECODEBOX_FINDINGS_IMPORT) + GENERIC(null, ScanType.GENERIC_FINDINGS_IMPORT) ; /** @@ -42,9 +42,6 @@ public static ScanNameMapping bySecureCodeBoxScanType(@NonNull String scanType) return mapping; } } - - throw new IllegalArgumentException("No Mapping found for ScanType '" + scanType + "'"); - // use this as soon as generic parser is released (in DD or this Hook) - // return ScanNameMapping.GENERIC; + return ScanNameMapping.GENERIC; } } diff --git a/hooks/persistence-defectdojo/src/test/java/io/securecodebox/persistence/mapping/DefectDojoFindingToSecureCodeBoxMapperTest.java b/hooks/persistence-defectdojo/src/test/java/io/securecodebox/persistence/mapping/DefectDojoFindingToSecureCodeBoxMapperTest.java index 643de36eab..c248419fae 100644 --- a/hooks/persistence-defectdojo/src/test/java/io/securecodebox/persistence/mapping/DefectDojoFindingToSecureCodeBoxMapperTest.java +++ b/hooks/persistence-defectdojo/src/test/java/io/securecodebox/persistence/mapping/DefectDojoFindingToSecureCodeBoxMapperTest.java @@ -8,6 +8,7 @@ import io.securecodebox.persistence.defectdojo.models.Endpoint; import io.securecodebox.persistence.defectdojo.models.Finding; import io.securecodebox.persistence.defectdojo.service.EndpointService; +import io.securecodebox.persistence.models.SecureCodeBoxFinding; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -50,7 +51,7 @@ public void shouldMapBasicFindings(){ when(endpointService.get(1337L)).thenReturn(Endpoint.builder().protocol("http").host("juice-shop.securecodebox-test.svc:3000").build()); - var actualFinding = this.mapper.fromDefectDojoFining(ddFinding); + var actualFinding = this.mapper.fromDefectDojoFinding(ddFinding); assertEquals( actualFinding.getName(), @@ -64,7 +65,7 @@ public void shouldMapBasicFindings(){ assertEquals( actualFinding.getSeverity(), - io.securecodebox.persistence.models.Finding.Severities.Medium + SecureCodeBoxFinding.Severities.Medium ); assertEquals( diff --git a/hooks/persistence-defectdojo/src/test/java/io/securecodebox/persistence/mapping/SecureCodeBoxFindingsToDefectDojoMapperTest.java b/hooks/persistence-defectdojo/src/test/java/io/securecodebox/persistence/mapping/SecureCodeBoxFindingsToDefectDojoMapperTest.java new file mode 100644 index 0000000000..d757437d4c --- /dev/null +++ b/hooks/persistence-defectdojo/src/test/java/io/securecodebox/persistence/mapping/SecureCodeBoxFindingsToDefectDojoMapperTest.java @@ -0,0 +1,80 @@ +package io.securecodebox.persistence.mapping; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.securecodebox.persistence.models.SecureCodeBoxFinding; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.HashMap; + +import static org.junit.jupiter.api.Assertions.*; + + +@ExtendWith(MockitoExtension.class) +public class SecureCodeBoxFindingsToDefectDojoMapperTest { + + @Test + public void yieldsCorrectResult() throws IOException { + String ddFindingsPath = "kubehunter-dd-findings.json"; + String scbFindingsPath = "kubehunter-scb-findings.json"; + ClassLoader cl = getClass().getClassLoader(); + + File ddFindingsFile = new File(cl.getResource(ddFindingsPath).getFile()); + File scbFindingsFile = new File(cl.getResource(scbFindingsPath).getFile()); + String expectedResult = new String(Files.readAllBytes(ddFindingsFile.toPath())); + String scbFindingsContent = new String(Files.readAllBytes(scbFindingsFile.toPath())); + String result = SecureCodeBoxFindingsToDefectDojoMapper.fromSecureCodeboxFindingsJson(scbFindingsContent); + ObjectMapper mapper = new ObjectMapper(); + JsonNode actualJSON = mapper.readTree(result); + JsonNode expectedJSON = mapper.readTree(expectedResult); + assertNotNull(actualJSON); + // if whitespaces should be ignored in strings, a Custom Comperator could be used + // then the result and expected result would not have to match exactly. + // see https://www.baeldung.com/jackson-compare-two-json-objects + assertEquals(actualJSON,expectedJSON); + } + + @Test + public void correctlyParsesFindings() throws IOException { + var name = "Name"; + var description = "Description"; + var severity = "High"; + var id = "123"; + var location = "ldap://[2001:db8::7]/c=GB?objectClass?one"; + var attributes = new HashMap(); + var subAttribute = new HashMap<>(); + subAttribute.put("sub_attribute", "1"); + attributes.put("attribute_1", subAttribute); + attributes.put("attribute_2", "2"); + attributes.put("attribute_3", "3"); + var scbFinding = SecureCodeBoxFinding.builder().name(name).description(description) + .severity(SecureCodeBoxFinding.Severities.High).id(id).location(location).attributes(attributes) + .build(); + + var ddFinding = SecureCodeBoxFindingsToDefectDojoMapper.fromSecureCodeBoxFinding(scbFinding); + assertEquals(ddFinding.getTitle(), name); + assertEquals(ddFinding.getSeverity(), severity); + assertEquals(ddFinding.getUniqueIdFromTool(), id); + assertEquals(ddFinding.getEndpoints().get(0), location); + assertTrue(ddFinding.getDescription().startsWith(description)); + //Description should consist of description and attributes as JSON + String attributesJson = ddFinding.getDescription().substring(description.length()+1); + String expectedAttributeJson = "{\n" + + " \"attribute_1\" : {\n" + + " \"sub_attribute\" : \"1\"\n" + + " },\n" + + " \"attribute_2\" : \"2\",\n" + + " \"attribute_3\" : \"3\"\n" + + "}"; + ObjectMapper mapper = new ObjectMapper(); + var expectedJson = mapper.readTree(attributesJson); + var actualJson = mapper.readTree(expectedAttributeJson); + assertNotNull(actualJson); + assertEquals(mapper.readTree(attributesJson),mapper.readTree(expectedAttributeJson)); + } +} diff --git a/hooks/persistence-defectdojo/src/test/java/io/securecodebox/persistence/service/KubernetesServiceTest.java b/hooks/persistence-defectdojo/src/test/java/io/securecodebox/persistence/service/KubernetesServiceTest.java index 68a8a9b396..01a0647fc8 100644 --- a/hooks/persistence-defectdojo/src/test/java/io/securecodebox/persistence/service/KubernetesServiceTest.java +++ b/hooks/persistence-defectdojo/src/test/java/io/securecodebox/persistence/service/KubernetesServiceTest.java @@ -4,7 +4,7 @@ package io.securecodebox.persistence.service; -import io.securecodebox.persistence.models.Finding; +import io.securecodebox.persistence.models.SecureCodeBoxFinding; import org.junit.jupiter.api.Test; import java.util.List; @@ -17,10 +17,10 @@ class KubernetesServiceTest { @Test public void calculatesFindingStatsCorrectly() throws Exception{ var findings = List.of( - Finding.builder().category("Open Port").severity(Finding.Severities.Informational).build(), - Finding.builder().category("Open Port").severity(Finding.Severities.Informational).build(), - Finding.builder().category("Open Port").severity(Finding.Severities.Informational).build(), - Finding.builder().category("Host").severity(Finding.Severities.Informational).build() + SecureCodeBoxFinding.builder().category("Open Port").severity(SecureCodeBoxFinding.Severities.Informational).build(), + SecureCodeBoxFinding.builder().category("Open Port").severity(SecureCodeBoxFinding.Severities.Informational).build(), + SecureCodeBoxFinding.builder().category("Open Port").severity(SecureCodeBoxFinding.Severities.Informational).build(), + SecureCodeBoxFinding.builder().category("Host").severity(SecureCodeBoxFinding.Severities.Informational).build() ); var actualStats = KubernetesService.recalculateFindingStats(findings); @@ -46,9 +46,9 @@ public void calculatesFindingStatsCorrectly() throws Exception{ @Test public void calculatesFindingStatsForEmptyFindingsCorrectly() throws Exception{ - List findings = List.of(); + List secureCodeBoxFindings = List.of(); - var actualStats = KubernetesService.recalculateFindingStats(findings); + var actualStats = KubernetesService.recalculateFindingStats(secureCodeBoxFindings); assertEquals(0L, actualStats.getCount()); diff --git a/hooks/persistence-defectdojo/src/test/resources/kubehunter-dd-findings.json b/hooks/persistence-defectdojo/src/test/resources/kubehunter-dd-findings.json new file mode 100644 index 0000000000..9ee138e593 --- /dev/null +++ b/hooks/persistence-defectdojo/src/test/resources/kubehunter-dd-findings.json @@ -0,0 +1,34 @@ +{ + "findings": [ + { + "title": "Read access to pod's service account token", + "description": " Accessing the pod service account token gives an attacker the option to use the server API \n {\n \"evidence\" : \"eyJhbGciOiJSUzI1NiIsImtpZCI6IkxuOE9ZaGt1SFFabmwzN3ZEYlg1R2ZqX25VVWtnWUlnU0VOdExBbWE4VFEifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6Imx1cmNoZXItdG9rZW4tdjI4cWIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoibHVyY2hlciIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjIzMTg2YTI5LTY0NDQtNGI3Ny1hZjA1LTY5YzcyYWViZWYwZCIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0Omx1cmNoZXIifQ.IeiByhB-g3W6Zzr4_cEU2sdfyAHVwgnKn-apu8YVkS9886zSeg_BB4F-KsljFApt8gZA4gzLEMuDTEubURkD-omaWw6_eHkV_SfIiD69rvLFC-wTtLLxQijrfBa1ZJ6PxI1kVnykaWMnmBUbkqaK-xsnhw8_pk536qWjwEFUyf6PUUg1wzk_IpEQAY8paDzB1Od2eoE18NbXUFZYllsqKZly59jvsjwPRRHk8yYr3eQW6AyxBezOhpY9qI3kooKY2_mzMXAiyrcmb-apucQabMuDfVaFF7zR0OQ-1wR_hwH-ZfLZXjf9S0Im8_Hf0WLPw60iWp5d9g_kdKuysPlJGg\",\n \"kubeHunterRule\" : \"Access Secrets\"\n}", + "severity": "Low", + "date": "2020-04-15", + "unique_id_from_tool": "df9afbb5-f0c4-475b-a0e5-e3635c55917a", + "endpoints": [ + "tcp://10.1.0.1" + ] + }, + { + "title": "CAP_NET_RAW Enabled", + "description": "CAP_NET_RAW is enabled by default for pods.\n If an attacker manages to compromise a pod,\n they could potentially take advantage of this capability to perform network\n attacks on other pods running on the same node\n {\n \"evidence\" : \"\",\n \"kubeHunterRule\" : \"Pod Capabilities Hunter\"\n}", + "severity": "Low", + "date": "2020-04-16", + "unique_id_from_tool": "341ad0c8-0f3b-47ff-9b89-83d4a7e121f7", + "endpoints": [ + "tcp://10.1.0.1" + ] + }, + { + "title": "Access to pod's secrets", + "description": " Accessing the pod's secrets within a compromised pod might disclose valuable data to a potential attacker\n {\n \"evidence\" : \"['/var/run/secrets/kubernetes.io/serviceaccount/token', '/var/run/secrets/kubernetes.io/serviceaccount/namespace', '/var/run/secrets/kubernetes.io/serviceaccount/ca.crt', '/var/run/secrets/kubernetes.io/serviceaccount/..2021_05_17_21_58_54.448582170/token', '/var/run/secrets/kubernetes.io/serviceaccount/..2021_05_17_21_58_54.448582170/namespace', '/var/run/secrets/kubernetes.io/serviceaccount/..2021_05_17_21_58_54.448582170/ca.crt']\",\n \"kubeHunterRule\" : \"Access Secrets\"\n}", + "severity": "Low", + "date": "2020-04-17", + "unique_id_from_tool": "eb0c75cf-beef-4fd8-b2ed-6e46a5afa1bc", + "endpoints": [ + "tcp://10.1.0.1" + ] + } + ] +} diff --git a/hooks/persistence-defectdojo/src/test/resources/kubehunter-scb-findings.json b/hooks/persistence-defectdojo/src/test/resources/kubehunter-scb-findings.json new file mode 100644 index 0000000000..257e766052 --- /dev/null +++ b/hooks/persistence-defectdojo/src/test/resources/kubehunter-scb-findings.json @@ -0,0 +1,47 @@ +[ + { + "name": "Read access to pod's service account token", + "description": " Accessing the pod service account token gives an attacker the option to use the server API ", + "timestamp": "2020-04-15T20:08:18.000Z", + "location": "tcp://10.1.0.1", + "severity": "LOW", + "category": "Access Risk", + "reference": { + "id": "KHV050", + "source": "https://aquasecurity.github.io/kube-hunter/kb/KHV050" + }, + "attributes": { + "evidence": "eyJhbGciOiJSUzI1NiIsImtpZCI6IkxuOE9ZaGt1SFFabmwzN3ZEYlg1R2ZqX25VVWtnWUlnU0VOdExBbWE4VFEifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6Imx1cmNoZXItdG9rZW4tdjI4cWIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoibHVyY2hlciIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjIzMTg2YTI5LTY0NDQtNGI3Ny1hZjA1LTY5YzcyYWViZWYwZCIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0Omx1cmNoZXIifQ.IeiByhB-g3W6Zzr4_cEU2sdfyAHVwgnKn-apu8YVkS9886zSeg_BB4F-KsljFApt8gZA4gzLEMuDTEubURkD-omaWw6_eHkV_SfIiD69rvLFC-wTtLLxQijrfBa1ZJ6PxI1kVnykaWMnmBUbkqaK-xsnhw8_pk536qWjwEFUyf6PUUg1wzk_IpEQAY8paDzB1Od2eoE18NbXUFZYllsqKZly59jvsjwPRRHk8yYr3eQW6AyxBezOhpY9qI3kooKY2_mzMXAiyrcmb-apucQabMuDfVaFF7zR0OQ-1wR_hwH-ZfLZXjf9S0Im8_Hf0WLPw60iWp5d9g_kdKuysPlJGg", + "kubeHunterRule": "Access Secrets" + }, + "id": "df9afbb5-f0c4-475b-a0e5-e3635c55917a" + }, + { + "name": "CAP_NET_RAW Enabled", + "description": "CAP_NET_RAW is enabled by default for pods.\n If an attacker manages to compromise a pod,\n they could potentially take advantage of this capability to perform network\n attacks on other pods running on the same node", + "timestamp": "2020-04-16T20:08:18Z", + "location": "tcp://10.1.0.1", + "severity": "LOW", + "category": "Access Risk", + "reference": {}, + "attributes": { + "evidence": "", + "kubeHunterRule": "Pod Capabilities Hunter" + }, + "id": "341ad0c8-0f3b-47ff-9b89-83d4a7e121f7" + }, + { + "name": "Access to pod's secrets", + "description": " Accessing the pod's secrets within a compromised pod might disclose valuable data to a potential attacker", + "timestamp": "2020-04-17T20:08:18Z", + "location": "tcp://10.1.0.1", + "severity": "LOW", + "category": "Access Risk", + "reference": {}, + "attributes": { + "evidence": "['/var/run/secrets/kubernetes.io/serviceaccount/token', '/var/run/secrets/kubernetes.io/serviceaccount/namespace', '/var/run/secrets/kubernetes.io/serviceaccount/ca.crt', '/var/run/secrets/kubernetes.io/serviceaccount/..2021_05_17_21_58_54.448582170/token', '/var/run/secrets/kubernetes.io/serviceaccount/..2021_05_17_21_58_54.448582170/namespace', '/var/run/secrets/kubernetes.io/serviceaccount/..2021_05_17_21_58_54.448582170/ca.crt']", + "kubeHunterRule": "Access Secrets" + }, + "id": "eb0c75cf-beef-4fd8-b2ed-6e46a5afa1bc" + } +]