UNPKG

@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
#!/usr/bin/env node "use strict"; 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();