Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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()) {
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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());
Expand All @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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<DefectDojoImportFinding> DefectDojoImportFindings = new ArrayList<>();
List<SecureCodeBoxFinding> 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);
}
}
Original file line number Diff line number Diff line change
@@ -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<String> tags;

@JsonProperty("unique_id_from_tool")
String uniqueIdFromTool;

@JsonProperty("vuln_id_from_tool")
String vulnIdFromTool;

@JsonProperty("endpoints")
List<String> endpoints;

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -19,7 +20,8 @@
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Finding {
@JsonIgnoreProperties(ignoreUnknown = true)
public class SecureCodeBoxFinding {
@JsonProperty
String id;
@JsonProperty
Expand All @@ -35,16 +37,18 @@ public class Finding {
@JsonProperty
Severities severity;
@JsonProperty
String timestamp;
@JsonProperty
Map<String, Object> attributes;

public enum Severities {
@JsonProperty("High")
@JsonProperty("HIGH")
High,
@JsonProperty("Medium")
@JsonProperty("MEDIUM")
Medium,
@JsonProperty("Low")
@JsonProperty("LOW")
Low,
@JsonProperty("Informational")
@JsonProperty("INFORMATIONAL")
Informational
;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -78,35 +78,35 @@ public V1Scan getScanFromKubernetes() throws IOException {
return response.getObject();
}

public void updateScanInKubernetes(List<Finding> findings) throws IOException {
public void updateScanInKubernetes(List<SecureCodeBoxFinding> 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<Finding> findings) {
static V1ScanStatusFindings recalculateFindingStats(List<SecureCodeBoxFinding> 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<Finding> findings) {
private static V1ScanStatusFindingsSeverities recalculateFindingSeverityStats(List<SecureCodeBoxFinding> 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);
Expand All @@ -125,9 +125,9 @@ private static V1ScanStatusFindingsSeverities recalculateFindingSeverityStats(Li
return severities;
}

private static HashMap<String, Long> recalculateFindingCategoryStats(List<Finding> findings) {
private static HashMap<String, Long> recalculateFindingCategoryStats(List<SecureCodeBoxFinding> secureCodeBoxFindings) {
var categories = new HashMap<String, Long>();
for (var finding: findings) {
for (var finding: secureCodeBoxFindings) {
if (categories.containsKey(finding.getCategory())) {
categories.put(finding.getCategory(), categories.get(finding.getCategory()) + 1);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -18,9 +18,9 @@
public class S3Service {
private static final Logger LOG = LoggerFactory.getLogger(S3Service.class);

public void overwriteFindings(String url, List<Finding> findings) throws IOException, InterruptedException {
public void overwriteFindings(String url, List<SecureCodeBoxFinding> secureCodeBoxFindings) throws IOException, InterruptedException {
ObjectMapper mapper = new ObjectMapper();
var findingJson = mapper.writeValueAsString(findings);
var findingJson = mapper.writeValueAsString(secureCodeBoxFindings);

LOG.info("Uploading Findings to S3");

Expand Down
Loading