Skip to content

Commit

Permalink
Add functionality to suppress CVEs in upload goal (pmckeown#423)
Browse files Browse the repository at this point in the history
  • Loading branch information
Kevin Pepryk committed Oct 17, 2024
1 parent 1352bee commit f3cf6f4
Show file tree
Hide file tree
Showing 14 changed files with 658 additions and 423 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,17 @@
*/
public final class ResourceConstants {

public static final String V1_BOM = "/api/v1/bom";
public static final String V1_BOM_TOKEN_UUID = "/api/v1/bom/token/{uuid}";
public static final String V1_PROJECT = "/api/v1/project?limit=1000000&offset=0";
public static final String V1_PROJECT_UUID = "/api/v1/project/{uuid}";
public static final String V1_FINDING_PROJECT_UUID = "/api/v1/finding/project/{uuid}";
public static final String V1_METRICS_PROJECT_UUID_CURRENT = "/api/v1/metrics/project/{uuid}/current";
public static final String V1_METRICS_PROJECT_UUID_REFRESH = "/api/v1/metrics/project/{uuid}/refresh";
public static final String V1_POLICY_VIOLATION_PROJECT_UUID = "/api/v1/violation/project/{uuid}";
public static final String V1_ANALYSIS = "/api/v1/analysis";
public static final String V1_BOM = "/api/v1/bom";
public static final String V1_BOM_TOKEN_UUID = "/api/v1/bom/token/{uuid}";
public static final String V1_PROJECT = "/api/v1/project?limit=1000000&offset=0";
public static final String V1_PROJECT_UUID = "/api/v1/project/{uuid}";
public static final String V1_FINDING_PROJECT_UUID = "/api/v1/finding/project/{uuid}";
public static final String V1_METRICS_PROJECT_UUID_CURRENT = "/api/v1/metrics/project/{uuid}/current";
public static final String V1_METRICS_PROJECT_UUID_REFRESH = "/api/v1/metrics/project/{uuid}/refresh";
public static final String V1_POLICY_VIOLATION_PROJECT_UUID = "/api/v1/violation/project/{uuid}";

private ResourceConstants() {
// Constants file
}
private ResourceConstants() {
// Constants file
}
}
Original file line number Diff line number Diff line change
@@ -1,43 +1,67 @@
package io.github.pmckeown.dependencytrack.finding;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import javax.xml.bind.annotation.XmlElement;

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

import javax.xml.bind.annotation.XmlElement;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;

public class Analysis {

public enum State {
NOT_AFFECTED,
FALSE_POSITIVE,
IN_TRIAGE,
EXPLOITABLE,
NOT_SET
}

private boolean isSuppressed;
private State state;

@JsonCreator
public Analysis(@JsonProperty("isSuppressed") boolean isSuppressed, @JsonProperty("state") State state) {
this.isSuppressed = isSuppressed;
this.state = state;
}

@XmlElement
public boolean isSuppressed() {
return isSuppressed;
}

@XmlElement
public State getState() {
return state;
}

@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
}
public enum AnalysisState {
NOT_AFFECTED,
FALSE_POSITIVE,
IN_TRIAGE,
EXPLOITABLE,
NOT_SET,
RESOLVED
}

public enum AnalysisJustification {
CODE_NOT_PRESENT,
CODE_NOT_REACHABLE,
REQUIRES_CONFIGURATION,
REQUIRES_DEPENDENCY,
REQUIRES_ENVIRONMENT,
PROTECTED_BY_COMPILER,
PROTECTED_AT_RUNTIME,
PROTECTED_AT_PERIMETER,
PROTECTED_BY_MITIGATING_CONTROL,
NOT_SET
}

private boolean isSuppressed;
private AnalysisState analysisState;
private AnalysisJustification analysisJustification;

@JsonCreator
public Analysis(@JsonProperty("isSuppressed") final boolean isSuppressed,
@JsonProperty("analysisState") final AnalysisState analysisState,
@JsonProperty("analysisJustification") final AnalysisJustification analysisJustification) {
this.isSuppressed = isSuppressed;
this.analysisState = analysisState;
this.analysisJustification = analysisJustification;
}

@XmlElement
public boolean isSuppressed() {
return isSuppressed;
}

@XmlElement
public AnalysisState getAnalysisState() {
return analysisState;
}

@XmlElement
public AnalysisJustification getAnalysisJustification() {
return analysisJustification;
}

@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package io.github.pmckeown.dependencytrack.finding;

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

import io.github.pmckeown.dependencytrack.finding.Analysis.AnalysisJustification;
import io.github.pmckeown.dependencytrack.finding.Analysis.AnalysisState;

public class AnalysisDecisionRequest {

/*
* For project, component and vulnerability the according UUID must be used
*/
private String project;
private String component;
private String vulnerability;
private AnalysisState analysisState;
private AnalysisJustification analysisJustification;
private String analysisDetails;
private boolean isSuppressed;

public AnalysisDecisionRequest(final String project, final String component, final String vulnerability,
final AnalysisState analysisState, final AnalysisJustification analysisJustification,
final String analysisDetails, final boolean isSuppressed) {
this.project = project;
this.component = component;
this.vulnerability = vulnerability;
this.analysisState = analysisState;
this.isSuppressed = isSuppressed;
this.analysisJustification = analysisJustification;
this.analysisDetails = analysisDetails;
}

public String getProject() {
return project;
}

public String getComponent() {
return component;
}

public String getVulnerability() {
return vulnerability;
}

public AnalysisState getAnalysisState() {
return analysisState;
}

public AnalysisJustification getAnalysisJustification() {
return analysisJustification;
}

public String getAnalysisDetails() {
return analysisDetails;
}

public boolean isSuppressed() {
return isSuppressed;
}

@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
}
}
Original file line number Diff line number Diff line change
@@ -1,50 +1,103 @@
package io.github.pmckeown.dependencytrack.finding;

