@soos-io/soos-sbom
Version:
Upload your Software Bill of Materials (SBOM) to SOOS for vulnerability analysis, license matching and more. Register for a free trial today at https://app.soos.io/register
224 lines (223 loc) • 12.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const api_client_1 = require("@soos-io/api-client");
const utilities_1 = require("@soos-io/api-client/dist/utilities");
const FileSystem = tslib_1.__importStar(require("fs"));
const Path = tslib_1.__importStar(require("path"));
const process_1 = require("process");
const AnalysisArgumentParser_1 = tslib_1.__importDefault(require("@soos-io/api-client/dist/services/AnalysisArgumentParser"));
const package_json_1 = require("../package.json");
const AnalysisService_1 = tslib_1.__importDefault(require("@soos-io/api-client/dist/services/AnalysisService"));
const constants_1 = require("./constants");
const utilities_2 = require("./utilities");
const Glob = tslib_1.__importStar(require("glob"));
class SOOSSBOMAnalysis {
args;
constructor(args) {
this.args = args;
}
static parseArgs() {
const analysisArgumentParser = AnalysisArgumentParser_1.default.create(api_client_1.IntegrationName.SoosSbom, api_client_1.IntegrationType.Script, api_client_1.ScanType.SBOM, package_json_1.version);
analysisArgumentParser.addArgument("directoriesToExclude", "Listing of directories or patterns to exclude from the search for SBOM files. eg: **bin/start/**, **/start/**", {
argParser: (value) => {
return (0, utilities_2.removeDuplicates)(value.split(",").map((pattern) => pattern.trim()));
},
defaultValue: constants_1.SOOS_SBOM_CONSTANTS.DefaultDirectoriesToExclude,
});
analysisArgumentParser.addArgument("filesToExclude", "Listing of files or patterns patterns to exclude from the search for SBOM files. eg: **/int**.cdx.json/, **/internal.cdx.json", {
argParser: (value) => {
return (0, utilities_2.removeDuplicates)(value.split(",").map((pattern) => pattern.trim()));
},
defaultValue: [],
});
analysisArgumentParser.addArgument("sbomPath", "The SBOM file or folder to scan. When a folder is specified all SBOMs found in the folder and sub-folders will be scanned.", { useNoOptionKey: true });
analysisArgumentParser.addArgument("outputDirectory", "Absolute path where SOOS will write exported reports and SBOMs. eg Correct: /out/sbom/ | Incorrect: ./out/sbom/", {
defaultValue: process.cwd(),
});
return analysisArgumentParser.parseArguments();
}
async runAnalysis() {
const scanType = api_client_1.ScanType.SBOM;
const soosAnalysisService = AnalysisService_1.default.create(this.args.apiKey, this.args.apiURL);
let projectHash;
let branchHash;
let analysisId;
let scanStatusUrl;
let scanStatus;
try {
const result = await soosAnalysisService.setupScan({
clientId: this.args.clientId,
projectName: this.args.projectName,
branchName: this.args.branchName,
commitHash: this.args.commitHash,
buildVersion: this.args.buildVersion,
buildUri: this.args.buildURI,
branchUri: this.args.branchURI,
operatingEnvironment: this.args.operatingEnvironment,
integrationName: this.args.integrationName,
integrationType: this.args.integrationType,
appVersion: this.args.appVersion,
scriptVersion: this.args.scriptVersion,
contributingDeveloperAudit: [
{
contributingDeveloperId: this.args.contributingDeveloperId,
source: this.args.contributingDeveloperSource,
sourceName: this.args.contributingDeveloperSourceName,
},
],
scanType,
commandLine: process.argv.length > 2
? (0, utilities_1.obfuscateCommandLine)((0, utilities_1.reassembleCommandLine)(process.argv.slice(2)), constants_1.SOOS_SBOM_CONSTANTS.ObfuscatedArguments.map((a) => `--${a}`))
: null,
});
projectHash = result.projectHash;
branchHash = result.branchHash;
analysisId = result.analysisId;
scanStatusUrl = result.scanStatusUrl;
const { sbomFilePaths, hasMoreThanMaximumManifests } = await this.findSbomFilePaths();
if (sbomFilePaths.length === 0) {
const noFilesMessage = `No SBOM files found. They need to match the pattern ${constants_1.SOOS_SBOM_CONSTANTS.FilePattern}. See https://kb.soos.io/getting-started-with-soos-sbom-manager for more information.`;
await soosAnalysisService.updateScanStatus({
analysisId,
clientId: this.args.clientId,
projectHash,
branchHash,
scanType,
status: api_client_1.ScanStatus.NoFiles,
message: noFilesMessage,
scanStatusUrl,
});
api_client_1.soosLogger.error(noFilesMessage);
api_client_1.soosLogger.always(`${noFilesMessage} - exit 1`);
(0, process_1.exit)(1);
}
if (sbomFilePaths.length === 1 &&
sbomFilePaths[0] === this.args.sbomPath &&
!constants_1.SOOS_SBOM_CONSTANTS.FileRegex.test(sbomFilePaths[0])) {
const noFilesMessage = `The file does not match the required SBOM pattern ${constants_1.SOOS_SBOM_CONSTANTS.FilePattern}. See https://kb.soos.io/getting-started-with-soos-sbom-manager for more information.`;
await soosAnalysisService.updateScanStatus({
analysisId,
clientId: this.args.clientId,
projectHash,
branchHash,
scanType,
status: api_client_1.ScanStatus.NoFiles,
message: noFilesMessage,
scanStatusUrl,
});
api_client_1.soosLogger.error(noFilesMessage);
api_client_1.soosLogger.always(`${noFilesMessage} - exit 1`);
(0, process_1.exit)(1);
}
api_client_1.soosLogger.info("Uploading SBOM File(s)...");
for (let i = 0; i < sbomFilePaths.length; i += constants_1.SOOS_SBOM_CONSTANTS.UploadBatchSize) {
const sbomFilePathsBatch = sbomFilePaths.slice(i, i + constants_1.SOOS_SBOM_CONSTANTS.UploadBatchSize);
const formData = await soosAnalysisService.getAnalysisFilesAsFormData(sbomFilePathsBatch, this.args.sbomPath);
const manifestUploadResponse = await soosAnalysisService.analysisApiClient.uploadManifestFiles({
clientId: this.args.clientId,
projectHash,
branchHash,
analysisId,
manifestFiles: formData,
hasMoreThanMaximumManifests,
});
api_client_1.soosLogger.info(` SBOM File(s): \n`, ` ${manifestUploadResponse.message} \n`, manifestUploadResponse.manifests
?.map((m) => ` ${m.name}: ${m.statusMessage}`)
.join("\n"));
}
await soosAnalysisService.startScan({
clientId: this.args.clientId,
projectHash,
analysisId,
scanType,
scanUrl: result.scanUrl,
});
scanStatus = await soosAnalysisService.waitForScanToFinish({
scanStatusUrl: result.scanStatusUrl,
scanUrl: result.scanUrl,
scanType,
});
if ((0, utilities_1.isScanDone)(scanStatus) &&
this.args.exportFormat !== api_client_1.AttributionFormatEnum.Unknown &&
this.args.exportFileType !== api_client_1.AttributionFileTypeEnum.Unknown) {
await soosAnalysisService.generateFormattedOutput({
clientId: this.args.clientId,
projectHash: result.projectHash,
projectName: this.args.projectName,
branchHash: result.branchHash,
analysisId: result.analysisId,
format: this.args.exportFormat,
fileType: this.args.exportFileType,
includeDependentProjects: false,
includeOriginalSbom: false,
includeVulnerabilities: false,
workingDirectory: this.args.outputDirectory,
});
}
const exitCodeWithMessage = (0, utilities_1.getAnalysisExitCodeWithMessage)(scanStatus, this.args.integrationName, this.args.onFailure);
api_client_1.soosLogger.always(`${exitCodeWithMessage.message} - exit ${exitCodeWithMessage.exitCode}`);
(0, process_1.exit)(exitCodeWithMessage.exitCode);
}
catch (error) {
if (projectHash && branchHash && analysisId && (!scanStatus || !(0, utilities_1.isScanDone)(scanStatus))) {
await soosAnalysisService.updateScanStatus({
clientId: this.args.clientId,
projectHash,
branchHash,
scanType,
analysisId: analysisId,
status: api_client_1.ScanStatus.Error,
message: "Error while performing scan.",
scanStatusUrl,
});
}
api_client_1.soosLogger.error(error);
api_client_1.soosLogger.always(`${error} - exit 1`);
(0, process_1.exit)(1);
}
}
async findSbomFilePaths() {
const sbomPathStat = await FileSystem.statSync(this.args.sbomPath);
if (sbomPathStat.isDirectory()) {
const searchPattern = this.args.sbomPath.endsWith("/") || this.args.sbomPath.endsWith("\\")
? `${this.args.sbomPath}${constants_1.SOOS_SBOM_CONSTANTS.FilePattern}`
: `${this.args.sbomPath}/${constants_1.SOOS_SBOM_CONSTANTS.FilePattern}`;
let sbomFilePaths = Glob.sync(searchPattern, {
ignore: [
...this.args.filesToExclude,
...this.args.directoriesToExclude,
constants_1.SOOS_SBOM_CONSTANTS.SoosDirectoryToExclude,
],
nocase: true,
});
const hasMoreThanMaximumManifests = sbomFilePaths.length > constants_1.SOOS_SBOM_CONSTANTS.MaxSbomsPerScan;
if (hasMoreThanMaximumManifests) {
const filesToSkip = sbomFilePaths.slice(constants_1.SOOS_SBOM_CONSTANTS.MaxSbomsPerScan);
sbomFilePaths = sbomFilePaths.slice(0, constants_1.SOOS_SBOM_CONSTANTS.MaxSbomsPerScan);
const filesDetectedString = utilities_1.StringUtilities.pluralizeTemplate(sbomFilePaths.length, "file was", "files were");
const filesSkippedString = utilities_1.StringUtilities.pluralizeTemplate(filesToSkip.length, "file");
api_client_1.soosLogger.info(`The maximum number of SBOMs per scan is ${constants_1.SOOS_SBOM_CONSTANTS.MaxSbomsPerScan}. ${filesDetectedString} detected, and ${filesSkippedString} will be not be uploaded. \n`, `The following SBOMs will not be included in the scan: \n`, filesToSkip.map((file) => ` "${Path.parse(file).base}": "${file}"`).join("\n"));
}
return { sbomFilePaths, hasMoreThanMaximumManifests };
}
return { sbomFilePaths: [this.args.sbomPath], hasMoreThanMaximumManifests: false };
}
static async createAndRun() {
try {
const args = this.parseArgs();
api_client_1.soosLogger.setMinLogLevel(args.logLevel);
api_client_1.soosLogger.always("Starting SOOS SBOM Analysis");
api_client_1.soosLogger.debug(JSON.stringify((0, utilities_1.obfuscateProperties)(args, ["apiKey"]), null, 2));
const soosSBOMAnalysis = new SOOSSBOMAnalysis(args);
await soosSBOMAnalysis.runAnalysis();
}
catch (error) {
api_client_1.soosLogger.error(`Error on createAndRun: ${error}`);
api_client_1.soosLogger.always(`Error on createAndRun: ${error} - exit 1`);
(0, process_1.exit)(1);
}
}
}
SOOSSBOMAnalysis.createAndRun();