Skip to content

Commit bbd3de8

Browse files
committed
Add KEV dimension to time series metrics
Tracks the number of KEV findings on the component-, project-, and portfolio-level. Signed-off-by: nscuro <nscuro@protonmail.com>
1 parent 21d5e15 commit bbd3de8

14 files changed

Lines changed: 370 additions & 3 deletions

File tree

api/src/main/openapi/resources/components/schemas/dependency-metrics.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ properties:
3131
unassigned:
3232
type: integer
3333
format: int32
34+
kev:
35+
type: integer
36+
format: int32
3437
vulnerabilities:
3538
type: integer
3639
format: int32

apiserver/src/main/java/org/dependencytrack/model/DependencyMetrics.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ public class DependencyMetrics implements Serializable {
4848
private int medium;
4949
private int low;
5050
private int unassigned;
51+
private int kev;
5152
private int vulnerabilities;
5253
private int suppressed;
5354
private int findingsTotal;
@@ -134,6 +135,14 @@ public void setUnassigned(int unassigned) {
134135
this.unassigned = unassigned;
135136
}
136137

138+
public int getKev() {
139+
return kev;
140+
}
141+
142+
public void setKev(int kev) {
143+
this.kev = kev;
144+
}
145+
137146
public int getVulnerabilities() {
138147
return vulnerabilities;
139148
}

apiserver/src/main/java/org/dependencytrack/model/PortfolioMetrics.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public class PortfolioMetrics implements Serializable {
4242
private int medium;
4343
private int low;
4444
private int unassigned;
45+
private int kev;
4546
private int vulnerabilities;
4647
private int projects;
4748
private int vulnerableProjects;
@@ -116,6 +117,14 @@ public void setUnassigned(int unassigned) {
116117
this.unassigned = unassigned;
117118
}
118119

120+
public int getKev() {
121+
return kev;
122+
}
123+
124+
public void setKev(int kev) {
125+
this.kev = kev;
126+
}
127+
119128
public int getVulnerabilities() {
120129
return vulnerabilities;
121130
}

apiserver/src/main/java/org/dependencytrack/model/ProjectMetrics.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public class ProjectMetrics implements Serializable {
4545
private int medium;
4646
private int low;
4747
private int unassigned;
48+
private int kev;
4849
private int vulnerabilities;
4950
private int vulnerableComponents;
5051
private int components;
@@ -125,6 +126,14 @@ public void setUnassigned(int unassigned) {
125126
this.unassigned = unassigned;
126127
}
127128

129+
public int getKev() {
130+
return kev;
131+
}
132+
133+
public void setKev(int kev) {
134+
this.kev = kev;
135+
}
136+
128137
public int getVulnerabilities() {
129138
return vulnerabilities;
130139
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ SELECT COUNT(DISTINCT "PROJECT_ID") AS projects
116116
, SUM("FINDINGS_TOTAL") AS findings_total
117117
, SUM("FINDINGS_UNAUDITED") AS findings_unaudited
118118
, SUM("HIGH") AS high
119+
, SUM("KEV") AS kev
119120
, SUM("RISKSCORE") as inherited_risk_score
120121
, SUM("LOW") AS low
121122
, SUM("MEDIUM") AS medium
@@ -149,6 +150,7 @@ SELECT COALESCE(dm.components, 0) AS components
149150
, COALESCE(dm.findings_unaudited, 0) AS findings_unaudited
150151
, date_range.metrics_date AS first_occurrence
151152
, COALESCE(dm.high, 0) AS high
153+
, COALESCE(dm.kev, 0) AS kev
152154
, COALESCE(dm.inherited_risk_score, 0) AS inherited_risk_score
153155
, date_range.metrics_date AS last_occurrence
154156
, COALESCE(dm.low, 0) AS low
@@ -361,6 +363,7 @@ daily_metrics AS(
361363
, SUM("FINDINGS_TOTAL") AS findings_total
362364
, SUM("FINDINGS_UNAUDITED") AS findings_unaudited
363365
, SUM("HIGH") AS high
366+
, SUM("KEV") AS kev
364367
, SUM("LOW") AS low
365368
, SUM("MEDIUM") AS medium
366369
, SUM("POLICYVIOLATIONS_AUDITED") AS policy_violations_audited
@@ -393,6 +396,7 @@ SELECT COALESCE(dm.components, 0) AS components
393396
, COALESCE(dm.findings_unaudited, 0) AS "findingsUnaudited"
394397
, date_range.metrics_date AS "firstOccurrence"
395398
, COALESCE(dm.high, 0) AS high
399+
, COALESCE(dm.kev, 0) AS kev
396400
, COALESCE(dm.inherited_risk_score, 0) AS "inheritedRiskScore"
397401
, date_range.metrics_date AS "lastOccurrence"
398402
, COALESCE(dm.low, 0) AS low
@@ -493,6 +497,7 @@ OR EXISTS (
493497
, COALESCE(SUM(pm."COMPONENTS"), 0) AS components
494498
, COALESCE(SUM(pm."CRITICAL"), 0) AS critical
495499
, COALESCE(SUM(pm."HIGH"), 0) AS high
500+
, COALESCE(SUM(pm."KEV"), 0) AS kev
496501
, COALESCE(SUM(pm."LOW"), 0) AS low
497502
, COALESCE(SUM(pm."MEDIUM"), 0) AS medium
498503
, COALESCE(SUM(pm."UNASSIGNED_SEVERITY"), 0) AS unassigned

apiserver/src/main/java/org/dependencytrack/resources/v2/mapping/ModelMapper.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ public static DependencyMetrics mapDependencyMetrics(org.dependencytrack.model.D
8282
.unassigned(metrics.getUnassigned())
8383
.vulnerabilities(metrics.getVulnerabilities())
8484
.suppressed(metrics.getSuppressed())
85+
.kev(metrics.getKev())
8586
.findingsTotal(metrics.getFindingsTotal())
8687
.findingsAudited(metrics.getFindingsAudited())
8788
.findingsUnaudited(metrics.getFindingsUnaudited())

apiserver/src/test/java/org/dependencytrack/TestDatabaseEventListener.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,9 @@ public void onTablesTruncated() {
6161
DO $$
6262
DECLARE
6363
partition_name TEXT;
64-
today_partition_pattern TEXT := FORMAT('^(PROJECT|DEPENDENCY)METRICS_%s', TO_CHAR(CURRENT_DATE, 'YYYYMMDD'));
65-
tomorrow_partition_pattern TEXT := FORMAT('^(PROJECT|DEPENDENCY)METRICS_%s', TO_CHAR(CURRENT_DATE + 1, 'YYYYMMDD'));
64+
today_utc DATE := CAST(CURRENT_TIMESTAMP AT TIME ZONE 'UTC' AS DATE);
65+
today_partition_pattern TEXT := FORMAT('^(PROJECT|DEPENDENCY)METRICS_%s', TO_CHAR(today_utc, 'YYYYMMDD'));
66+
tomorrow_partition_pattern TEXT := FORMAT('^(PROJECT|DEPENDENCY)METRICS_%s', TO_CHAR(today_utc + 1, 'YYYYMMDD'));
6667
BEGIN
6768
FOR partition_name IN
6869
SELECT tablename

apiserver/src/test/java/org/dependencytrack/metrics/UpdatePortfolioMetricsWorkflowTest.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.dependencytrack.dex.engine.api.request.CreateTaskQueueRequest;
2828
import org.dependencytrack.dex.engine.api.request.CreateWorkflowRunRequest;
2929
import org.dependencytrack.dex.testing.WorkflowTestExtension;
30+
import org.dependencytrack.kevdatasource.api.KevAssertion;
3031
import org.dependencytrack.model.AnalysisState;
3132
import org.dependencytrack.model.Component;
3233
import org.dependencytrack.model.Policy;
@@ -40,6 +41,7 @@
4041
import org.dependencytrack.model.Vulnerability;
4142
import org.dependencytrack.persistence.command.MakeAnalysisCommand;
4243
import org.dependencytrack.persistence.command.MakeViolationAnalysisCommand;
44+
import org.dependencytrack.persistence.jdbi.KevDao;
4345
import org.dependencytrack.persistence.jdbi.MetricsDao;
4446
import org.dependencytrack.persistence.jdbi.MetricsTestDao;
4547
import org.dependencytrack.proto.internal.workflow.v1.FetchProjectMetricsUpdateCandidatesRes;
@@ -165,6 +167,18 @@ void shouldUpdateMetricsVulnerabilities() {
165167
vuln.setSeverity(Severity.HIGH);
166168
vuln = qm.createVulnerability(vuln);
167169

170+
useJdbiTransaction(handle -> handle
171+
.attach(KevDao.class)
172+
.upsertBatch("cisa", List.of(
173+
new KevAssertion(
174+
"INTERNAL",
175+
"INTERNAL-001",
176+
null,
177+
null,
178+
null,
179+
null,
180+
null))));
181+
168182
// Create a project with an unaudited vulnerability.
169183
var projectUnaudited = new Project();
170184
projectUnaudited.setName("acme-app-a");
@@ -218,6 +232,7 @@ void shouldUpdateMetricsVulnerabilities() {
218232
assertThat(metrics.getMedium()).isZero();
219233
assertThat(metrics.getLow()).isZero();
220234
assertThat(metrics.getUnassigned()).isZero();
235+
assertThat(metrics.getKev()).isEqualTo(2); // One is suppressed
221236
assertThat(metrics.getVulnerabilities()).isEqualTo(2); // One is suppressed
222237
assertThat(metrics.getSuppressed()).isEqualTo(1);
223238
assertThat(metrics.getFindingsTotal()).isEqualTo(2); // One is suppressed

apiserver/src/test/java/org/dependencytrack/metrics/UpdateProjectMetricsActivityTest.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
*/
1919
package org.dependencytrack.metrics;
2020

21+
import org.dependencytrack.kevdatasource.api.KevAssertion;
2122
import org.dependencytrack.model.AnalysisState;
2223
import org.dependencytrack.model.Component;
2324
import org.dependencytrack.model.DependencyMetrics;
@@ -30,6 +31,7 @@
3031
import org.dependencytrack.model.Vulnerability;
3132
import org.dependencytrack.persistence.command.MakeAnalysisCommand;
3233
import org.dependencytrack.persistence.command.MakeViolationAnalysisCommand;
34+
import org.dependencytrack.persistence.jdbi.KevDao;
3335
import org.dependencytrack.persistence.jdbi.MetricsDao;
3436
import org.dependencytrack.persistence.jdbi.MetricsTestDao;
3537
import org.dependencytrack.proto.internal.workflow.v1.UpdateProjectMetricsArg;
@@ -66,6 +68,7 @@ void shouldUpdateMetricsEmpty() throws Exception {
6668
assertThat(metrics.getMedium()).isZero();
6769
assertThat(metrics.getLow()).isZero();
6870
assertThat(metrics.getUnassigned()).isZero();
71+
assertThat(metrics.getKev()).isZero();
6972
assertThat(metrics.getVulnerabilities()).isZero();
7073
assertThat(metrics.getSuppressed()).isZero();
7174
assertThat(metrics.getFindingsTotal()).isZero();
@@ -131,6 +134,18 @@ void shouldUpdateMetricsVulnerabilities() throws Exception {
131134
vuln.setSeverity(Severity.HIGH);
132135
vuln = qm.createVulnerability(vuln);
133136

137+
useJdbiHandle(handle -> handle
138+
.attach(KevDao.class)
139+
.upsertBatch("cisa", List.of(
140+
new KevAssertion(
141+
"INTERNAL",
142+
"INTERNAL-001",
143+
null,
144+
null,
145+
null,
146+
null,
147+
null))));
148+
134149
// Create a component with an unaudited vulnerability.
135150
var componentUnaudited = new Component();
136151
componentUnaudited.setProject(project);
@@ -200,6 +215,7 @@ void shouldUpdateMetricsVulnerabilities() throws Exception {
200215
assertThat(metrics.getMedium()).isZero();
201216
assertThat(metrics.getLow()).isZero();
202217
assertThat(metrics.getUnassigned()).isZero();
218+
assertThat(metrics.getKev()).isEqualTo(2); // One is suppressed
203219
assertThat(metrics.getVulnerabilities()).isEqualTo(2); // One is suppressed
204220
assertThat(metrics.getSuppressed()).isEqualTo(1);
205221
assertThat(metrics.getFindingsTotal()).isEqualTo(2); // One is suppressed
@@ -307,6 +323,7 @@ void shouldUpdateMetricsPolicyViolations() throws Exception {
307323
assertThat(metrics.getMedium()).isZero();
308324
assertThat(metrics.getLow()).isZero();
309325
assertThat(metrics.getUnassigned()).isZero();
326+
assertThat(metrics.getKev()).isZero();
310327
assertThat(metrics.getVulnerabilities()).isZero();
311328
assertThat(metrics.getSuppressed()).isZero();
312329
assertThat(metrics.getFindingsTotal()).isZero();

apiserver/src/test/java/org/dependencytrack/resources/v1/MetricsResourceTest.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,7 @@ public void getCurrentPortfolioMetricsEmptyTest() {
362362
"firstOccurrence": "${json-unit.any-number}",
363363
"high": 0,
364364
"inheritedRiskScore": 0.0,
365+
"kev": 0,
365366
"lastOccurrence": "${json-unit.any-number}",
366367
"low": 0,
367368
"medium": 0,
@@ -729,6 +730,7 @@ public void getPortfolioMetricsXDaysAclTest() {
729730
"firstOccurrence": "${json-unit.any-number}",
730731
"high": 0,
731732
"inheritedRiskScore": 0.0,
733+
"kev": 0,
732734
"lastOccurrence": "${json-unit.any-number}",
733735
"low": 0,
734736
"medium": 0,
@@ -763,6 +765,7 @@ public void getPortfolioMetricsXDaysAclTest() {
763765
"firstOccurrence": "${json-unit.any-number}",
764766
"high": 1,
765767
"inheritedRiskScore": "${json-unit.matches:inheritedRiskScoreDay2}",
768+
"kev": 0,
766769
"lastOccurrence": "${json-unit.any-number}",
767770
"low": 1,
768771
"medium": 1,
@@ -797,6 +800,7 @@ public void getPortfolioMetricsXDaysAclTest() {
797800
"firstOccurrence": "${json-unit.any-number}",
798801
"high": 3,
799802
"inheritedRiskScore": "${json-unit.matches:inheritedRiskScoreDay3}",
803+
"kev": 0,
800804
"lastOccurrence": "${json-unit.any-number}",
801805
"low": 3,
802806
"medium": 3,
@@ -979,6 +983,7 @@ public void getPortfolioMetricsXDaysGlobalTest() {
979983
"firstOccurrence": "${json-unit.any-number}",
980984
"high": 0,
981985
"inheritedRiskScore": 0.0,
986+
"kev": 0,
982987
"lastOccurrence": "${json-unit.any-number}",
983988
"low": 0,
984989
"medium": 0,
@@ -1013,6 +1018,7 @@ public void getPortfolioMetricsXDaysGlobalTest() {
10131018
"firstOccurrence": "${json-unit.any-number}",
10141019
"high": 1,
10151020
"inheritedRiskScore": "${json-unit.matches:inheritedRiskScoreDay2}",
1021+
"kev": 0,
10161022
"lastOccurrence": "${json-unit.any-number}",
10171023
"low": 1,
10181024
"medium": 1,
@@ -1047,6 +1053,7 @@ public void getPortfolioMetricsXDaysGlobalTest() {
10471053
"firstOccurrence": "${json-unit.any-number}",
10481054
"high": 3,
10491055
"inheritedRiskScore": "${json-unit.matches:inheritedRiskScoreDay3}",
1056+
"kev": 0,
10501057
"lastOccurrence": "${json-unit.any-number}",
10511058
"low": 3,
10521059
"medium": 3,
@@ -1227,6 +1234,7 @@ public void getPortfolioMetricsSinceAclTest() {
12271234
"firstOccurrence": "${json-unit.any-number}",
12281235
"high": 0,
12291236
"inheritedRiskScore": 0.0,
1237+
"kev": 0,
12301238
"lastOccurrence": "${json-unit.any-number}",
12311239
"low": 0,
12321240
"medium": 0,
@@ -1261,6 +1269,7 @@ public void getPortfolioMetricsSinceAclTest() {
12611269
"firstOccurrence": "${json-unit.any-number}",
12621270
"high": 1,
12631271
"inheritedRiskScore": "${json-unit.matches:inheritedRiskScoreDay2}",
1272+
"kev": 0,
12641273
"lastOccurrence": "${json-unit.any-number}",
12651274
"low": 1,
12661275
"medium": 1,
@@ -1295,6 +1304,7 @@ public void getPortfolioMetricsSinceAclTest() {
12951304
"firstOccurrence": "${json-unit.any-number}",
12961305
"high": 3,
12971306
"inheritedRiskScore": "${json-unit.matches:inheritedRiskScoreDay3}",
1307+
"kev": 0,
12981308
"lastOccurrence": "${json-unit.any-number}",
12991309
"low": 3,
13001310
"medium": 3,
@@ -1477,6 +1487,7 @@ public void getPortfolioMetricsSinceGlobalTest() {
14771487
"firstOccurrence": "${json-unit.any-number}",
14781488
"high": 0,
14791489
"inheritedRiskScore": 0.0,
1490+
"kev": 0,
14801491
"lastOccurrence": "${json-unit.any-number}",
14811492
"low": 0,
14821493
"medium": 0,
@@ -1511,6 +1522,7 @@ public void getPortfolioMetricsSinceGlobalTest() {
15111522
"firstOccurrence": "${json-unit.any-number}",
15121523
"high": 1,
15131524
"inheritedRiskScore": "${json-unit.matches:inheritedRiskScoreDay2}",
1525+
"kev": 0,
15141526
"lastOccurrence": "${json-unit.any-number}",
15151527
"low": 1,
15161528
"medium": 1,
@@ -1545,6 +1557,7 @@ public void getPortfolioMetricsSinceGlobalTest() {
15451557
"firstOccurrence": "${json-unit.any-number}",
15461558
"high": 3,
15471559
"inheritedRiskScore": "${json-unit.matches:inheritedRiskScoreDay3}",
1560+
"kev": 0,
15481561
"lastOccurrence": "${json-unit.any-number}",
15491562
"low": 3,
15501563
"medium": 3,

0 commit comments

Comments
 (0)