import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

import javax.inject.Inject;
import javax.inject.Singleton;

import io.github.pmckeown.dependencytrack.DependencyTrackException;
import io.github.pmckeown.dependencytrack.Response;
import io.github.pmckeown.dependencytrack.finding.Analysis.AnalysisJustification;
import io.github.pmckeown.dependencytrack.finding.Analysis.AnalysisState;
import io.github.pmckeown.dependencytrack.project.Project;
import io.github.pmckeown.util.Logger;
import kong.unirest.UnirestException;

import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

@Singleton
public class FindingsAction {

private FindingsClient findingClient;

private Logger logger;

@Inject
public FindingsAction(FindingsClient findingClient, Logger logger) {
this.findingClient = findingClient;
this.logger = logger;
}

List<Finding> getFindings(Project project) throws DependencyTrackException {
logger.info("Getting findings for project %s-%s", project.getName(), project.getVersion());

try {
Response<List<Finding>> response = findingClient.getFindingsForProject(project);
Optional<List<Finding>> body = response.getBody();
if (response.isSuccess()) {
if (body.isPresent()) {
return body.get();
} else {
logger.info("No findings available for project %s-%s", project.getName(),
project.getVersion());
return Collections.emptyList();
}
} else {
throw new DependencyTrackException("Error received from server");
}
} catch (UnirestException ex) {
logger.error(ex.getMessage());
throw new DependencyTrackException(ex.getMessage());
}
}
private FindingsClient findingClient;

private Logger logger;

@Inject
public FindingsAction(final FindingsClient findingClient, final Logger logger) {
this.findingClient = findingClient;
this.logger = logger;
}

public List<Finding> getFindings(final Project project) throws DependencyTrackException {
logger.info("Getting findings for project %s-%s", project.getName(), project.getVersion());

try {
final Response<List<Finding>> response = findingClient.getFindingsForProject(project);
final Optional<List<Finding>> body = response.getBody();
if (response.isSuccess()) {
if (body.isPresent()) {
return body.get();
} else {
logger.info("No findings available for project %s-%s", project.getName(), project.getVersion());
return Collections.emptyList();
}
} else {
throw new DependencyTrackException("Error received from server");
}
} catch (final UnirestException ex) {
logger.error(ex.getMessage());
throw new DependencyTrackException(ex.getMessage());
}
}

public void suppressFindings(final Project project, final List<Suppression> suppressions)
throws DependencyTrackException {
logger.info("Suppression list is present");
final List<Finding> findings = getFindings(project);

if (findings.isEmpty()) {
logger.info("Skipping suppression");
} else {
suppressions.forEach((suppression) -> {
final List<Finding> relevantFindings = findings.stream().filter(
finding -> Objects.equals(suppression.getCve(), finding.getVulnerability().getVulnId()))
.collect(Collectors.toList());
if (relevantFindings.isEmpty()) {
logger.debug("Findings do not include vulnerability %s",
Objects.requireNonNullElse(suppression.getCve(), "?"));
} else {
logger.info("Suppressing vulnerability %s", suppression.getCve());
logger.debug("%s", suppression);

final AnalysisState state = parseState(suppression.getState());
relevantFindings.forEach(finding -> findingClient.recordNewAnalysisDecision(
new AnalysisDecisionRequest(project.getUuid(), finding.getComponent().getUuid(),
finding.getVulnerability().getUuid(), parseState(suppression.getState()),
parseJustification(suppression.getJustification(), state), suppression.getDetails(),
true)));
}
});
}
}

private AnalysisState parseState(final String state) {
try {
return AnalysisState.valueOf(Objects.toString(state, "").toUpperCase());
} catch (final RuntimeException ex) {
return AnalysisState.NOT_AFFECTED;
}
}

private AnalysisJustification parseJustification(final String justification, final AnalysisState state) {
try {
return AnalysisState.NOT_AFFECTED.equals(state)
? AnalysisJustification.valueOf(Objects.toString(justification, "").toUpperCase())
: AnalysisJustification.NOT_SET;
} catch (final RuntimeException ex) {
return AnalysisJustification.NOT_SET;
}
}

}
Loading

0 comments on commit f3cf6f4

Please sign in to comment.