Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ignore (suppress) findings globally (system-wide) #3231

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
11 changes: 10 additions & 1 deletion docs/_docs/triage/suppression.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,13 @@ By suppressing findings, external systems will, by default, have the same positi
finding will have a positive impact not only on Dependency-Track itself, but the metrics of external systems as well.

For example, vulnerability aggregation platforms such as Kenna Security or ThreadFix will become aware of the updated
list of findings and metrics the next time they sync. These systems will assume suppressed findings have been 'fixed'.
list of findings and metrics the next time they sync. These systems will assume suppressed findings have been 'fixed'.

### Ignoring Advisories Systemwide

Individual advisories can be ignored by setting the environment variable "IGNORED_ADVISORIES" before starting
Dependency-Track API server. Identifiers of the advisories need to be separated by a space character, for example:

```
export IGNORED_ADVISORIES="CVE-2019-123 GHSA-567"
```
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@
*/
package org.dependencytrack.persistence;

import alpine.common.logging.Logger;
import alpine.event.framework.Event;
import alpine.persistence.PaginatedResult;
import alpine.resources.AlpineRequest;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.dependencytrack.event.IndexEvent;
import org.dependencytrack.model.AffectedVersionAttribution;
import org.dependencytrack.model.Analysis;
Expand All @@ -36,18 +38,22 @@
import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

