Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
sahibamittal committed Dec 22, 2023
1 parent b680ddc commit 59274ec
Show file tree
Hide file tree
Showing 4 changed files with 360 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
import static org.dependencytrack.persistence.jdbi.JdbiFactory.jdbi;
import static org.dependencytrack.proto.notification.v1.Group.GROUP_NEW_VULNERABILITY;
import static org.dependencytrack.proto.notification.v1.Group.GROUP_NEW_VULNERABLE_DEPENDENCY;
import static org.dependencytrack.proto.notification.v1.Group.GROUP_PROJECT_AUDIT_CHANGE;
import static org.dependencytrack.proto.notification.v1.Level.LEVEL_INFORMATIONAL;
import static org.dependencytrack.proto.notification.v1.Scope.SCOPE_PORTFOLIO;
import static org.dependencytrack.proto.vulnanalysis.v1.ScanStatus.SCAN_STATUS_FAILED;
Expand Down Expand Up @@ -372,13 +373,14 @@ private List<Vulnerability> synchronizeFindingsAndAnalyses(final QueryManager qm
.toList();
dao.createFindingAttributions(findingAttributions);

return maybeApplyPolicyAnalyses(dao, component, vulns, newFindingVulnIds, policiesByVulnUuid);
return maybeApplyPolicyAnalyses(qm, dao, component, vulns, newFindingVulnIds, policiesByVulnUuid);
});
}

