Skip to content

Commit 591ebce

Browse files
authored
Merge pull request aws#183 from aws/fix/epss-and-deps
fix: EPSS threshold reads scan response instead of inventory SBOM (#169)
2 parents 4e69c64 + 345e3ee commit 591ebce

3 files changed

Lines changed: 205 additions & 80 deletions

File tree

pom.xml

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
<dependency>
4545
<groupId>software.amazon.awssdk</groupId>
4646
<artifactId>bom</artifactId>
47-
<version>2.42.4</version>
47+
<version>2.42.25</version>
4848
<type>pom</type>
4949
<scope>import</scope>
5050
</dependency>
@@ -54,19 +54,19 @@
5454
<dependency>
5555
<groupId>com.fasterxml.jackson.core</groupId>
5656
<artifactId>jackson-databind</artifactId>
57-
<version>2.20.1</version>
57+
<version>2.21.2</version>
5858
</dependency>
5959

6060
<dependency>
6161
<groupId>org.jenkins-ci.plugins</groupId>
6262
<artifactId>plain-credentials</artifactId>
63-
<version>209.vc1e7c6295cff</version>
63+
<version>199.v9f8e1f741799</version>
6464
</dependency>
6565

6666
<dependency>
6767
<groupId>org.jenkins-ci.plugins</groupId>
6868
<artifactId>jackson2-api</artifactId>
69-
<version>2.21.0-428.v87fd3700d79d</version>
69+
<version>2.19.2-408.v18248a_324cfe</version>
7070
</dependency>
7171

7272
<dependency>
@@ -78,7 +78,7 @@
7878
<dependency>
7979
<groupId>org.projectlombok</groupId>
8080
<artifactId>lombok</artifactId>
81-
<version>1.18.42</version>
81+
<version>1.18.44</version>
8282
<scope>provided</scope>
8383
</dependency>
8484

@@ -120,6 +120,21 @@
120120
<version>5.12.0</version>
121121
</dependency>
122122

123+
<!-- opencsv 5.12 requires commons-beanutils 1.11.0; parent POM manages
124+
1.9.4. Exclude commons-logging because the 1.3.5 it pulls is banned
125+
by the parent's BannedDependencies rule. -->
126+
<dependency>
127+
<groupId>commons-beanutils</groupId>
128+
<artifactId>commons-beanutils</artifactId>
129+
<version>1.11.0</version>
130+
<exclusions>
131+
<exclusion>
132+
<groupId>commons-logging</groupId>
133+
<artifactId>commons-logging</artifactId>
134+
</exclusion>
135+
</exclusions>
136+
</dependency>
137+
123138
<dependency>
124139
<groupId>software.amazon.awssdk</groupId>
125140
<artifactId>sts</artifactId>
@@ -154,7 +169,7 @@
154169
<dependency>
155170
<groupId>org.jenkins-ci.plugins</groupId>
156171
<artifactId>structs</artifactId>
157-
<version>353.v261ea_40a_80fb_</version>
172+
<version>362.va_b_695ef4fdf9</version>
158173
</dependency>
159174

160175
<dependency>

src/main/java/com/amazon/inspector/jenkins/amazoninspectorbuildstep/AmazonInspectorBuilder.java

Lines changed: 53 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
import com.google.gson.JsonArray;
1313
import com.google.gson.JsonElement;
1414
import com.google.gson.JsonObject;
15-
import com.google.gson.JsonParseException;
1615
import com.google.gson.JsonParser;
1716
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
1817
import hudson.EnvVars;
@@ -707,7 +706,7 @@ public void perform(Run<?, ?> build, FilePath workspace, EnvVars env, Launcher l
707706

708707
if (isEpssThresholdEnabled && epssThreshold != null) {
709708
listener.getLogger().println("EPSS Threshold set to: " + epssThreshold);
710-
boolean cvesExceedThreshold = assessCVEsAgainstEPSS(build, workspace, listener, epssThreshold, sbomWorkspacePath);
709+
boolean cvesExceedThreshold = assessCVEsAgainstEPSS(listener, epssThreshold, sbomData);
711710
if (cvesExceedThreshold) {
712711
doesBuildPass = false;
713712
}
@@ -738,86 +737,66 @@ public void perform(Run<?, ?> build, FilePath workspace, EnvVars env, Launcher l
738737
}
739738
}
740739

741-
private boolean assessCVEsAgainstEPSS(Run<?, ?> build, FilePath workspace, TaskListener listener, Double epssThreshold, String sbomPath)
742-
throws IOException, InterruptedException {
743-
FilePath sbomFile = workspace.child(sbomPath);
744-
if (!sbomFile.exists()) {
745-
listener.getLogger().println("SBOM file not found at: " + sbomFile.getRemote());
746-
return true;
740+
private boolean assessCVEsAgainstEPSS(TaskListener listener, Double epssThreshold, SbomData sbomData) {
741+
List<Vulnerability> vulnerabilities = sbomData.getSbom().getVulnerabilities();
742+
if (vulnerabilities == null || vulnerabilities.isEmpty()) {
743+
listener.getLogger().println("No vulnerabilities found in the scan response.");
744+
return false;
747745
}
748-
try {
749-
String sbomContent = sbomFile.readToString();
750-
listener.getLogger().println("SBOM file read successfully.");
751-
Gson gson = new Gson();
752-
Sbom sbom = gson.fromJson(sbomContent, Sbom.class);
753-
listener.getLogger().println("SBOM JSON parsed successfully.");
754-
List<Vulnerability> vulnerabilities = sbom.getVulnerabilities();
755-
if (vulnerabilities == null || vulnerabilities.isEmpty()) {
756-
listener.getLogger().println("No vulnerabilities found in the SBOM.");
757-
return false;
746+
747+
Set<String> suppressedCveSet = new HashSet<>();
748+
if (isSuppressedCveEnabled && suppressedCveList != null && !suppressedCveList.trim().isEmpty()) {
749+
String[] cveArray = suppressedCveList.split("[,\\n\\r]+");
750+
for (String cve : cveArray) {
751+
suppressedCveSet.add(cve.trim().toUpperCase());
758752
}
759-
760-
Set<String> suppressedCveSet = new HashSet<>();
761-
if (isSuppressedCveEnabled && suppressedCveList != null && !suppressedCveList.trim().isEmpty()) {
762-
String[] cveArray = suppressedCveList.split("[,\\n\\r]+");
763-
for (String cve : cveArray) {
764-
suppressedCveSet.add(cve.trim().toUpperCase());
765-
}
766-
listener.getLogger().println("Suppressing " + suppressedCveSet.size() + " CVEs from EPSS assessment: " + suppressedCveSet);
753+
listener.getLogger().println("Suppressing " + suppressedCveSet.size() + " CVEs from EPSS assessment: " + suppressedCveSet);
754+
}
755+
756+
listener.getLogger().println("Starting EPSS assessment for vulnerabilities...");
757+
boolean exceedsThreshold = false;
758+
Map<String, Double> exceedingCVEsMap = new HashMap<>();
759+
int suppressedCount = 0;
760+
761+
for (Vulnerability vulnerability : vulnerabilities) {
762+
String cveId = vulnerability.getId();
763+
Double epssScore = vulnerability.getEpssScore();
764+
765+
if (suppressedCveSet.contains(cveId.toUpperCase())) {
766+
suppressedCount++;
767+
continue;
767768
}
768-
769-
listener.getLogger().println("Starting EPSS assessment for vulnerabilities...");
770-
boolean exceedsThreshold = false;
771-
Map<String, Double> exceedingCVEsMap = new HashMap<>();
772-
int suppressedCount = 0;
773-
774-
for (Vulnerability vulnerability : vulnerabilities) {
775-
String cveId = vulnerability.getId();
776-
Double epssScore = vulnerability.getEpssScore();
777-
778-
// Skip suppressed CVEs
779-
if (suppressedCveSet.contains(cveId.toUpperCase())) {
780-
suppressedCount++;
781-
continue;
782-
}
783-
784-
if (epssScore == null) {
785-
continue;
786-
}
787-
if (epssScore >= epssThreshold) {
788-
exceedsThreshold = true;
789-
exceedingCVEsMap.put(cveId, epssScore);
790-
}
769+
770+
if (epssScore == null) {
771+
continue;
791772
}
792-
793-
if (suppressedCount > 0) {
794-
listener.getLogger().println("Suppressed " + suppressedCount + " CVEs from EPSS assessment.");
773+
if (epssScore >= epssThreshold) {
774+
exceedsThreshold = true;
775+
exceedingCVEsMap.put(cveId, epssScore);
795776
}
796-
797-
if (exceedsThreshold) {
798-
listener.getLogger().println("The following CVEs exceed the EPSS threshold of " + epssThreshold + ":");
799-
int count = 0;
800-
for (Map.Entry<String, Double> entry : exceedingCVEsMap.entrySet()) {
801-
if (count < MAX_EPSS_CVES_CONSOLE) {
802-
listener.getLogger().println(String.format(" - %s (EPSS: %.3f)", entry.getKey(), entry.getValue()));
803-
count++;
804-
} else {
805-
listener.getLogger().println(" ... and " + (exceedingCVEsMap.size() - count) + " more EPSS breaches (check assessment file for complete list)");
806-
break;
807-
}
777+
}
778+
779+
if (suppressedCount > 0) {
780+
listener.getLogger().println("Suppressed " + suppressedCount + " CVEs from EPSS assessment.");
781+
}
782+
783+
if (exceedsThreshold) {
784+
listener.getLogger().println("The following CVEs exceed the EPSS threshold of " + epssThreshold + ":");
785+
int count = 0;
786+
for (Map.Entry<String, Double> entry : exceedingCVEsMap.entrySet()) {
787+
if (count < MAX_EPSS_CVES_CONSOLE) {
788+
listener.getLogger().println(String.format(" - %s (EPSS: %.3f)", entry.getKey(), entry.getValue()));
789+
count++;
790+
} else {
791+
listener.getLogger().println(" ... and " + (exceedingCVEsMap.size() - count) + " more EPSS breaches (check assessment file for complete list)");
792+
break;
808793
}
809-
listener.getLogger().println("Failing the build due to EPSS threshold breach.");
810-
} else {
811-
listener.getLogger().println("All assessed CVEs are within the EPSS threshold of " + epssThreshold + ".");
812794
}
813-
return exceedsThreshold;
814-
} catch (JsonParseException e) {
815-
listener.getLogger().println("Invalid JSON structure in SBOM file: " + e.getMessage());
816-
return true;
817-
} catch (IOException e) {
818-
listener.getLogger().println("Error reading SBOM file: " + e.getMessage());
819-
return true;
795+
listener.getLogger().println("Failing the build due to EPSS threshold breach.");
796+
} else {
797+
listener.getLogger().println("All assessed CVEs are within the EPSS threshold of " + epssThreshold + ".");
820798
}
799+
return exceedsThreshold;
821800
}
822801

823802
private String getOidcToken(IdTokenStringCredentials oidcStr, IdTokenFileCredentials oidcFile) throws IOException {
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
package com.amazon.inspector.jenkins.amazoninspectorbuildstep;
2+
3+
import com.amazon.inspector.jenkins.amazoninspectorbuildstep.models.sbom.Sbom;
4+
import com.amazon.inspector.jenkins.amazoninspectorbuildstep.models.sbom.SbomData;
5+
import com.amazon.inspector.jenkins.amazoninspectorbuildstep.models.sbom.Components.Rating;
6+
import com.amazon.inspector.jenkins.amazoninspectorbuildstep.models.sbom.Components.Source;
7+
import com.amazon.inspector.jenkins.amazoninspectorbuildstep.models.sbom.Components.Vulnerability;
8+
import hudson.model.TaskListener;
9+
import org.junit.Before;
10+
import org.junit.Test;
11+
12+
import java.io.ByteArrayOutputStream;
13+
import java.io.PrintStream;
14+
import java.lang.reflect.Method;
15+
import java.util.Arrays;
16+
import java.util.Collections;
17+
import java.util.List;
18+
19+
import static org.junit.Assert.assertFalse;
20+
import static org.junit.Assert.assertTrue;
21+
import static org.mockito.Mockito.mock;
22+
import static org.mockito.Mockito.when;
23+
24+
public class AmazonInspectorBuilderEpssTest {
25+
26+
private TaskListener listener;
27+
private Method assessMethod;
28+
29+
@Before
30+
public void setUp() throws Exception {
31+
listener = mock(TaskListener.class);
32+
when(listener.getLogger()).thenReturn(new PrintStream(new ByteArrayOutputStream()));
33+
34+
assessMethod = AmazonInspectorBuilder.class.getDeclaredMethod(
35+
"assessCVEsAgainstEPSS", TaskListener.class, Double.class, SbomData.class);
36+
assessMethod.setAccessible(true);
37+
}
38+
39+
@Test
40+
public void emptyVulnerabilitiesReturnsFalse() throws Exception {
41+
assertFalse(invoke(builder(false, ""), 0.5, sbomData(Collections.emptyList())));
42+
}
43+
44+
@Test
45+
public void nullVulnerabilitiesReturnsFalse() throws Exception {
46+
assertFalse(invoke(builder(false, ""), 0.5, sbomData(null)));
47+
}
48+
49+
@Test
50+
public void vulnAboveThresholdReturnsTrue() throws Exception {
51+
List<Vulnerability> vulns = Collections.singletonList(epssVuln("CVE-1", 0.8));
52+
assertTrue(invoke(builder(false, ""), 0.5, sbomData(vulns)));
53+
}
54+
55+
@Test
56+
public void vulnBelowThresholdReturnsFalse() throws Exception {
57+
List<Vulnerability> vulns = Collections.singletonList(epssVuln("CVE-1", 0.1));
58+
assertFalse(invoke(builder(false, ""), 0.5, sbomData(vulns)));
59+
}
60+
61+
@Test
62+
public void vulnEqualToThresholdReturnsTrue() throws Exception {
63+
List<Vulnerability> vulns = Collections.singletonList(epssVuln("CVE-1", 0.5));
64+
assertTrue(invoke(builder(false, ""), 0.5, sbomData(vulns)));
65+
}
66+
67+
@Test
68+
public void nullEpssScoreIsSkipped() throws Exception {
69+
List<Vulnerability> vulns = Collections.singletonList(noEpssVuln("CVE-1"));
70+
assertFalse(invoke(builder(false, ""), 0.5, sbomData(vulns)));
71+
}
72+
73+
@Test
74+
public void suppressedCveAboveThresholdIsSkippedWhenSuppressionEnabled() throws Exception {
75+
List<Vulnerability> vulns = Collections.singletonList(epssVuln("CVE-1", 0.9));
76+
assertFalse(invoke(builder(true, "CVE-1"), 0.5, sbomData(vulns)));
77+
}
78+
79+
@Test
80+
public void suppressedCveAboveThresholdIsCountedWhenSuppressionDisabled() throws Exception {
81+
List<Vulnerability> vulns = Collections.singletonList(epssVuln("CVE-1", 0.9));
82+
assertTrue(invoke(builder(false, "CVE-1"), 0.5, sbomData(vulns)));
83+
}
84+
85+
@Test
86+
public void mixOfSuppressedAndBreachingCves() throws Exception {
87+
List<Vulnerability> vulns = Arrays.asList(
88+
epssVuln("CVE-1", 0.9),
89+
epssVuln("CVE-2", 0.8),
90+
epssVuln("CVE-3", 0.1));
91+
assertTrue(invoke(builder(true, "CVE-1"), 0.5, sbomData(vulns)));
92+
}
93+
94+
@Test
95+
public void allSuppressedReturnsFalseEvenWhenAboveThreshold() throws Exception {
96+
List<Vulnerability> vulns = Arrays.asList(
97+
epssVuln("CVE-1", 0.9),
98+
epssVuln("CVE-2", 0.8));
99+
assertFalse(invoke(builder(true, "CVE-1,CVE-2"), 0.5, sbomData(vulns)));
100+
}
101+
102+
@Test
103+
public void suppressionMatchIsCaseInsensitive() throws Exception {
104+
List<Vulnerability> vulns = Collections.singletonList(epssVuln("cve-2024-1234", 0.9));
105+
assertFalse(invoke(builder(true, "CVE-2024-1234"), 0.5, sbomData(vulns)));
106+
}
107+
108+
private boolean invoke(AmazonInspectorBuilder builder, double threshold, SbomData data) throws Exception {
109+
return (boolean) assessMethod.invoke(builder, listener, threshold, data);
110+
}
111+
112+
private static AmazonInspectorBuilder builder(boolean suppressionEnabled, String suppressedList) {
113+
return new AmazonInspectorBuilder(
114+
"test", "test", "container", false, "", "us-east-1", "", "", "",
115+
"automatic", "", 0, 0, 0, 0, "", "", 0.5, suppressedList,
116+
suppressionEnabled, false, "", false, true, true);
117+
}
118+
119+
private static SbomData sbomData(List<Vulnerability> vulnerabilities) {
120+
return SbomData.builder().sbom(Sbom.builder().vulnerabilities(vulnerabilities).build()).build();
121+
}
122+
123+
private static Vulnerability epssVuln(String id, double score) {
124+
Rating epss = Rating.builder().source(Source.builder().name("EPSS").build()).score(score).build();
125+
return Vulnerability.builder().id(id).ratings(Collections.singletonList(epss)).build();
126+
}
127+
128+
private static Vulnerability noEpssVuln(String id) {
129+
return Vulnerability.builder().id(id).ratings(Collections.emptyList()).build();
130+
}
131+
}

0 commit comments

Comments
 (0)