diff --git a/scb-persistenceproviders/defectdojo-persistenceprovider/src/main/java/io/securecodebox/persistence/DefectDojoLoopException.java b/scb-persistenceproviders/defectdojo-persistenceprovider/src/main/java/io/securecodebox/persistence/DefectDojoLoopException.java new file mode 100644 index 00000000..6ab330e2 --- /dev/null +++ b/scb-persistenceproviders/defectdojo-persistenceprovider/src/main/java/io/securecodebox/persistence/DefectDojoLoopException.java @@ -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); + } +} diff --git a/scb-persistenceproviders/defectdojo-persistenceprovider/src/main/java/io/securecodebox/persistence/DefectDojoPersistenceProvider.java b/scb-persistenceproviders/defectdojo-persistenceprovider/src/main/java/io/securecodebox/persistence/DefectDojoPersistenceProvider.java index f9595cf4..23e9b653 100644 --- a/scb-persistenceproviders/defectdojo-persistenceprovider/src/main/java/io/securecodebox/persistence/DefectDojoPersistenceProvider.java +++ b/scb-persistenceproviders/defectdojo-persistenceprovider/src/main/java/io/securecodebox/persistence/DefectDojoPersistenceProvider.java @@ -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); } diff --git a/scb-persistenceproviders/defectdojo-persistenceprovider/src/main/java/io/securecodebox/persistence/DefectDojoService.java b/scb-persistenceproviders/defectdojo-persistenceprovider/src/main/java/io/securecodebox/persistence/DefectDojoService.java index 05e8be9b..55ffda9b 100644 --- a/scb-persistenceproviders/defectdojo-persistenceprovider/src/main/java/io/securecodebox/persistence/DefectDojoService.java +++ b/scb-persistenceproviders/defectdojo-persistenceprovider/src/main/java/io/securecodebox/persistence/DefectDojoService.java @@ -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; @@ -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") @@ -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(); @@ -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); @@ -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 @@ -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 getEngagementIdByEngagementName(String engagementName, String productName){ + long productId = retrieveProductId(productName); + return getEngagementIdByEngagementName(engagementName, productId, 0L); + } + private Optional getEngagementIdByEngagementName(String engagementName, long productId){ + return getEngagementIdByEngagementName(engagementName, productId, 0L); + } + + private Optional 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> engagementResponse = restTemplate.exchange(builder.toUriString(), HttpMethod.GET, engagementRequest, new ParameterizedTypeReference>(){}); + + 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 payload = new HttpEntity<>(productPayload, getHeaders()); + + try { + ResponseEntity 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 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 existingBranches, long productId) { + RestTemplate restTemplate = new RestTemplate(); + + //get existing branches + List 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 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> engagementResponse = restTemplate.exchange(builder.toUriString(), HttpMethod.GET, engagementRequest, new ParameterizedTypeReference>(){}); + List engagementPayloads = new LinkedList(); + 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 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 mvn = new LinkedMultiValueMap<>(); + mvn.add("engagement", Long.toString(engagementId)); + */ + private List getCurrentFindings(long engagementId, LinkedMultiValueMap 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> response = restTemplate.exchange(builder.toUriString(), HttpMethod.GET, request, new ParameterizedTypeReference>(){}); + List findings = new LinkedList(); + 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 queryParameters, UriComponentsBuilder builder) { + Iterator it = queryParameters.keySet().iterator(); + + while(it.hasNext()){ + String theKey = (String)it.next(); + builder.replaceQueryParam(theKey, queryParameters.getFirst(theKey)); + } + return builder; + } + + public List receiveNonHandeldFindings(String productName, String engagementName, String minimumServerity, LinkedMultiValueMap options){ + Long engagementId = getEngagementIdByEngagementName(engagementName, productName).orElse(0L); + //getCurrentFindings + List findings = new LinkedList(); + for (String serverity : Finding.getServeritiesAndHigherServerities(minimumServerity)) { + LinkedMultiValueMap optionTemp = options.clone(); + optionTemp.add("serverity", serverity); + findings.addAll(getCurrentFindings(engagementId, optionTemp)); + } + return findings; + } } diff --git a/scb-persistenceproviders/defectdojo-persistenceprovider/src/main/java/io/securecodebox/persistence/models/DefectDojoProduct.java b/scb-persistenceproviders/defectdojo-persistenceprovider/src/main/java/io/securecodebox/persistence/models/DefectDojoProduct.java index ab20285a..6e5a3f04 100644 --- a/scb-persistenceproviders/defectdojo-persistenceprovider/src/main/java/io/securecodebox/persistence/models/DefectDojoProduct.java +++ b/scb-persistenceproviders/defectdojo-persistenceprovider/src/main/java/io/securecodebox/persistence/models/DefectDojoProduct.java @@ -21,4 +21,11 @@ public class DefectDojoProduct { @JsonProperty("authorized_users") List authorizedUsers; + + public DefectDojoProduct() {} + + public DefectDojoProduct(String productName, String productDescription) { + name = productName; + description = productDescription; + } } diff --git a/scb-persistenceproviders/defectdojo-persistenceprovider/src/main/java/io/securecodebox/persistence/models/EngagementPayload.java b/scb-persistenceproviders/defectdojo-persistenceprovider/src/main/java/io/securecodebox/persistence/models/EngagementPayload.java index 2bf1283f..25b2dc67 100644 --- a/scb-persistenceproviders/defectdojo-persistenceprovider/src/main/java/io/securecodebox/persistence/models/EngagementPayload.java +++ b/scb-persistenceproviders/defectdojo-persistenceprovider/src/main/java/io/securecodebox/persistence/models/EngagementPayload.java @@ -22,6 +22,8 @@ import lombok.Data; import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; import java.util.List; @@ -49,7 +51,7 @@ public class EngagementPayload { protected Status status = Status.IN_PROGRESS; @JsonProperty - protected List tags = Arrays.asList("secureCodeBox", "automated"); + protected List tags = new LinkedList<>(); @JsonProperty protected String tracker; @@ -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; @@ -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 ;) diff --git a/scb-persistenceproviders/defectdojo-persistenceprovider/src/main/java/io/securecodebox/persistence/models/EngagementResponse.java b/scb-persistenceproviders/defectdojo-persistenceprovider/src/main/java/io/securecodebox/persistence/models/EngagementResponse.java index b4bd7911..f221ae63 100644 --- a/scb-persistenceproviders/defectdojo-persistenceprovider/src/main/java/io/securecodebox/persistence/models/EngagementResponse.java +++ b/scb-persistenceproviders/defectdojo-persistenceprovider/src/main/java/io/securecodebox/persistence/models/EngagementResponse.java @@ -24,6 +24,12 @@ public class EngagementResponse { @JsonProperty protected long id; + @JsonProperty + protected String name; + + @JsonProperty("branch_tag") + protected String branch; + public long getId() { return id; } @@ -31,4 +37,20 @@ public long getId() { 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; + } } diff --git a/scb-persistenceproviders/defectdojo-persistenceprovider/src/main/java/io/securecodebox/persistence/models/Finding.java b/scb-persistenceproviders/defectdojo-persistenceprovider/src/main/java/io/securecodebox/persistence/models/Finding.java new file mode 100644 index 00000000..8b35a728 --- /dev/null +++ b/scb-persistenceproviders/defectdojo-persistenceprovider/src/main/java/io/securecodebox/persistence/models/Finding.java @@ -0,0 +1,85 @@ +/* + * + * SecureCodeBox (SCB) + * + * 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.models; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + + +@Data +public class Finding { + @JsonProperty + protected long id; + + @JsonProperty + protected String title; + + @JsonProperty + protected long cwe; + + @JsonProperty + protected String cve; + @JsonProperty + + protected String severity; + + @JsonProperty + protected String description; + + @JsonProperty + protected boolean active = true; + + @JsonProperty + protected boolean verified = true; + + @JsonProperty("false_p") + protected boolean falsePostive = false; + + @JsonProperty + protected boolean duplicate = false; + + @JsonProperty("is_Mitigated") + protected boolean isMitigated = false; + + enum FindingSeverities { + + } + public static final LinkedList findingServerities = new LinkedList(){{ + add("Low"); + add("Medium"); + add("High"); + add("Critical"); + }}; + public static LinkedList getServeritiesAndHigherServerities(String minimumServerity){ + LinkedList severities = new LinkedList(); + boolean minimumFound = false; + for(String serverity : findingServerities) { + if(minimumFound || minimumServerity.equals(serverity)) { + minimumFound = true; + severities.add(serverity); + } + } + + return severities; + } +} \ No newline at end of file diff --git a/scb-persistenceproviders/defectdojo-persistenceprovider/src/main/java/io/securecodebox/persistence/models/ProductPayload.java b/scb-persistenceproviders/defectdojo-persistenceprovider/src/main/java/io/securecodebox/persistence/models/ProductPayload.java new file mode 100644 index 00000000..b7dfee33 --- /dev/null +++ b/scb-persistenceproviders/defectdojo-persistenceprovider/src/main/java/io/securecodebox/persistence/models/ProductPayload.java @@ -0,0 +1,34 @@ +/* +* 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.models; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; +import lombok.Data; + +@Data +public class ProductPayload { + @JsonProperty + String name; + + @JsonProperty + String description; + + public ProductPayload(String productName, String productDescription) { + name = productName; + description = productDescription; + } +} diff --git a/scb-persistenceproviders/defectdojo-persistenceprovider/src/main/java/io/securecodebox/persistence/models/ProductResponse.java b/scb-persistenceproviders/defectdojo-persistenceprovider/src/main/java/io/securecodebox/persistence/models/ProductResponse.java new file mode 100644 index 00000000..70ae774c --- /dev/null +++ b/scb-persistenceproviders/defectdojo-persistenceprovider/src/main/java/io/securecodebox/persistence/models/ProductResponse.java @@ -0,0 +1,45 @@ +/* + * + * 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.models; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class ProductResponse { + @JsonProperty + protected long id; + + @JsonProperty + protected String name; + + 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; + } +} diff --git a/scb-persistenceproviders/defectdojo-persistenceprovider/src/test/java/io/securecodebox/persistence/DefectDojoPersistenceProviderTest.java b/scb-persistenceproviders/defectdojo-persistenceprovider/src/test/java/io/securecodebox/persistence/DefectDojoPersistenceProviderTest.java index da045633..1c0bac60 100644 --- a/scb-persistenceproviders/defectdojo-persistenceprovider/src/test/java/io/securecodebox/persistence/DefectDojoPersistenceProviderTest.java +++ b/scb-persistenceproviders/defectdojo-persistenceprovider/src/test/java/io/securecodebox/persistence/DefectDojoPersistenceProviderTest.java @@ -140,6 +140,8 @@ public void createsTheEngagement(){ payload.setBuildServer(5l); payload.setScmServer(7l); payload.setOrchestrationEngine(9l); + payload.getTags().add("secureCodeBox"); + payload.getTags().add("automated"); persistenceProvider.persist(securityTest); @@ -231,4 +233,13 @@ public void createsFindingsForNonSupportedScanner() { eq("Generic Findings Import") ); } + + @Test + public void createProduct() { + String productName = "mytestproduct"; + defectDojoService.createProduct(productName); + verify(defectDojoService, times(1)).createProduct( + eq(productName) + ); + } }