Skip to content

Commit

Permalink
Add BOM_PROCESSING_FAILED notification (DependencyTrack#2600)
Browse files Browse the repository at this point in the history
* Add BOM_PROCESSING_FAILED notification

A new notification is sent if the notification rule includes the
notification group BOM_PROCESSING_FAILED and if an error happens during
the upload of a BOM.

Signed-off-by: RBickert <rbt@mm-software.com>

* Add project url and exception to new notification

Signed-off-by: RBickert <rbt@mm-software.com>

* Add BOM format and specVersion

Detach `bomProcessingFailedProject`

Rename `exception` to `cause`

Signed-off-by: RBickert <rbt@mm-software.com>

---------

Signed-off-by: RBickert <rbt@mm-software.com>
  • Loading branch information
rbt-mm committed Mar 22, 2023
1 parent 7fd47cd commit 7a789d5
Show file tree
Hide file tree
Showing 10 changed files with 143 additions and 1 deletion.
1 change: 1 addition & 0 deletions docs/_docs/integrations/notifications.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Each scope contains a set of notification groups that can be used to subscribe t
| PORTFOLIO | PROJECT_AUDIT_CHANGE | Notifications generated whenever an analysis or suppression state has changed on a finding from a project |
| PORTFOLIO | BOM_CONSUMED | Notifications generated whenever a supported BOM is ingested and identified |
| PORTFOLIO | BOM_PROCESSED | Notifications generated after a supported BOM is ingested, identified, and successfully processed |
| PORTFOLIO | BOM_PROCESSING_FAILED | Notifications generated whenever a BOM upload process fails |
| PORTFOLIO | POLICY_VIOLATION | Notifications generated whenever a policy violation is identified |

## Configuring Publishers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public static class Title {
public static final String POLICY_VIOLATION = "Policy Violation";
public static final String BOM_CONSUMED = "Bill of Materials Consumed";
public static final String BOM_PROCESSED = "Bill of Materials Processed";
public static final String BOM_PROCESSING_FAILED = "Bill of Materials Processing Failed";
public static final String VEX_CONSUMED = "Vulnerability Exploitability Exchange (VEX) Consumed";
public static final String VEX_PROCESSED = "Vulnerability Exploitability Exchange (VEX) Processed";
public static final String PROJECT_CREATED = "Project Added";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public enum NotificationGroup {
PROJECT_AUDIT_CHANGE,
BOM_CONSUMED,
BOM_PROCESSED,
BOM_PROCESSING_FAILED,
VEX_CONSUMED,
VEX_PROCESSED,
POLICY_VIOLATION,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.dependencytrack.notification.publisher.SendMailPublisher;
import org.dependencytrack.notification.vo.AnalysisDecisionChange;
import org.dependencytrack.notification.vo.BomConsumedOrProcessed;
import org.dependencytrack.notification.vo.BomProcessingFailed;
import org.dependencytrack.notification.vo.NewVulnerabilityIdentified;
import org.dependencytrack.notification.vo.NewVulnerableDependency;
import org.dependencytrack.notification.vo.PolicyViolationIdentified;
Expand Down Expand Up @@ -175,6 +176,9 @@ List<NotificationRule> resolveRules(final Notification notification) {
} else if (NotificationScope.PORTFOLIO.name().equals(notification.getScope())
&& notification.getSubject() instanceof final BomConsumedOrProcessed subject) {
limitToProject(rules, result, notification, subject.getProject());
} else if (NotificationScope.PORTFOLIO.name().equals(notification.getScope())
&& notification.getSubject() instanceof final BomProcessingFailed subject) {
limitToProject(rules, result, notification, subject.getProject());
} else if (NotificationScope.PORTFOLIO.name().equals(notification.getScope())
&& notification.getSubject() instanceof final VexConsumedOrProcessed subject) {
limitToProject(rules, result, notification, subject.getProject());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.dependencytrack.notification.NotificationScope;
import org.dependencytrack.notification.vo.AnalysisDecisionChange;
import org.dependencytrack.notification.vo.BomConsumedOrProcessed;
import org.dependencytrack.notification.vo.BomProcessingFailed;
import org.dependencytrack.notification.vo.NewVulnerabilityIdentified;
import org.dependencytrack.notification.vo.NewVulnerableDependency;
import org.dependencytrack.notification.vo.PolicyViolationIdentified;
Expand Down Expand Up @@ -115,6 +116,9 @@ default String prepareTemplate(final Notification notification, final PebbleTemp
} else if (notification.getSubject() instanceof final BomConsumedOrProcessed subject) {
context.put("subject", subject);
context.put("subjectJson", NotificationUtil.toJson(subject));
} else if (notification.getSubject() instanceof final BomProcessingFailed subject) {
context.put("subject", subject);
context.put("subjectJson", NotificationUtil.toJson(subject));
} else if (notification.getSubject() instanceof final VexConsumedOrProcessed subject) {
context.put("subject", subject);
context.put("subjectJson", NotificationUtil.toJson(subject));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* This file is part of Dependency-Track.
*
* 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.
*
* SPDX-License-Identifier: Apache-2.0
* Copyright (c) Steve Springett. All Rights Reserved.
*/
package org.dependencytrack.notification.vo;

import org.dependencytrack.model.Bom;
import org.dependencytrack.model.Project;

public class BomProcessingFailed {

private Project project;
private String bom;
private String cause;
private Bom.Format format;
private String specVersion;

public BomProcessingFailed(final Project project, final String bom, final String cause, final Bom.Format format, final String specVersion) {
this.project = project;
this.bom = bom;
this.cause = cause;
this.format = format;
this.specVersion = specVersion;
}

public Project getProject() {
return project;
}

public String getBom() {
return bom;
}

public String getCause() {
return cause;
}

public Bom.Format getFormat() {
return format;
}

public String getSpecVersion() {
return specVersion;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import org.dependencytrack.notification.NotificationGroup;
import org.dependencytrack.notification.NotificationScope;
import org.dependencytrack.notification.vo.BomConsumedOrProcessed;
import org.dependencytrack.notification.vo.BomProcessingFailed;
import org.dependencytrack.parser.cyclonedx.util.ModelConverter;
import org.dependencytrack.persistence.QueryManager;
import org.dependencytrack.util.CompressUtil;
Expand Down Expand Up @@ -66,11 +67,15 @@ public class BomUploadProcessingTask implements Subscriber {
*/
public void inform(final Event e) {
if (e instanceof BomUploadEvent) {
Project bomProcessingFailedProject = null;
Bom.Format bomProcessingFailedBomFormat = null;
String bomProcessingFailedBomVersion = null;
final BomUploadEvent event = (BomUploadEvent) e;
final byte[] bomBytes = CompressUtil.optionallyDecompress(event.getBom());
final QueryManager qm = new QueryManager();
try {
final Project project = qm.getObjectByUuid(Project.class, event.getProjectUuid());
final Project project = qm.getObjectByUuid(Project.class, event.getProjectUuid());
bomProcessingFailedProject = project;

if (project == null) {
LOGGER.warn("Ignoring BOM Upload event for no longer existing project " + event.getProjectUuid());
Expand All @@ -95,9 +100,11 @@ public void inform(final Event e) {
if (qm.isEnabled(ConfigPropertyConstants.ACCEPT_ARTIFACT_CYCLONEDX)) {
LOGGER.info("Processing CycloneDX BOM uploaded to project: " + event.getProjectUuid());
bomFormat = Bom.Format.CYCLONEDX;
bomProcessingFailedBomFormat = bomFormat;
final Parser parser = BomParserFactory.createParser(bomBytes);
cycloneDxBom = parser.parse(bomBytes);
bomSpecVersion = cycloneDxBom.getSpecVersion();
bomProcessingFailedBomVersion = bomSpecVersion;
bomVersion = cycloneDxBom.getVersion();
if (project.getClassifier() == null) {
final var classifier = Optional.ofNullable(cycloneDxBom.getMetadata())
Expand Down Expand Up @@ -173,6 +180,16 @@ public void inform(final Event e) {
.subject(new BomConsumedOrProcessed(detachedProject, Base64.getEncoder().encodeToString(bomBytes), bomFormat, bomSpecVersion)));
} catch (Exception ex) {
LOGGER.error("Error while processing bom", ex);
if (bomProcessingFailedProject != null) {
bomProcessingFailedProject = qm.detach(Project.class, bomProcessingFailedProject.getId());
}
Notification.dispatch(new Notification()
.scope(NotificationScope.PORTFOLIO)
.group(NotificationGroup.BOM_PROCESSING_FAILED)
.title(NotificationConstants.Title.BOM_PROCESSING_FAILED)
.level(NotificationLevel.ERROR)
.content("An error occurred while processing a BOM")
.subject(new BomProcessingFailed(bomProcessingFailedProject, Base64.getEncoder().encodeToString(bomBytes), ex.getMessage(), bomProcessingFailedBomFormat, bomProcessingFailedBomVersion)));
} finally {
qm.commitSearchIndex(true, Component.class);
qm.commitSearchIndex(true, ServiceComponent.class);
Expand Down
19 changes: 19 additions & 0 deletions src/main/java/org/dependencytrack/util/NotificationUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import org.dependencytrack.notification.publisher.DefaultNotificationPublishers;
import org.dependencytrack.notification.vo.AnalysisDecisionChange;
import org.dependencytrack.notification.vo.BomConsumedOrProcessed;
import org.dependencytrack.notification.vo.BomProcessingFailed;
import org.dependencytrack.notification.vo.NewVulnerabilityIdentified;
import org.dependencytrack.notification.vo.NewVulnerableDependency;
import org.dependencytrack.notification.vo.PolicyViolationIdentified;
Expand Down Expand Up @@ -442,6 +443,24 @@ public static JsonObject toJson(final BomConsumedOrProcessed vo) {
return builder.build();
}

public static JsonObject toJson(final BomProcessingFailed vo) {
final JsonObjectBuilder builder = Json.createObjectBuilder();
if (vo.getProject() != null) {
builder.add("project", toJson(vo.getProject()));
}
if (vo.getBom() != null) {
builder.add("bom", Json.createObjectBuilder()
.add("content", vo.getBom())
.add("format", vo.getFormat().getFormatShortName())
.add("specVersion", vo.getSpecVersion()).build()
);
}
if (vo.getCause() != null) {
builder.add("cause", vo.getCause());
}
return builder.build();
}

public static JsonObject toJson(final VexConsumedOrProcessed vo) {
final JsonObjectBuilder builder = Json.createObjectBuilder();
if (vo.getProject() != null) {
Expand Down
10 changes: 10 additions & 0 deletions src/main/resources/templates/notification/publisher/email.peb
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,16 @@ Project: {{ subject.project.name }}
Version: {{ subject.project.version }}
Description: {{ subject.project.description }}
Project URL: {{ baseUrl }}/projects/{{ subject.project.uuid }}
{% elseif notification.group == "BOM_PROCESSING_FAILED" %}
Project: {{ subject.project.name }}
Version: {{ subject.project.version }}
Description: {{ subject.project.description }}
Project URL: {{ baseUrl }}/projects/{{ subject.project.uuid }}

--------------------------------------------------------------------------------

Cause:
{{ subject.cause }}
{% elseif notification.group == "VEX_CONSUMED" %}
Project: {{ subject.project.name }}
Version: {{ subject.project.version }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import org.dependencytrack.notification.publisher.Publisher;
import org.dependencytrack.notification.vo.AnalysisDecisionChange;
import org.dependencytrack.notification.vo.BomConsumedOrProcessed;
import org.dependencytrack.notification.vo.BomProcessingFailed;
import org.dependencytrack.notification.vo.NewVulnerabilityIdentified;
import org.dependencytrack.notification.vo.NewVulnerableDependency;
import org.dependencytrack.notification.vo.PolicyViolationIdentified;
Expand Down Expand Up @@ -403,6 +404,31 @@ public void testBomConsumedOrProcessedLimitedToProject() {
.satisfiesExactly(resolvedRule -> assertThat(resolvedRule.getName()).isEqualTo("Test Rule"));
}

@Test
public void testBomProcessingFailedLimitedToProject() {
final Project projectA = qm.createProject("Project A", null, "1.0", null, null, null, true, false);
final Project projectB = qm.createProject("Project B", null, "1.0", null, null, null, true, false);

final NotificationPublisher publisher = createSlackPublisher();

final NotificationRule rule = qm.createNotificationRule("Test Rule", NotificationScope.PORTFOLIO, NotificationLevel.INFORMATIONAL, publisher);
rule.setNotifyOn(Set.of(NotificationGroup.BOM_PROCESSING_FAILED));
rule.setProjects(List.of(projectA));

final var notification = new Notification();
notification.setScope(NotificationScope.PORTFOLIO.name());
notification.setGroup(NotificationGroup.BOM_PROCESSING_FAILED.name());
notification.setLevel(NotificationLevel.INFORMATIONAL);
notification.setSubject(new BomProcessingFailed(projectB, "", null, Bom.Format.CYCLONEDX, ""));

final var router = new NotificationRouter();
assertThat(router.resolveRules(notification)).isEmpty();

notification.setSubject(new BomProcessingFailed(projectA, "", null, Bom.Format.CYCLONEDX, ""));
assertThat(router.resolveRules(notification))
.satisfiesExactly(resolvedRule -> assertThat(resolvedRule.getName()).isEqualTo("Test Rule"));
}

@Test
public void testVexConsumedOrProcessedLimitedToProject() {
final Project projectA = qm.createProject("Project A", null, "1.0", null, null, null, true, false);
Expand Down

0 comments on commit 7a789d5

Please sign in to comment.