in src/main/java/com/amazon/inspector/jenkins/amazoninspectorbuildstep/AmazonInspectorBuilder.java [169:389]
public void perform(Run<?, ?> build, FilePath workspace, EnvVars env, Launcher launcher, TaskListener listener)
throws IOException, InterruptedException {
logger = listener.getLogger();
File outFile = new File(build.getRootDir(), "out");
this.job = build.getParent();
PrintStream printStream = new PrintStream(outFile, StandardCharsets.UTF_8);
try {
Map<String, String> artifactMap = new HashMap<>();
if (Jenkins.getInstanceOrNull() == null) {
throw new RuntimeException("No Jenkins instance found.");
}
String activeArchiveType = archiveType;
if (activeArchiveType == null || activeArchiveType.isEmpty()) {
activeArchiveType = "container";
}
String sbomgenSelection = this.sbomgenSelection;
String activeSbomgenPath;
if ("automatic".equalsIgnoreCase(sbomgenSelection)) {
logger.println("Automatic SBOMGen selected, downloading using default settings...");
activeSbomgenPath = SbomgenDownloader.getBinary(workspace);
} else if ("manual".equalsIgnoreCase(sbomgenSelection)) {
if (sbomgenPath == null || sbomgenPath.isEmpty()) {
throw new IllegalArgumentException("Manual SBOMGen selected but no path provided.");
}
File sbomgenFile = new File(sbomgenPath);
if (!sbomgenFile.exists() || !sbomgenFile.canExecute()) {
throw new IllegalArgumentException("Provided SBOMgen path is invalid or not executable: " + sbomgenPath);
}
logger.println("Manual SBOMGen selected, using provided path: " + sbomgenPath);
activeSbomgenPath = sbomgenPath;
} else {
logger.println("Invalid SBOMGen selection. Defaulting to Automatic.");
activeSbomgenPath = SbomgenDownloader.getBinary(workspace);
}
StandardUsernamePasswordCredentials credential = null;
if (credentialId == null) {
logger.println("Credential ID is null, this is not normal, please check your config. " +
"Continuing without docker credentials.");
} else {
credential = CredentialsProvider.findCredentialById(credentialId,
StandardUsernamePasswordCredentials.class, build);
}
String skipfiles = (sbomgenSkipFiles != null) ? sbomgenSkipFiles : "";
String sbom;
if (credential != null) {
sbom = new SbomgenRunner(launcher, activeSbomgenPath, activeArchiveType, archivePath, credential.getUsername(),
credential.getPassword().getPlainText(),skipfiles).run();
} else {
sbom = new SbomgenRunner(launcher, activeSbomgenPath, activeArchiveType, archivePath, null, null, skipfiles).run();
}
JsonElement metadata = JsonParser.parseString(sbom).getAsJsonObject().get("metadata");
JsonObject component = null;
if (metadata != null && metadata.getAsJsonObject().get("component") != null) {
component = metadata.getAsJsonObject().get("component").getAsJsonObject();
}
Gson gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
String imageSha = getImageSha(sbom);
listener.getLogger().printf("Sending SBOM to Inspector for validation with info: credential:%s, role:%s, profile:%s",
awsCredentialId, iamRole, awsProfileName);
AmazonWebServicesCredentials awsCredential = null;
if (awsCredentialId != null) {
awsCredential = CredentialsProvider.findCredentialById(awsCredentialId,
AmazonWebServicesCredentials.class, build);
}
listener.getLogger().print("\n");
String workingOidcCredentialId = oidcCredentialId;
if (workingOidcCredentialId == null) {
workingOidcCredentialId = "";
}
IdTokenStringCredentials oidcStr = CredentialsProvider.findCredentialById(workingOidcCredentialId, IdTokenStringCredentials.class, build);
IdTokenFileCredentials oidcFile = CredentialsProvider.findCredentialById(workingOidcCredentialId, IdTokenFileCredentials.class, build);
String oidcToken = getOidcToken(oidcStr, oidcFile);
String responseData = new SdkRequests(awsRegion, awsCredential, oidcToken, awsProfileName, iamRole).requestSbom(sbom);
SbomData sbomData = SbomData.builder().sbom(gson.fromJson(responseData, Sbom.class)).build();
String sbomFileName = String.format("%s-%s-sbom.json", reportArtifactName, build.getDisplayName()).replaceAll("[ #]", "");
String sbomWorkspacePath = String.format("%s/%s", build.getId(), sbomFileName);
FilePath sbomFile = workspace.child(sbomWorkspacePath);
FilePath sbomFileParent = sbomFile.getParent();
if (sbomFile == null || sbomFileParent == null) {
throw new NullPointerException("SbomFile cannot be null.");
}
if (!sbomFileParent.exists()) {
sbomFileParent.mkdirs();
}
sbomFile.write(gson.toJson(sbomData.getSbom()), "UTF-8");
artifactMap.put(sbomFileName, sbomWorkspacePath);
build.getArtifactManager().archive(workspace, launcher, new BuildListenerAdapter(listener), artifactMap);
listener.getLogger().println("Artifact saved: " + sbomFile.getRemote());
CsvConverter converter = new CsvConverter(sbomData);
String csvVulnFileName = String.format("%s-%s-vuln.csv", build.getParent().getDisplayName(),
build.getDisplayName()).replaceAll("[ #]", "");
String csvVulnWorkspacePath = String.format("%s/%s", build.getId(), csvVulnFileName);
FilePath csvVulnFile = workspace.child(csvVulnWorkspacePath);
String csvDockerFileName = String.format("%s-%s-docker.csv", build.getParent().getDisplayName(),
build.getDisplayName()).replaceAll("[ #]", "");
String csvDockerWorkspacePath = String.format("%s/%s", build.getId(), csvDockerFileName);
FilePath csvDockerFile = workspace.child(csvDockerWorkspacePath);
logger.println("Converting SBOM Results to CSV.");
SbomOutputParser parser = new SbomOutputParser(sbomData);
parser.parseVulnCounts();
String sanitizedArchiveName = null;
String componentName = null;
if (component != null && component.get("name") != null) {
componentName = component.get("name").getAsString();
}
if (componentName != null && componentName.endsWith(".tar")) {
sanitizedArchiveName = sanitizeFilePath("file://" + componentName);
} else {
sanitizedArchiveName = archivePath;
}
converter.routeVulnerabilities();
String csvVulnContent = converter.convertVulnerabilities(sanitizedArchiveName, imageSha, build.getId(), SbomOutputParser.vulnCounts);
if (csvVulnContent != null) {
artifactMap.put(csvVulnFileName, csvVulnWorkspacePath);
csvVulnFile.write(csvVulnContent, "UTF-8");
}
String csvDockerContent = converter.convertDocker(sanitizedArchiveName, imageSha, build.getId(), SbomOutputParser.dockerCounts);
if (csvDockerContent != null) {
artifactMap.put(csvDockerFileName, csvDockerWorkspacePath);
csvDockerFile.write(csvDockerContent, "UTF-8");
}
String[] splitName = sanitizedArchiveName.split(":");
String tag = null;
if (splitName.length > 1) {
tag = splitName[1];
}
@SuppressFBWarnings
HtmlData htmlData = HtmlData.builder()
.artifactsPath(sanitizeUrl(env.get("RUN_ARTIFACTS_DISPLAY_URL"))) //jenkins specific
.updatedAt(new SimpleDateFormat("MM/dd/yyyy, hh:mm:ss aa").format(Calendar.getInstance().getTime()))
.imageMetadata(ImageMetadata.builder()
.id(splitName[0])
.tags(tag)
.sha(imageSha)
.build())
.docker(HtmlConversionUtils.convertDocker(sbomData.getSbom().getVulnerabilities(),
sbomData.getSbom().getComponents()))
.vulnerabilities(HtmlConversionUtils.convertVulnerabilities(sbomData.getSbom().getVulnerabilities(),
sbomData.getSbom().getComponents()))
.build();
String reportData = gson.toJson(htmlData);
String htmlJarPath = String.valueOf(new FilePath(new File(HtmlJarHandler.class.getProtectionDomain().getCodeSource().getLocation()
.toURI())));
new HtmlJarHandler(htmlJarPath, reportData).copyHtmlToDir(workspace, build.getId());
artifactMap.put("index.html", String.format("%s/%s", build.getId(), "index.html"));
build.getArtifactManager().archive(workspace, launcher, new BuildListenerAdapter(listener), artifactMap);
listener.getLogger().println("Build Artifacts: " + env.get("RUN_ARTIFACTS_DISPLAY_URL"));
boolean doesBuildPass = !doesBuildFail(SbomOutputParser.aggregateCounts.getCounts());
if (!isThresholdEnabled) {
listener.getLogger().println("Thresholds disabled. Skipping all threshold/EPSS checks.");
doesBuildPass = true;
} else {
if (epssThreshold != null) {
listener.getLogger().println("EPSS Threshold set to: " + epssThreshold);
boolean cvesExceedThreshold = assessCVEsAgainstEPSS(build, workspace, listener, epssThreshold, sbomWorkspacePath);
if (cvesExceedThreshold) {
doesBuildPass = false;
} else {
listener.getLogger().println("All CVEs are within the EPSS threshold of " + epssThreshold + ".");
}
} else {
listener.getLogger().println("No EPSS Threshold specified. Skipping EPSS assessment.");
}
}
if (doesBuildPass) {
build.setResult(Result.SUCCESS);
} else {
build.setResult(Result.FAILURE);
}
listener.getLogger().println("Results: " + SbomOutputParser.aggregateCounts.toString());
if (!isThresholdEnabled) {
listener.getLogger().println("Ignoring results due to thresholds being disabled.");
}
listener.getLogger().println("Does Build Pass: " + doesBuildPass);
} catch (Exception e) {
listener.getLogger().println("Plugin execution ran into an error and is being aborted!");
build.setResult(Result.ABORTED);
listener.getLogger().println("Exception:" + e);
e.printStackTrace(listener.getLogger());
} finally {
printStream.close();
}
}