UNPKG

@soos-io/soos-sca

Version:

SOOS Core SCA Security Analysis - Check for vulnerabilities, licenses, policy violations and more! Register for your free trial at https://app.soos.io/register

275 lines (274 loc) 15.4 kB
#!/usr/bin/env node "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const Path = tslib_1.__importStar(require("path")); const package_json_1 = require("../package.json"); const api_client_1 = require("@soos-io/api-client"); const utilities_1 = require("@soos-io/api-client/dist/utilities"); const constants_1 = require("./constants"); const process_1 = require("process"); const AnalysisService_1 = tslib_1.__importDefault(require("@soos-io/api-client/dist/services/AnalysisService")); const AnalysisArgumentParser_1 = tslib_1.__importDefault(require("@soos-io/api-client/dist/services/AnalysisArgumentParser")); const utilities_2 = require("./utilities"); const enums_1 = require("@soos-io/api-client/dist/enums"); class SOOSSCAAnalysis { args; constructor(args) { this.args = args; } static parseArgs() { const analysisArgumentParser = AnalysisArgumentParser_1.default.create(api_client_1.IntegrationName.SoosSca, api_client_1.IntegrationType.Script, api_client_1.ScanType.SCA, package_json_1.version); analysisArgumentParser.addArgument("directoriesToExclude", "Listing of directories or patterns to exclude from the search for manifest files. eg: **bin/start/**, **/start/**", { argParser: (value) => { return (0, utilities_2.removeDuplicates)(value.split(",").map((pattern) => pattern.trim())); }, defaultValue: constants_1.SOOS_SCA_CONSTANTS.DefaultDirectoriesToExclude, }); analysisArgumentParser.addArgument("filesToExclude", "Listing of files or patterns patterns to exclude from the search for manifest files. eg: **/req**.txt/, **/requirements.txt", { argParser: (value) => { return value.split(",").map((pattern) => pattern.trim()); }, }); analysisArgumentParser.addEnumArgument("fileMatchType", enums_1.FileMatchTypeEnum, "The method to use to locate files for scanning, looking for manifest files and/or files to hash.", { defaultValue: enums_1.FileMatchTypeEnum.Manifest, }); analysisArgumentParser.addEnumArgument("packageManagers", { [api_client_1.PackageManagerType.CFamily]: api_client_1.PackageManagerType.CFamily, [api_client_1.PackageManagerType.Dart]: api_client_1.PackageManagerType.Dart, [api_client_1.PackageManagerType.Erlang]: api_client_1.PackageManagerType.Erlang, [api_client_1.PackageManagerType.Go]: api_client_1.PackageManagerType.Go, [api_client_1.PackageManagerType.Homebrew]: api_client_1.PackageManagerType.Homebrew, [api_client_1.PackageManagerType.Java]: api_client_1.PackageManagerType.Java, [api_client_1.PackageManagerType.NPM]: api_client_1.PackageManagerType.NPM, [api_client_1.PackageManagerType.NuGet]: api_client_1.PackageManagerType.NuGet, [api_client_1.PackageManagerType.Php]: api_client_1.PackageManagerType.Php, [api_client_1.PackageManagerType.Python]: api_client_1.PackageManagerType.Python, [api_client_1.PackageManagerType.Ruby]: api_client_1.PackageManagerType.Ruby, [api_client_1.PackageManagerType.Rust]: api_client_1.PackageManagerType.Rust, [api_client_1.PackageManagerType.Swift]: api_client_1.PackageManagerType.Swift, [api_client_1.PackageManagerType.Unity]: api_client_1.PackageManagerType.Unity, }, "A list of package managers, delimited by comma, to include when searching for manifest files.", { allowMultipleValues: true, }); analysisArgumentParser.addArgument("sourceCodePath", "Root path to begin recursive search for manifests.", { defaultValue: process.cwd(), }); analysisArgumentParser.addArgument("workingDirectory", "Absolute path where SOOS may write and read persistent files for the given build. eg Correct: /tmp/workspace/ | Incorrect: ./bin/start/", { defaultValue: process.cwd(), }); 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.SCA; const analysisService = AnalysisService_1.default.create(this.args.apiKey, this.args.apiURL); let projectHash; let branchHash; let analysisId; let scanStatusUrl; let scanStatus; try { const result = await analysisService.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: !this.args.contributingDeveloperId || !this.args.contributingDeveloperSource || !this.args.contributingDeveloperSourceName ? [] : [ { contributingDeveloperId: this.args.contributingDeveloperId, source: this.args.contributingDeveloperSource, sourceName: this.args.contributingDeveloperSourceName, }, ], scanType, }); projectHash = result.projectHash; branchHash = result.branchHash; analysisId = result.analysisId; scanStatusUrl = result.scanStatusUrl; api_client_1.soosLogger.logLineSeparator(); const manifestsAndHashableFiles = await analysisService.findManifestsAndHashableFiles({ clientId: this.args.clientId, projectHash, filesToExclude: this.args.filesToExclude, directoriesToExclude: this.args.directoriesToExclude, sourceCodePath: this.args.sourceCodePath, packageManagers: this.args.packageManagers ?? [], fileMatchType: this.args.fileMatchType, }); const manifestFiles = manifestsAndHashableFiles.manifestFiles ?? []; const soosHashesManifests = manifestsAndHashableFiles.hashManifests ?? []; let errorMessage = null; if (this.args.fileMatchType === enums_1.FileMatchTypeEnum.Manifest && manifestFiles.length === 0) { errorMessage = "No valid files found, cannot continue. For more help, please visit https://kb.soos.io/error-no-valid-manifests-found"; } if (this.args.fileMatchType === enums_1.FileMatchTypeEnum.FileHash && soosHashesManifests.length === 0) { errorMessage = "No valid files to hash were found, cannot continue. For more help, please visit https://kb.soos.io/error-no-valid-files-to-hash-found"; } if (this.args.fileMatchType === enums_1.FileMatchTypeEnum.ManifestAndFileHash && soosHashesManifests.length === 0 && manifestFiles.length === 0) { errorMessage = "No valid files found, cannot continue. For more help, please visit https://kb.soos.io/error-no-valid-manifests-found and https://kb.soos.io/error-no-valid-files-to-hash-found"; } if (errorMessage) { await analysisService.updateScanStatus({ clientId: this.args.clientId, projectHash, branchHash, scanType, analysisId: analysisId, status: api_client_1.ScanStatus.Incomplete, message: errorMessage, scanStatusUrl: result.scanStatusUrl, }); (0, process_1.exit)(1); } const filesToUpload = manifestFiles.slice(0, api_client_1.SOOS_CONSTANTS.FileUploads.MaxManifests); const hasMoreThanMaximumManifests = manifestFiles.length > api_client_1.SOOS_CONSTANTS.FileUploads.MaxManifests; if (hasMoreThanMaximumManifests) { const filesToSkip = manifestFiles.slice(api_client_1.SOOS_CONSTANTS.FileUploads.MaxManifests); const filesDetectedString = utilities_1.StringUtilities.pluralizeTemplate(manifestFiles.length, "file was", "files were"); const filesSkippedString = utilities_1.StringUtilities.pluralizeTemplate(filesToSkip.length, "file"); api_client_1.soosLogger.info(`The maximum number of manifest per scan is ${api_client_1.SOOS_CONSTANTS.FileUploads.MaxManifests}. ${filesDetectedString} detected, and ${filesSkippedString} will be not be uploaded. \n`, `The following manifests will not be included in the scan: \n`, filesToSkip.map((file) => ` "${file.name}": "${file.path}"`).join("\n")); } const manifestsByPackageManager = filesToUpload.reduce((accumulator, file) => { const packageManagerFiles = accumulator[file.packageManager] ?? []; return { ...accumulator, [file.packageManager]: packageManagerFiles.concat(file), }; }, {}); let allUploadsFailed = true; for (const [packageManager, files] of Object.entries(manifestsByPackageManager)) { try { const manifestUploadResponse = await this.uploadManifestFiles({ analysisService, clientId: this.args.clientId, projectHash, branchHash, analysisId, manifestFiles: files.map((f) => f.path), hasMoreThanMaximumManifests, }); api_client_1.soosLogger.info(`${packageManager} Manifest Files: \n`, ` ${manifestUploadResponse.message} \n`, manifestUploadResponse.manifests ?.map((m) => ` ${m.name}: ${m.statusMessage}`) .join("\n")); allUploadsFailed = false; } catch (e) { api_client_1.soosLogger.warn(e instanceof Error ? e.message : e); } } if (allUploadsFailed) { await analysisService.updateScanStatus({ clientId: this.args.clientId, projectHash, branchHash, scanType, analysisId: analysisId, status: api_client_1.ScanStatus.Incomplete, message: `Error uploading manifests.`, scanStatusUrl: result.scanStatusUrl, }); (0, process_1.exit)(1); } api_client_1.soosLogger.logLineSeparator(); await analysisService.startScan({ clientId: this.args.clientId, projectHash, analysisId: result.analysisId, scanType, scanUrl: result.scanUrl, }); scanStatus = await analysisService.waitForScanToFinish({ scanStatusUrl: result.scanStatusUrl, scanUrl: result.scanUrl, scanType, }); if ((0, utilities_1.isScanDone)(scanStatus) && this.args.exportFormat !== enums_1.AttributionFormatEnum.Unknown && this.args.exportFileType !== enums_1.AttributionFileTypeEnum.Unknown) { await analysisService.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 analysisService.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 uploadManifestFiles({ analysisService, clientId, projectHash, branchHash, analysisId, manifestFiles, hasMoreThanMaximumManifests, }) { const formData = await analysisService.getAnalysisFilesAsFormData(manifestFiles, Path.resolve(this.args.sourceCodePath)); const response = await analysisService.analysisApiClient.uploadManifestFiles({ clientId, projectHash, branchHash, analysisId, manifestFiles: formData, hasMoreThanMaximumManifests, }); return response; } static async createAndRun() { try { const args = this.parseArgs(); api_client_1.soosLogger.setMinLogLevel(args.logLevel); api_client_1.soosLogger.always("Starting SOOS SCA Analysis"); api_client_1.soosLogger.debug(JSON.stringify((0, utilities_1.obfuscateProperties)(args, ["apiKey"]), null, 2)); api_client_1.soosLogger.logLineSeparator(); const soosSCAAnalysis = new SOOSSCAAnalysis(args); await soosSCAAnalysis.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); } } } SOOSSCAAnalysis.createAndRun();