final class VulnerabilityQueryManager extends QueryManager implements IQueryManager {
private final Logger LOGGER = Logger.getLogger(this.getClass());
private static final Set<String> ignoredAdvisories = new HashSet<String>(Arrays.asList(SystemUtils.getEnvironmentVariable("IGNORED_ADVISORIES", "").split(" ")));

/**
* Constructs a new QueryManager.
Expand Down Expand Up @@ -212,10 +218,14 @@ public void addVulnerability(Vulnerability vulnerability, Component component, A
*/
public void addVulnerability(Vulnerability vulnerability, Component component, AnalyzerIdentity analyzerIdentity,
String alternateIdentifier, String referenceUrl) {
if (!contains(vulnerability, component)) {
component.addVulnerability(vulnerability);
component = persist(component);
persist(new FindingAttribution(component, vulnerability, analyzerIdentity, alternateIdentifier, referenceUrl));
if (ignoredAdvisories.contains(vulnerability.getVulnId())) {
this.LOGGER.warn("IGNORED_ADVISORIES hit in addVulnerability, skipping: " + vulnerability.getVulnId());
} else {
if (!contains(vulnerability, component)) {
component.addVulnerability(vulnerability);
component = persist(component);
persist(new FindingAttribution(component, vulnerability, analyzerIdentity, alternateIdentifier, referenceUrl));
}
}
}

Expand Down
50 changes: 28 additions & 22 deletions src/main/java/org/dependencytrack/util/NotificationUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import alpine.notification.Notification;
import alpine.notification.NotificationLevel;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.SystemUtils;
import org.dependencytrack.model.Analysis;
import org.dependencytrack.model.Bom;
import org.dependencytrack.model.Component;
Expand Down Expand Up @@ -63,46 +64,51 @@
import java.io.IOException;
import java.net.URLDecoder;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

import static java.nio.charset.StandardCharsets.UTF_8;

public final class NotificationUtil {
private static final Set<String> ignoredAdvisories = new HashSet<String>(Arrays.asList(SystemUtils.getEnvironmentVariable("IGNORED_ADVISORIES", "").split(" ")));

/**
* Private constructor.
*/
private NotificationUtil() { }

public static void analyzeNotificationCriteria(QueryManager qm, Vulnerability vulnerability, Component component, VulnerabilityAnalysisLevel vulnerabilityAnalysisLevel) {
if (!qm.contains(vulnerability, component)) {
// Component did not previously contain this vulnerability. It could be a newly discovered vulnerability
// against an existing component, or it could be a newly added (and vulnerable) component. Either way,
// it warrants a Notification be dispatched.
final Map<Long,Project> affectedProjects = new HashMap<>();
final List<Component> components = qm.matchIdentity(new ComponentIdentity(component));
for (final Component c : components) {
if(!affectedProjects.containsKey(c.getProject().getId())) {
affectedProjects.put(c.getProject().getId(), qm.detach(Project.class, c.getProject().getId()));
if (!ignoredAdvisories.contains(vulnerability.getVulnId())) {
if (!qm.contains(vulnerability, component)) {
// Component did not previously contain this vulnerability. It could be a newly discovered vulnerability
// against an existing component, or it could be a newly added (and vulnerable) component. Either way,
// it warrants a Notification be dispatched.
final Map<Long,Project> affectedProjects = new HashMap<>();
final List<Component> components = qm.matchIdentity(new ComponentIdentity(component));
for (final Component c : components) {
if(!affectedProjects.containsKey(c.getProject().getId())) {

Check warning on line 94 in src/main/java/org/dependencytrack/util/NotificationUtil.java

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/main/java/org/dependencytrack/util/NotificationUtil.java#L94

Deeply nested if..then statements are hard to read
affectedProjects.put(c.getProject().getId(), qm.detach(Project.class, c.getProject().getId()));
}
}
}

final Vulnerability detachedVuln = qm.detach(Vulnerability.class, vulnerability.getId());
detachedVuln.setAliases(qm.detach(qm.getVulnerabilityAliases(vulnerability))); // Aliases are lost during detach above
final Component detachedComponent = qm.detach(Component.class, component.getId());

Notification.dispatch(new Notification()
.scope(NotificationScope.PORTFOLIO)
.group(NotificationGroup.NEW_VULNERABILITY)
.title(generateNotificationTitle(NotificationConstants.Title.NEW_VULNERABILITY, component.getProject()))
.level(NotificationLevel.INFORMATIONAL)
.content(generateNotificationContent(detachedVuln))
.subject(new NewVulnerabilityIdentified(detachedVuln, detachedComponent, new HashSet<>(affectedProjects.values()), vulnerabilityAnalysisLevel))
);
final Vulnerability detachedVuln = qm.detach(Vulnerability.class, vulnerability.getId());
detachedVuln.setAliases(qm.detach(qm.getVulnerabilityAliases(vulnerability))); // Aliases are lost during detach above
final Component detachedComponent = qm.detach(Component.class, component.getId());

Notification.dispatch(new Notification()
.scope(NotificationScope.PORTFOLIO)
.group(NotificationGroup.NEW_VULNERABILITY)
.title(generateNotificationTitle(NotificationConstants.Title.NEW_VULNERABILITY, component.getProject()))
.level(NotificationLevel.INFORMATIONAL)
.content(generateNotificationContent(detachedVuln))
.subject(new NewVulnerabilityIdentified(detachedVuln, detachedComponent, new HashSet<>(affectedProjects.values()), vulnerabilityAnalysisLevel))
);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package org.dependencytrack.tasks.scanners;

import alpine.persistence.PaginatedResult;
import org.dependencytrack.PersistenceCapableTest;
import org.dependencytrack.model.Component;
import org.dependencytrack.model.Project;
import org.dependencytrack.model.Vulnerability;
import org.dependencytrack.model.VulnerableSoftware;
import org.junit.Test;

import java.lang.reflect.Field;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;

public class InternalAnalysisIgnoredGloballyTaskTest extends PersistenceCapableTest {

@Test
public void testIssueIgnoredGlobally1574() throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
final Map<String, String> env = System.getenv();
final Field envField = env.getClass().getDeclaredField("m");
try {
envField.setAccessible(true);
((Map<String, String>) envField.get(env)).put("IGNORED_ADVISORIES", "GHSA-wjm3-fq3r-5x46");

var project = new Project();
project.setName("acme-app");
project = qm.createProject(project, Collections.emptyList(), false);
var component = new Component();
component.setProject(project);
component.setName("github.com/tidwall/gjson");
component.setVersion("v1.6.0");
component.setPurl("pkg:golang/github.com/tidwall/[email protected]?type=module");
component = qm.createComponent(component, false);

var vulnerableSoftware = new VulnerableSoftware();
vulnerableSoftware.setPurlType("golang");
vulnerableSoftware.setPurlNamespace("github.com/tidwall");
vulnerableSoftware.setPurlName("gjson");
vulnerableSoftware.setVersionEndExcluding("1.6.5");
vulnerableSoftware.setVulnerable(true);
vulnerableSoftware = qm.persist(vulnerableSoftware);

var vulnerability = new Vulnerability();
vulnerability.setVulnId("GHSA-wjm3-fq3r-5x46");
vulnerability.setSource(Vulnerability.Source.GITHUB);
vulnerability.setVulnerableSoftware(List.of(vulnerableSoftware));
qm.createVulnerability(vulnerability, false);

new InternalAnalysisTask().analyze(List.of(component));

final PaginatedResult vulnerabilities = qm.getVulnerabilities(component);
assertThat(vulnerabilities.getTotal()).isEqualTo(0);
} finally {
((Map<String, String>) envField.get(env)).remove("IGNORED_ADVISORIES"); // this doesn't work!
envField.setAccessible(false);
}
}
}