Skip to content
This repository was archived by the owner on Feb 26, 2021. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
*
* SecureCodeBox (SCB)
* Copyright 2015-2018 iteratec GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* /
*/
package io.securecodebox.persistence;

public class DefectDojoLoopException extends RuntimeException{
public DefectDojoLoopException(String message) {
super(message);
}

public DefectDojoLoopException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,9 @@ private EngagementResponse createEngagement(SecurityTest securityTest) {
engagementPayload.setTargetEnd(currentDate());
engagementPayload.setStatus(EngagementPayload.Status.COMPLETED);

engagementPayload.getTags().add("secureCodeBox");
engagementPayload.getTags().add("automated");

return defectDojoService.createEngagement(engagementPayload);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
*/
package io.securecodebox.persistence;


import io.securecodebox.persistence.models.*;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
Expand All @@ -35,10 +35,18 @@
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import java.nio.charset.StandardCharsets;
import java.text.MessageFormat;
import java.time.Clock;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.Iterator;

@Component
@ConditionalOnProperty(name = "securecodebox.persistence.defectdojo.enabled", havingValue = "true")
Expand All @@ -52,8 +60,15 @@ public class DefectDojoService {
@Value("${securecodebox.persistence.defectdojo.auth.name}")
protected String defectDojoDefaultUserName;

private static final Logger LOG = LoggerFactory.getLogger(DefectDojoService.class);
protected static final String DATE_FORMAT = "yyyy-MM-dd";

Clock clock = Clock.systemDefaultZone();

private String currentDate() {
return LocalDate.now(clock).format(DateTimeFormatter.ofPattern(DATE_FORMAT));
}

private static final Logger LOG = LoggerFactory.getLogger(DefectDojoService.class);

private HttpHeaders getHeaders(){
HttpHeaders headers = new HttpHeaders();
Expand Down Expand Up @@ -169,8 +184,13 @@ public EngagementResponse createEngagement(EngagementPayload engagementPayload)
throw new DefectDojoPersistenceException("Failed to create Engagement for SecurityTest", e);
}
}

public ImportScanResponse createFindings(String rawResult, long engagementId, long lead, String currentDate,String defectDojoScanName) {
return createFindings(rawResult, engagementId, lead, currentDate,defectDojoScanName, "");
}
/**
* Till version 1.5.4. testName (in defectdojo _test_type_) must be defectDojoScanName, afterwards, you can have somethings else
*/
public ImportScanResponse createFindings(String rawResult, long engagementId, long lead, String currentDate,String defectDojoScanName, String testName) {
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = getHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
Expand All @@ -181,7 +201,12 @@ public ImportScanResponse createFindings(String rawResult, long engagementId, lo
mvn.add("lead", Long.toString(lead));
mvn.add("scan_date", currentDate);
mvn.add("scan_type", defectDojoScanName);

mvn.add("close_old_findings", "true");
mvn.add("skip_duplicates", "false");

if(!testName.isEmpty())
mvn.add("test_type", testName);

try {
ByteArrayResource contentsAsResource = new ByteArrayResource(rawResult.getBytes(StandardCharsets.UTF_8)) {
@Override
Expand All @@ -201,5 +226,199 @@ public String getFilename() {
throw new DefectDojoPersistenceException("Failed to attach findings to engagement.");
}
}
public ImportScanResponse createFindingsForEngagementName(String engagementName, String rawResults, String defectDojoScanName, long productId, long lead){
return createFindingsForEngagementName(engagementName, rawResults, defectDojoScanName, productId, lead, new EngagementPayload(), "");
}

public ImportScanResponse createFindingsForEngagementName(String engagementName, String rawResults, String defectDojoScanName, long productId, long lead, EngagementPayload engagementPayload, String testName){
Long engagementId = getEngagementIdByEngagementName(engagementName, productId).orElseGet(() -> {
engagementPayload.setName(engagementName);
engagementPayload.setProduct(productId);
engagementPayload.setTargetStart(currentDate());
engagementPayload.setTargetEnd(currentDate());
engagementPayload.setLead(lead);
return createEngagement(engagementPayload).getId();
});

return createFindings(rawResults, engagementId, lead, currentDate(), defectDojoScanName, testName);
}

public ImportScanResponse createFindingsForEngagementName(String engagementName, String rawResults, String defectDojoScanName, String productName, long lead, EngagementPayload engagementPayload, String testName){
long productId = 0;
try {
productId = retrieveProductId(productName);
} catch(DefectDojoProductNotFound e) {
LOG.debug("Given product does not exists");
}
if(productId == 0) {
ProductResponse productResponse = createProduct(productName);
productId = productResponse.getId();
}

return createFindingsForEngagementName(engagementName, rawResults, defectDojoScanName, productId, lead, engagementPayload, testName);
}

private Optional<Long> getEngagementIdByEngagementName(String engagementName, String productName){
long productId = retrieveProductId(productName);
return getEngagementIdByEngagementName(engagementName, productId, 0L);
}
private Optional<Long> getEngagementIdByEngagementName(String engagementName, long productId){
return getEngagementIdByEngagementName(engagementName, productId, 0L);
}

private Optional<Long> getEngagementIdByEngagementName(String engagementName, long productId, long offset){

UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(defectDojoUrl + "/api/v2/engagements")
.queryParam("product", Long.toString(productId))
.queryParam("limit", Long.toString(50L))
.queryParam("offset", Long.toString(offset));

RestTemplate restTemplate = new RestTemplate();
HttpEntity engagementRequest = new HttpEntity(getHeaders());

ResponseEntity<DefectDojoResponse<EngagementResponse>> engagementResponse = restTemplate.exchange(builder.toUriString(), HttpMethod.GET, engagementRequest, new ParameterizedTypeReference<DefectDojoResponse<EngagementResponse>>(){});

for(EngagementResponse engagement : engagementResponse.getBody().getResults()){
if(engagement.getName().equals(engagementName)){
return Optional.of(engagement.getId());
}
}
if(engagementResponse.getBody().getNext() != null){
return getEngagementIdByEngagementName(engagementName, productId, offset + 1);
}
LOG.warn("Engagement with name '{}' not found.", engagementName);
return Optional.empty();
}
public ProductResponse createProduct(String productName) {
RestTemplate restTemplate = new RestTemplate();
ProductPayload productPayload = new ProductPayload(productName, "Description missing");
HttpEntity<ProductPayload> payload = new HttpEntity<>(productPayload, getHeaders());

try {
ResponseEntity<ProductResponse> response = restTemplate.exchange(defectDojoUrl + "/api/v2/products/", HttpMethod.POST, payload, ProductResponse.class);
return response.getBody();
} catch (HttpClientErrorException e) {
LOG.warn("Failed to create product {}", e);
LOG.warn("Failure response body. {}", e.getResponseBodyAsString());
throw new DefectDojoPersistenceException("Failed to create product", e);
}
}

public void deleteUnusedBranches(List<String> existingBranches, String producName) {
long productId = retrieveProductId(producName);
deleteUnusedBranches(existingBranches, productId);
}

/**
* Deletes engagements based on branch tag
* Be aware that the branch tag MUST be set, otherwise all engagments will be deleted
*/
public void deleteUnusedBranches(List<String> existingBranches, long productId) {
RestTemplate restTemplate = new RestTemplate();

//get existing branches
List<EngagementResponse> engagementPayloads = getEngagementsForProduct(productId, 0);
for(EngagementResponse engagementPayload : engagementPayloads) {
boolean branchExists = false;
for(String existingBranchName : existingBranches) {
if(existingBranchName.equals(engagementPayload.getBanch())) {
branchExists = true;
break;
}
}
if(!branchExists) {
deleteEnageament(engagementPayload.getId());
LOG.info("Deleted engagement with id " + engagementPayload.getId() + ", branch " + engagementPayload.getBanch());
}
}
}

private List<EngagementResponse> getEngagementsForProduct(long productId, long offset) throws DefectDojoLoopException{
if(offset > 9999) {
throw new DefectDojoLoopException("offset engagement products too much!");
}
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(defectDojoUrl + "/api/v2/engagements")
.queryParam("product", Long.toString(productId))
.queryParam("limit", Long.toString(50L))
.queryParam("offset", Long.toString(offset));

RestTemplate restTemplate = new RestTemplate();
HttpEntity engagementRequest = new HttpEntity(getHeaders());

ResponseEntity<DefectDojoResponse<EngagementResponse>> engagementResponse = restTemplate.exchange(builder.toUriString(), HttpMethod.GET, engagementRequest, new ParameterizedTypeReference<DefectDojoResponse<EngagementResponse>>(){});
List<EngagementResponse> engagementPayloads = new LinkedList<EngagementResponse>();
for(EngagementResponse engagement : engagementResponse.getBody().getResults()){
engagementPayloads.add(engagement);
}
if(engagementResponse.getBody().getNext() != null){
engagementPayloads.addAll(getEngagementsForProduct(productId, offset + 1));;
}
return engagementPayloads;
}
public void deleteEnageament(long engagementId){
RestTemplate restTemplate = new RestTemplate();

String uri = defectDojoUrl + "/api/v2/engagements/" + engagementId + "/?id=" + engagementId;
HttpEntity request = new HttpEntity(getHeaders());
try {
ResponseEntity<DefectDojoResponse> response = restTemplate.exchange(uri, HttpMethod.GET, request, DefectDojoResponse.class);
} catch (HttpClientErrorException e) {
LOG.warn("Failed to delete engagment {}, engagementId: " + engagementId, e);
LOG.warn("Failure response body. {}", e.getResponseBodyAsString());
throw new DefectDojoPersistenceException("Failed to delete product", e);
}
}

/* options is created as follows:
MultiValueMap<String, String> mvn = new LinkedMultiValueMap<>();
mvn.add("engagement", Long.toString(engagementId));
*/
private List<Finding> getCurrentFindings(long engagementId, LinkedMultiValueMap<String, String> options){
RestTemplate restTemplate = new RestTemplate();

UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(defectDojoUrl + "/api/v2/findings/")
.queryParam("active", "true")
.queryParam("false_p", "false")
.queryParam("duplicate", "false")
.queryParam("test__engagement", Long.toString(engagementId));

if(options != null) {
builder = prepareParameters(options, builder);
}

HttpEntity request = new HttpEntity(getHeaders());
try {
ResponseEntity<DefectDojoResponse<Finding>> response = restTemplate.exchange(builder.toUriString(), HttpMethod.GET, request, new ParameterizedTypeReference<DefectDojoResponse<Finding>>(){});
List<Finding> findings = new LinkedList<Finding>();
for(Finding finding : response.getBody().getResults()){
findings.add(finding);
}
return findings;
} catch (HttpClientErrorException e) {
LOG.warn("Failed to get findings {}, engagementId: " + engagementId, e);
LOG.warn("Failure response body. {}", e.getResponseBodyAsString());
throw new DefectDojoPersistenceException("Failed to get findings", e);
}
}
private UriComponentsBuilder prepareParameters(LinkedMultiValueMap<String, String> queryParameters, UriComponentsBuilder builder) {
Iterator<String> it = queryParameters.keySet().iterator();

while(it.hasNext()){
String theKey = (String)it.next();
builder.replaceQueryParam(theKey, queryParameters.getFirst(theKey));
}
return builder;
}

public List<Finding> receiveNonHandeldFindings(String productName, String engagementName, String minimumServerity, LinkedMultiValueMap<String, String> options){
Long engagementId = getEngagementIdByEngagementName(engagementName, productName).orElse(0L);
//getCurrentFindings
List<Finding> findings = new LinkedList<Finding>();
for (String serverity : Finding.getServeritiesAndHigherServerities(minimumServerity)) {
LinkedMultiValueMap<String, String> optionTemp = options.clone();
optionTemp.add("serverity", serverity);
findings.addAll(getCurrentFindings(engagementId, optionTemp));
}
return findings;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,11 @@ public class DefectDojoProduct {

@JsonProperty("authorized_users")
List<String> authorizedUsers;

public DefectDojoProduct() {}

public DefectDojoProduct(String productName, String productDescription) {
name = productName;
description = productDescription;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import lombok.Data;

import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;


Expand Down Expand Up @@ -49,7 +51,7 @@ public class EngagementPayload {
protected Status status = Status.IN_PROGRESS;

@JsonProperty
protected List<String> tags = Arrays.asList("secureCodeBox", "automated");
protected List<String> tags = new LinkedList<>();

@JsonProperty
protected String tracker;
Expand All @@ -61,7 +63,7 @@ public class EngagementPayload {
protected String commitHash;

@JsonProperty("branch_tag")
protected String branch;
public String branch;

@JsonProperty("source_code_management_uri")
protected String repo;
Expand All @@ -78,6 +80,9 @@ public class EngagementPayload {
@JsonProperty
protected String description;

@JsonProperty("deduplication_on_engagement")
protected boolean deduplicationOnEngagement;

/**
* Currently only contains the statuses relevant to us.
* If you need others, feel free to add them ;)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,33 @@ public class EngagementResponse {
@JsonProperty
protected long id;

@JsonProperty
protected String name;

@JsonProperty("branch_tag")
protected String branch;

public long getId() {
return id;
}

public void setId(long id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getBanch() {
return branch;
}

public void setBranch(String branch) {
this.branch = branch;
}
}
Loading