Skip to content

Commit 21d5e15

Browse files
authored
Merge pull request #6546 from nscuro/policy-notification
Include KEV flag in notifications
2 parents e4880f9 + 281086b commit 21d5e15

12 files changed

Lines changed: 111 additions & 30 deletions

File tree

apiserver/src/main/java/org/dependencytrack/notification/NotificationModelConverter.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,8 @@ public static Vulnerability convert(org.dependencytrack.model.Vulnerability vuln
250250
final Vulnerability.Builder builder = Vulnerability.newBuilder()
251251
.setUuid(vuln.getUuid().toString())
252252
.setVulnId(vuln.getVulnId())
253-
.setSource(vuln.getSource());
253+
.setSource(vuln.getSource())
254+
.setIsKev(vuln.isKev());
254255

255256
if (vuln.getAliases() != null) {
256257
getUniqueAliases(vuln).stream()

apiserver/src/main/java/org/dependencytrack/persistence/AnalysisQueryManager.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.dependencytrack.model.AnalysisState;
2929
import org.dependencytrack.model.Component;
3030
import org.dependencytrack.model.Vulnerability;
31+
import org.dependencytrack.model.VulnerabilityKey;
3132
import org.dependencytrack.notification.JdoNotificationEmitter;
3233
import org.dependencytrack.notification.NotificationModelConverter;
3334
import org.dependencytrack.persistence.command.MakeAnalysisCommand;
@@ -148,6 +149,12 @@ public long makeAnalysis(final MakeAnalysisCommand command) {
148149

149150
if (!command.options().contains(MakeAnalysisCommand.Option.OMIT_NOTIFICATION)
150151
&& (stateChanged || suppressionChanged)) {
152+
// NB: KEV is a transient field and must be computed ad-hoc.
153+
final boolean isKev = !super.getKev(
154+
List.of(VulnerabilityKey.of(
155+
analysis.getVulnerability()))).isEmpty();
156+
analysis.getVulnerability().setKev(isKev);
157+
151158
new JdoNotificationEmitter(this).emit(
152159
createVulnerabilityAnalysisDecisionChangeNotification(
153160
NotificationModelConverter.convert(analysis.getProject()),

apiserver/src/main/java/org/dependencytrack/persistence/QueryManager.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -809,6 +809,10 @@ public List<VulnerabilityAlias> getVulnerabilityAliases(Vulnerability vulnerabil
809809
return getVulnerabilityQueryManager().getVulnerabilityAliases(vulnerability);
810810
}
811811

812+
public Set<VulnerabilityKey> getKev(Collection<VulnerabilityKey> keys) {
813+
return getVulnerabilityQueryManager().getKev(keys);
814+
}
815+
812816
public Analysis getAnalysis(Component component, Vulnerability vulnerability) {
813817
return getAnalysisQueryManager().getAnalysis(component, vulnerability);
814818
}

apiserver/src/main/java/org/dependencytrack/persistence/VulnerabilityQueryManager.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,7 @@ public PaginatedResult getVulnerabilities() {
339339
return result;
340340
}
341341

342+
@Override
342343
public Set<VulnerabilityKey> getKev(Collection<VulnerabilityKey> keys) {
343344
if (keys.isEmpty()) {
344345
return Set.of();

apiserver/src/main/java/org/dependencytrack/persistence/jdbi/NotificationSubjectDao.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ SELECT ARRAY_AGG(DISTINCT t."NAME")
145145
, COALESCE(a."SEVERITY", v."SEVERITY") AS "vulnSeverity"
146146
, STRING_TO_ARRAY(v."CWES", ',') AS "vulnCwes"
147147
, JSONB_VULN_ALIASES(v."SOURCE", v."VULNID") AS "vulnAliasesJson"
148+
, <@sql.isKev vulnSource='v."SOURCE"' vulnId='v."VULNID"'/> AS "vulnIsKev"
148149
FROM UNNEST(:componentIds, :vulnerabilityIds)
149150
AS req(component_id, vulnerability_id)
150151
INNER JOIN "COMPONENTS_VULNERABILITIES" AS cv
@@ -259,6 +260,7 @@ SELECT ARRAY_AGG(DISTINCT t."NAME")
259260
, COALESCE(a."SEVERITY", v."SEVERITY") AS "vulnSeverity"
260261
, STRING_TO_ARRAY(v."CWES", ',') AS "vulnCwes"
261262
, JSONB_VULN_ALIASES(v."SOURCE", v."VULNID") AS "vulnAliasesJson"
263+
, <@sql.isKev vulnSource='v."SOURCE"' vulnId='v."VULNID"'/> AS "vulnIsKev"
262264
FROM "COMPONENT" AS c
263265
INNER JOIN "PROJECT" AS p
264266
ON p."ID" = c."PROJECT_ID"
@@ -405,6 +407,7 @@ SELECT ARRAY_AGG(DISTINCT t."NAME")
405407
, COALESCE(a."SEVERITY", v."SEVERITY") AS "vulnSeverity"
406408
, STRING_TO_ARRAY(v."CWES", ',') AS "vulnCwes"
407409
, JSONB_VULN_ALIASES(v."SOURCE", v."VULNID") AS "vulnAliasesJson"
410+
, <@sql.isKev vulnSource='v."SOURCE"' vulnId='v."VULNID"'/> AS "vulnIsKev"
408411
, req.analysis_state AS "vulnAnalysisState"
409412
, req.suppressed AS "isVulnAnalysisSuppressed"
410413
, format('/api/v1/vulnerability/source/%s/vuln/%s/projects', v."SOURCE", v."VULNID") AS "affectedProjectsApiUrl"
@@ -595,6 +598,7 @@ default Map<FindingKey, Vulnerability> getVulnsByFindingKey(Collection<FindingKe
595598
, COALESCE(a."SEVERITY", v."SEVERITY") AS "vulnSeverity"
596599
, STRING_TO_ARRAY(v."CWES", ',') AS "vulnCwes"
597600
, JSONB_VULN_ALIASES(v."SOURCE", v."VULNID") AS "vulnAliasesJson"
601+
, <@sql.isKev vulnSource='v."SOURCE"' vulnId='v."VULNID"'/> AS "vulnIsKev"
598602
FROM UNNEST(:componentIds, :vulnDbIds)
599603
AS t(component_id, vuln_db_id)
600604
INNER JOIN "VULNERABILITY" AS v

apiserver/src/main/java/org/dependencytrack/persistence/jdbi/mapping/NotificationVulnerabilityRowMapper.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ public Vulnerability map(final ResultSet rs, final StatementContext ctx) throws
6363
maybeSet(rs, "vulnSeverity", ResultSet::getString, builder::setSeverity);
6464
maybeSet(rs, "vulnCwes", NotificationVulnerabilityRowMapper::maybeConvertCwes, builder::addAllCwes);
6565
maybeSet(rs, "vulnAliasesJson", NotificationVulnerabilityRowMapper::maybeConvertAliases, builder::addAllAliases);
66+
maybeSet(rs, "vulnIsKev", ResultSet::getBoolean, builder::setIsKev);
6667
return builder.build();
6768
}
6869

apiserver/src/test/java/org/dependencytrack/persistence/jdbi/NotificationSubjectDaoTest.java

Lines changed: 74 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import com.google.protobuf.util.JsonFormat;
2222
import org.dependencytrack.PersistenceCapableTest;
23+
import org.dependencytrack.kevdatasource.api.KevAssertion;
2324
import org.dependencytrack.model.Analysis;
2425
import org.dependencytrack.model.AnalysisState;
2526
import org.dependencytrack.model.Component;
@@ -103,9 +104,24 @@ public void testGetForNewVulnerabilities() {
103104
vulnB.setSource(Vulnerability.Source.NVD);
104105
qm.persist(vulnB);
105106

106-
useJdbiTransaction(handle -> new VulnerabilityAliasDao(handle)
107-
.syncAssertions("TEST", new VulnerabilityKey("CVE-100", Vulnerability.Source.NVD),
108-
Set.of(new VulnerabilityKey("GHSA-100", Vulnerability.Source.GITHUB))));
107+
useJdbiTransaction(handle -> {
108+
new VulnerabilityAliasDao(handle)
109+
.syncAssertions(
110+
"TEST",
111+
new VulnerabilityKey("CVE-100", Vulnerability.Source.NVD),
112+
Set.of(new VulnerabilityKey("GHSA-100", Vulnerability.Source.GITHUB)));
113+
handle
114+
.attach(KevDao.class)
115+
.upsertBatch("cisa", List.of(
116+
new KevAssertion(
117+
"NVD",
118+
"CVE-100",
119+
null,
120+
null,
121+
null,
122+
null,
123+
null)));
124+
});
109125

110126
qm.addVulnerability(vulnA, component, "internal");
111127
qm.addVulnerability(vulnB, component, "internal");
@@ -125,7 +141,7 @@ public void testGetForNewVulnerabilities() {
125141
.withMatcher("projectUuid", equalTo(project.getUuid().toString()))
126142
.withMatcher("componentUuid", equalTo(component.getUuid().toString()))
127143
.withMatcher("vulnUuid", equalTo(vulnA.getUuid().toString()))
128-
.isEqualTo("""
144+
.isEqualTo(/* language=JSON */ """
129145
{
130146
"affectedProjects": [
131147
{
@@ -193,7 +209,8 @@ public void testGetForNewVulnerabilities() {
193209
"vulnId": "CVE-100",
194210
"cvssV2Vector": "(AV:N/AC:M/Au:S/C:P/I:P/A:P)",
195211
"cvssV3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H",
196-
"owaspRRVector": "(SL:5/M:5/O:2/S:9/ED:4/EE:2/A:7/ID:2/LC:2/LI:2/LAV:7/LAC:9/FD:3/RD:5/NC:0/PV:7)"
212+
"owaspRRVector": "(SL:5/M:5/O:2/S:9/ED:4/EE:2/A:7/ID:2/LC:2/LI:2/LAV:7/LAC:9/FD:3/RD:5/NC:0/PV:7)",
213+
"isKev": true
197214
}
198215
}
199216
"""));
@@ -247,7 +264,7 @@ public void testGetForNewVulnerabilityWithAnalysisRatingOverwrite() throws Excep
247264
.withMatcher("projectUuid", equalTo(project.getUuid().toString()))
248265
.withMatcher("componentUuid", equalTo(component.getUuid().toString()))
249266
.withMatcher("vulnUuid", equalTo(vuln.getUuid().toString()))
250-
.isEqualTo("""
267+
.isEqualTo(/* language=JSON */ """
251268
{
252269
"component": {
253270
"uuid": "${json-unit.matches:componentUuid}",
@@ -270,7 +287,8 @@ public void testGetForNewVulnerabilityWithAnalysisRatingOverwrite() throws Excep
270287
"severity": "CRITICAL",
271288
"cvssV2Vector": "",
272289
"cvssV3Vector": "cvssV3VectorOverwrite",
273-
"owaspRRVector": "owaspRrVector"
290+
"owaspRRVector": "owaspRrVector",
291+
"isKev": false
274292
},
275293
"affectedProjects": [
276294
{
@@ -332,11 +350,25 @@ public void testGetForNewVulnerableDependency() throws Exception {
332350
vulnB.setSource(Vulnerability.Source.NVD);
333351
qm.persist(vulnB);
334352

335-
useJdbiTransaction(handle -> new VulnerabilityAliasDao(handle)
336-
.syncAssertions(
337-
"TEST",
338-
new VulnerabilityKey("CVE-100", Vulnerability.Source.NVD),
339-
Set.of(new VulnerabilityKey("GHSA-100", Vulnerability.Source.GITHUB))));
353+
useJdbiTransaction(handle -> {
354+
new VulnerabilityAliasDao(handle)
355+
.syncAssertions(
356+
"TEST",
357+
new VulnerabilityKey("CVE-100", Vulnerability.Source.NVD),
358+
Set.of(new VulnerabilityKey("GHSA-100", Vulnerability.Source.GITHUB)));
359+
360+
handle
361+
.attach(KevDao.class)
362+
.upsertBatch("cisa", List.of(
363+
new KevAssertion(
364+
"NVD",
365+
"CVE-100",
366+
null,
367+
null,
368+
null,
369+
null,
370+
null)));
371+
});
340372

341373
qm.addVulnerability(vulnA, component, "internal");
342374
qm.addVulnerability(vulnB, component, "internal");
@@ -355,7 +387,7 @@ public void testGetForNewVulnerableDependency() throws Exception {
355387
.withMatcher("projectUuid", equalTo(project.getUuid().toString()))
356388
.withMatcher("componentUuid", equalTo(component.getUuid().toString()))
357389
.withMatcher("vulnUuid", equalTo(vulnA.getUuid().toString()))
358-
.isEqualTo("""
390+
.isEqualTo(/* language=JSON */ """
359391
{
360392
"component": {
361393
"uuid": "${json-unit.matches:componentUuid}",
@@ -410,7 +442,8 @@ public void testGetForNewVulnerableDependency() throws Exception {
410442
"owaspRRVector": "(SL:5/M:5/O:2/S:9/ED:4/EE:2/A:7/ID:2/LC:2/LI:2/LAV:7/LAC:9/FD:3/RD:5/NC:0/PV:7)",
411443
"aliases": [
412444
{"vulnId": "GHSA-100", "source": "GITHUB"}
413-
]
445+
],
446+
"isKev": true
414447
}
415448
]
416449
}
@@ -465,7 +498,7 @@ public void testGetForNewVulnerableDependencyWithAnalysisRatingOverwrite() throw
465498
.withMatcher("projectUuid", equalTo(project.getUuid().toString()))
466499
.withMatcher("componentUuid", equalTo(component.getUuid().toString()))
467500
.withMatcher("vulnUuid", equalTo(vuln.getUuid().toString()))
468-
.isEqualTo("""
501+
.isEqualTo(/* language=JSON */ """
469502
{
470503
"component": {
471504
"uuid": "${json-unit.matches:componentUuid}",
@@ -489,7 +522,8 @@ public void testGetForNewVulnerableDependencyWithAnalysisRatingOverwrite() throw
489522
"severity": "CRITICAL",
490523
"cvssV2Vector": "",
491524
"cvssV3Vector": "cvssV3VectorOverwrite",
492-
"owaspRRVector": "owaspRrVector"
525+
"owaspRRVector": "owaspRrVector",
526+
"isKev": false
493527
}
494528
]
495529
}
@@ -540,9 +574,25 @@ public void testGetForProjectAuditChange() {
540574
vulnA.setCwes(List.of(666, 777));
541575
qm.persist(vulnA);
542576

543-
useJdbiTransaction(handle -> new VulnerabilityAliasDao(handle)
544-
.syncAssertions("TEST", new VulnerabilityKey("CVE-100", Vulnerability.Source.NVD),
545-
Set.of(new VulnerabilityKey("GHSA-100", Vulnerability.Source.GITHUB))));
577+
useJdbiTransaction(handle -> {
578+
new VulnerabilityAliasDao(handle)
579+
.syncAssertions(
580+
"TEST",
581+
new VulnerabilityKey("CVE-100", Vulnerability.Source.NVD),
582+
Set.of(new VulnerabilityKey("GHSA-100", Vulnerability.Source.GITHUB)));
583+
584+
handle
585+
.attach(KevDao.class)
586+
.upsertBatch("cisa", List.of(
587+
new KevAssertion(
588+
"NVD",
589+
"CVE-100",
590+
null,
591+
null,
592+
null,
593+
null,
594+
null)));
595+
});
546596

547597
qm.addVulnerability(vulnA, component, "internal");
548598

@@ -565,7 +615,7 @@ public void testGetForProjectAuditChange() {
565615
.withMatcher("projectUuid", equalTo(project.getUuid().toString()))
566616
.withMatcher("componentUuid", equalTo(component.getUuid().toString()))
567617
.withMatcher("vulnUuid", equalTo(vulnA.getUuid().toString()))
568-
.isEqualTo("""
618+
.isEqualTo(/* language=JSON */ """
569619
{
570620
"component": {
571621
"uuid": "${json-unit.matches:componentUuid}",
@@ -622,7 +672,8 @@ public void testGetForProjectAuditChange() {
622672
],
623673
"cvssV2Vector": "(AV:N/AC:M/Au:S/C:P/I:P/A:P)",
624674
"cvssV3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H",
625-
"owaspRRVector": "(SL:5/M:5/O:2/S:9/ED:4/EE:2/A:7/ID:2/LC:2/LI:2/LAV:7/LAC:9/FD:3/RD:5/NC:0/PV:7)"
675+
"owaspRRVector": "(SL:5/M:5/O:2/S:9/ED:4/EE:2/A:7/ID:2/LC:2/LI:2/LAV:7/LAC:9/FD:3/RD:5/NC:0/PV:7)",
676+
"isKev": true
626677
},
627678
"analysis": {
628679
"component": {
@@ -680,7 +731,8 @@ public void testGetForProjectAuditChange() {
680731
],
681732
"cvssV2Vector": "(AV:N/AC:M/Au:S/C:P/I:P/A:P)",
682733
"cvssV3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H",
683-
"owaspRRVector": "(SL:5/M:5/O:2/S:9/ED:4/EE:2/A:7/ID:2/LC:2/LI:2/LAV:7/LAC:9/FD:3/RD:5/NC:0/PV:7)"
734+
"owaspRRVector": "(SL:5/M:5/O:2/S:9/ED:4/EE:2/A:7/ID:2/LC:2/LI:2/LAV:7/LAC:9/FD:3/RD:5/NC:0/PV:7)",
735+
"isKev": true
684736
},
685737
"state": "NOT_AFFECTED",
686738
"suppressed": false

e2e/src/test/java/org/dependencytrack/e2e/BomUploadProcessingE2ET.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,8 @@ private void verifyWebhookNotification() {
239239
"source": "INTERNAL",
240240
"cvssv3" : "${json-unit.any-number}",
241241
"severity": "CRITICAL",
242-
"cvssV3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H"
242+
"cvssV3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H",
243+
"isKev": false
243244
},
244245
"affectedProjectsReference": {
245246
"apiUri": "/api/v1/vulnerability/source/INTERNAL/vuln/INT-123/projects",

e2e/src/test/java/org/dependencytrack/e2e/VulnerabilityPolicyE2ET.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -454,7 +454,8 @@ private void verifyNewVulnerableDependencyNotification() {
454454
"source": "INTERNAL",
455455
"cvssv3": 7.1,
456456
"severity": "HIGH",
457-
"cvssV3Vector" : "CVSS:3.0/AV:N/AC:H/PR:L/UI:R/S:U/C:H/I:H/A:H"
457+
"cvssV3Vector" : "CVSS:3.0/AV:N/AC:H/PR:L/UI:R/S:U/C:H/I:H/A:H",
458+
"isKev": false
458459
},
459460
{
460461
"uuid": "${json-unit.any-string}",
@@ -463,7 +464,8 @@ private void verifyNewVulnerableDependencyNotification() {
463464
"cvssv2": 2.6,
464465
"cvssv3": 9.8,
465466
"severity": "LOW",
466-
"cvssV3Vector": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"
467+
"cvssV3Vector": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
468+
"isKev": false
467469
}
468470
]
469471
}
@@ -512,7 +514,8 @@ private void verifyNewVulnerabilityNotifications() {
512514
"source": "INTERNAL",
513515
"cvssv3": 7.1,
514516
"severity": "HIGH",
515-
"cvssV3Vector" : "CVSS:3.0/AV:N/AC:H/PR:L/UI:R/S:U/C:H/I:H/A:H"
517+
"cvssV3Vector" : "CVSS:3.0/AV:N/AC:H/PR:L/UI:R/S:U/C:H/I:H/A:H",
518+
"isKev": false
516519
},
517520
"analysisTrigger" : "ANALYSIS_TRIGGER_BOM_UPLOAD",
518521
"vulnerabilityAnalysisLevel": "BOM_UPLOAD_ANALYSIS",
@@ -574,7 +577,8 @@ private void verifyNewVulnerabilityNotifications() {
574577
"cvssv2": 2.6,
575578
"cvssv3": 9.8,
576579
"severity": "LOW",
577-
"cvssV3Vector": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"
580+
"cvssV3Vector": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
581+
"isKev": false
578582
},
579583
"analysisTrigger" : "ANALYSIS_TRIGGER_BOM_UPLOAD",
580584
"vulnerabilityAnalysisLevel": "BOM_UPLOAD_ANALYSIS",

notification/api/src/main/java/org/dependencytrack/notification/api/TestNotificationFactory.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,7 @@ private static Vulnerability createVulnerability() {
446446
.addCwes(Vulnerability.Cwe.newBuilder()
447447
.setCweId(777)
448448
.setName("Regular Expression without Anchors"))
449+
.setIsKev(false)
449450
.build();
450451
}
451452

0 commit comments

Comments
 (0)