forked from pmckeown/dependency-track-maven-plugin
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add functionality to suppress CVEs in upload goal (pmckeown#423)
- Loading branch information
Kevin Pepryk
committed
Oct 17, 2024
1 parent
1352bee
commit f3cf6f4
Showing
14 changed files
with
658 additions
and
423 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
92 changes: 58 additions & 34 deletions
92
src/main/java/io/github/pmckeown/dependencytrack/finding/Analysis.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
66 changes: 66 additions & 0 deletions
66
src/main/java/io/github/pmckeown/dependencytrack/finding/AnalysisDecisionRequest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
129 changes: 91 additions & 38 deletions
129
src/main/java/io/github/pmckeown/dependencytrack/finding/FindingsAction.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} | ||
|
||
} |
Oops, something went wrong.