/**
* Apply analyses of matched {@link VulnerabilityPolicy}s. Do nothing when no policies matched.
*
* @param qm
* @param dao The {@link Dao} to use for persistence operations
* @param component The {@link Component} to apply analyses for
* @param vulns The {@link Vulnerability}s identified for the {@link Component}
Expand All @@ -387,7 +389,7 @@ private List<Vulnerability> synchronizeFindingsAndAnalyses(final QueryManager qm
* @return A {@link List} of {@link Vulnerability}s, that were not previously associated with the {@link Component},
* and which have not been suppressed via {@link VulnerabilityPolicy}.
*/
private List<Vulnerability> maybeApplyPolicyAnalyses(final Dao dao, final Component component, final Collection<Vulnerability> vulns,
private List<Vulnerability> maybeApplyPolicyAnalyses(QueryManager qm, final Dao dao, final Component component, final Collection<Vulnerability> vulns,
final List<Long> newFindingVulnIds, final Map<UUID, VulnerabilityPolicy> policiesByVulnUuid) {
// Unless we have any matching vulnerability policies, there's nothing to do!
if (policiesByVulnUuid.isEmpty()) {
Expand All @@ -411,6 +413,7 @@ private List<Vulnerability> maybeApplyPolicyAnalyses(final Dao dao, final Compon

final var analysesToCreateOrUpdate = new ArrayList<Analysis>();
final var analysisCommentsByVulnId = new MultivaluedHashMap<Long, AnalysisComment>();

for (final Map.Entry<UUID, VulnerabilityPolicy> vulnUuidAndPolicy : policiesByVulnUuid.entrySet()) {
final Vulnerability vuln = vulnByUuid.get(vulnUuidAndPolicy.getKey());
final VulnerabilityPolicy policy = vulnUuidAndPolicy.getValue();
Expand All @@ -421,7 +424,6 @@ private List<Vulnerability> maybeApplyPolicyAnalyses(final Dao dao, final Compon
LOGGER.warn("Unable to apply policy %s as it was found to be invalid".formatted(policy.name()), e);
continue;
}

final Analysis existingAnalysis = existingAnalyses.get(vuln.getUuid());
if (existingAnalysis == null) {
policyAnalysis.setComponentId(component.id());
Expand Down Expand Up @@ -595,10 +597,6 @@ private List<Vulnerability> maybeApplyPolicyAnalyses(final Dao dao, final Compon

if (!analysesToCreateOrUpdate.isEmpty()) {
final List<CreatedAnalysis> createdAnalyses = dao.createOrUpdateAnalyses(analysesToCreateOrUpdate);
// TODO: Construct notifications for PROJECT_AUDIT_CHANGE, but do not dispatch them here!
// They should be dispatched together with NEW_VULNERABILITY and NEW_VULNERABLE_DEPENDENCY
// notifications, AFTER this database transaction completed successfully.

// Comments for new analyses do not have an analysis ID set yet, as that ID was not known prior
// to inserting the respective analysis record. Enrich comments with analysis IDs now that we know them.
for (final CreatedAnalysis createdAnalysis : createdAnalyses) {
Expand All @@ -607,8 +605,10 @@ private List<Vulnerability> maybeApplyPolicyAnalyses(final Dao dao, final Compon
.map(comment -> new AnalysisComment(createdAnalysis.id(), comment.comment(), comment.commenter()))
.toList());
}

dao.createAnalysisComments(analysisCommentsByVulnId.values().stream().flatMap(Collection::stream).toList());

// dispatch PROJECT_AUDIT_CHANGE notifications
maybeSendProjectAuditChangeNotification(qm, component, createdAnalyses.stream().map(CreatedAnalysis::vulnId).toList());
}

return vulnById.entrySet().stream()
Expand Down Expand Up @@ -668,6 +668,31 @@ private void maybeSendNotifications(final QueryManager qm, final Component compo
}
}


private void maybeSendProjectAuditChangeNotification(final QueryManager qm, final Component component, List<Long> vulnIds) {
if (vulnIds.isEmpty()) {
return;
}
final Timestamp notificationTimestamp = Timestamps.now();
final var notifications = new ArrayList<org.dependencytrack.proto.notification.v1.Notification>();
jdbi(qm).useExtension(NotificationSubjectDao.class, dao -> {
dao.getForProjectAuditChange(component.uuid(), vulnIds).stream()
.map(subject -> org.dependencytrack.proto.notification.v1.Notification.newBuilder()
.setScope(SCOPE_PORTFOLIO)
.setGroup(GROUP_PROJECT_AUDIT_CHANGE)
.setLevel(LEVEL_INFORMATIONAL)
.setTimestamp(notificationTimestamp)
.setTitle(generateNotificationTitle(NotificationConstants.Title.NEW_VULNERABILITY, subject.getProject())) // TODO
.setContent(generateNotificationContent(subject.getVulnerability())) // TODO
.setSubject(Any.pack(subject))
.build())
.forEach(notifications::add);
});
for (final org.dependencytrack.proto.notification.v1.Notification notification : notifications) {
eventDispatcher.dispatchAsync(component.projectUuid().toString(), notification);
}
}

private boolean canUpdateVulnerability(final Vulnerability vuln, final Scanner scanner) {
var canUpdate = true;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
import org.dependencytrack.persistence.jdbi.mapping.NotificationProjectRowMapper;
import org.dependencytrack.persistence.jdbi.mapping.NotificationSubjectNewVulnerabilityRowMapper;
import org.dependencytrack.persistence.jdbi.mapping.NotificationSubjectNewVulnerableDependencyRowReducer;
import org.dependencytrack.persistence.jdbi.mapping.NotificationSubjectProjectAuditChangeRowMapper;
import org.dependencytrack.persistence.jdbi.mapping.NotificationVulnerabilityRowMapper;
import org.dependencytrack.proto.notification.v1.NewVulnerabilitySubject;
import org.dependencytrack.proto.notification.v1.NewVulnerableDependencySubject;
import org.dependencytrack.proto.notification.v1.VulnerabilityAnalysisDecisionChangeSubject;
import org.jdbi.v3.sqlobject.config.RegisterRowMapper;
import org.jdbi.v3.sqlobject.config.RegisterRowMappers;
import org.jdbi.v3.sqlobject.statement.SqlQuery;
Expand Down Expand Up @@ -235,4 +237,109 @@ LEFT JOIN LATERAL (
@UseRowReducer(NotificationSubjectNewVulnerableDependencyRowReducer.class)
Optional<NewVulnerableDependencySubject> getForNewVulnerableDependency(final UUID componentUuid);

@SqlQuery("""
SELECT
"C"."UUID" AS "componentUuid",
"C"."GROUP" AS "componentGroup",
"C"."NAME" AS "componentName",
"C"."VERSION" AS "componentVersion",
"C"."PURL" AS "componentPurl",
"C"."MD5" AS "componentMd5",
"C"."SHA1" AS "componentSha1",
"C"."SHA_256" AS "componentSha256",
"C"."SHA_512" AS "componentSha512",
"P"."UUID" AS "projectUuid",
"P"."NAME" AS "projectName",
"P"."VERSION" AS "projectVersion",
"P"."DESCRIPTION" AS "projectDescription",
"P"."PURL" AS "projectPurl",
(SELECT
ARRAY_AGG(DISTINCT "T"."NAME")
FROM
"TAG" AS "T"
INNER JOIN
"PROJECTS_TAGS" AS "PT" ON "PT"."TAG_ID" = "T"."ID"
WHERE
"PT"."PROJECT_ID" = "P"."ID"
) AS "projectTags",
"V"."UUID" AS "vulnUuid",
"V"."VULNID" AS "vulnId",
"V"."SOURCE" AS "vulnSource",
"V"."TITLE" AS "vulnTitle",
"V"."SUBTITLE" AS "vulnSubTitle",
"V"."DESCRIPTION" AS "vulnDescription",
"V"."RECOMMENDATION" AS "vulnRecommendation",
CASE
WHEN "A"."SEVERITY" IS NOT NULL THEN "A"."CVSSV2SCORE"
ELSE "V"."CVSSV2BASESCORE"
END AS "vulnCvssV2BaseScore",
CASE
WHEN "A"."SEVERITY" IS NOT NULL THEN "A"."CVSSV3SCORE"
ELSE "V"."CVSSV3BASESCORE"
END AS "vulnCvssV3BaseScore",
-- TODO: Analysis only has a single score, but OWASP RR defines multiple.
-- How to handle this?
CASE
WHEN "A"."SEVERITY" IS NOT NULL THEN "A"."OWASPSCORE"
ELSE "V"."OWASPRRBUSINESSIMPACTSCORE"
END AS "vulnOwaspRrBusinessImpactScore",
CASE
WHEN "A"."SEVERITY" IS NOT NULL THEN "A"."OWASPSCORE"
ELSE "V"."OWASPRRLIKELIHOODSCORE"
END AS "vulnOwaspRrLikelihoodScore",
CASE
WHEN "A"."SEVERITY" IS NOT NULL THEN "A"."OWASPSCORE"
ELSE "V"."OWASPRRTECHNICALIMPACTSCORE"
END AS "vulnOwaspRrTechnicalImpactScore",
"CALC_SEVERITY"(
"V"."SEVERITY",
"A"."SEVERITY",
"V"."CVSSV3BASESCORE",
"V"."CVSSV2BASESCORE"
) AS "vulnSeverity",
STRING_TO_ARRAY("V"."CWES", ',') AS "vulnCwes",
"vulnAliasesJson",
"A"."SUPPRESSED" AS "isVulnAnalysisSuppressed",
"A"."STATE" AS "vulnAnalysisState",
'/api/v1/vulnerability/source/' || "V"."SOURCE" || '/vuln/' || "V"."VULNID" || '/projects' AS "affectedProjectsApiUrl",
'/vulnerabilities/' || "V"."SOURCE" || '/' || "V"."VULNID" || '/affectedProjects' AS "affectedProjectsFrontendUrl"
FROM
"COMPONENT" AS "C"
INNER JOIN
"PROJECT" AS "P" ON "P"."ID" = "C"."PROJECT_ID"
INNER JOIN
"COMPONENTS_VULNERABILITIES" AS "CV" ON "CV"."COMPONENT_ID" = "C"."ID"
INNER JOIN
"VULNERABILITY" AS "V" ON "V"."ID" = "CV"."VULNERABILITY_ID"
LEFT JOIN
"ANALYSIS" AS "A" ON "A"."COMPONENT_ID" = "C"."ID" AND "A"."VULNERABILITY_ID" = "V"."ID"
LEFT JOIN LATERAL (
SELECT
CAST(JSONB_AGG(DISTINCT JSONB_STRIP_NULLS(JSONB_BUILD_OBJECT(
'cveId', "VA"."CVE_ID",
'ghsaId', "VA"."GHSA_ID",
'gsdId', "VA"."GSD_ID",
'internalId', "VA"."INTERNAL_ID",
'osvId', "VA"."OSV_ID",
'sonatypeId', "VA"."SONATYPE_ID",
'snykId', "VA"."SNYK_ID",
'vulnDbId', "VA"."VULNDB_ID"
))) AS TEXT) AS "vulnAliasesJson"
FROM
"VULNERABILITYALIAS" AS "VA"
WHERE
("V"."SOURCE" = 'NVD' AND "VA"."CVE_ID" = "V"."VULNID")
OR ("V"."SOURCE" = 'GITHUB' AND "VA"."GHSA_ID" = "V"."VULNID")
OR ("V"."SOURCE" = 'GSD' AND "VA"."GSD_ID" = "V"."VULNID")
OR ("V"."SOURCE" = 'INTERNAL' AND "VA"."INTERNAL_ID" = "V"."VULNID")
OR ("V"."SOURCE" = 'OSV' AND "VA"."OSV_ID" = "V"."VULNID")
OR ("V"."SOURCE" = 'SONATYPE' AND "VA"."SONATYPE_ID" = "V"."VULNID")
OR ("V"."SOURCE" = 'SNYK' AND "VA"."SNYK_ID" = "V"."VULNID")
OR ("V"."SOURCE" = 'VULNDB' AND "VA"."VULNDB_ID" = "V"."VULNID")
) AS "vulnAliases" ON TRUE
WHERE
"C"."UUID" = (:componentUuid)::TEXT AND "V"."ID" = ANY((:vulnIds)::BIGINT[])
""")
@RegisterRowMapper(NotificationSubjectProjectAuditChangeRowMapper.class)
List<VulnerabilityAnalysisDecisionChangeSubject> getForProjectAuditChange(final UUID componentUuid, final Collection<Long> vulnIds);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.dependencytrack.persistence.jdbi.mapping;

import org.dependencytrack.proto.notification.v1.Component;
import org.dependencytrack.proto.notification.v1.Project;
import org.dependencytrack.proto.notification.v1.Vulnerability;
import org.dependencytrack.proto.notification.v1.VulnerabilityAnalysis;
import org.dependencytrack.proto.notification.v1.VulnerabilityAnalysisDecisionChangeSubject;
import org.jdbi.v3.core.mapper.RowMapper;
import org.jdbi.v3.core.statement.StatementContext;

import java.sql.ResultSet;
import java.sql.SQLException;

import static org.dependencytrack.persistence.jdbi.mapping.RowMapperUtil.maybeSet;

public class NotificationSubjectProjectAuditChangeRowMapper implements RowMapper<VulnerabilityAnalysisDecisionChangeSubject> {

@Override
public VulnerabilityAnalysisDecisionChangeSubject map(final ResultSet rs, final StatementContext ctx) throws SQLException {
final RowMapper<Component> componentRowMapper = ctx.findRowMapperFor(Component.class).orElseThrow();
final RowMapper<Project> projectRowMapper = ctx.findRowMapperFor(Project.class).orElseThrow();
final RowMapper<Vulnerability> vulnRowMapper = ctx.findRowMapperFor(Vulnerability.class).orElseThrow();
final VulnerabilityAnalysis.Builder vulnAnalysisBuilder = VulnerabilityAnalysis.newBuilder()
.setComponent(componentRowMapper.map(rs, ctx))
.setProject(projectRowMapper.map(rs, ctx))
.setVulnerability(vulnRowMapper.map(rs, ctx));
maybeSet(rs, "vulnAnalysisState", ResultSet::getString, vulnAnalysisBuilder::setState);
maybeSet(rs, "isVulnAnalysisSuppressed", ResultSet::getBoolean, vulnAnalysisBuilder::setSuppressed);
final VulnerabilityAnalysisDecisionChangeSubject.Builder builder = VulnerabilityAnalysisDecisionChangeSubject.newBuilder()
.setComponent(componentRowMapper.map(rs, ctx))
.setProject(projectRowMapper.map(rs, ctx))
.setVulnerability(vulnRowMapper.map(rs, ctx))
.setAnalysis(vulnAnalysisBuilder);
return builder.build();
}

}
Loading

0 comments on commit 59274ec

Please sign in to comment.