@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
JavaScript
#!/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();