Skip to content

Commit

Permalink
Refactor 2
Browse files Browse the repository at this point in the history
  • Loading branch information
yahavi committed Oct 9, 2023
1 parent eb968d6 commit 0f91072
Show file tree
Hide file tree
Showing 24 changed files with 723 additions and 757 deletions.
32 changes: 2 additions & 30 deletions src/main/scanLogic/scanManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,12 @@ import { LogManager } from '../log/logManager';

import { IGraphResponse, XrayScanProgress } from 'jfrog-client-js';
import { RootNode } from '../treeDataProviders/dependenciesTree/dependenciesRoot/rootTree';
import { AnalyzerUtils } from '../treeDataProviders/utils/analyzerUtils';
import { StepProgress } from '../treeDataProviders/utils/stepProgress';
import { Configuration } from '../utils/configuration';
import { Resource } from '../utils/resource';
import { ScanUtils } from '../utils/scanUtils';
import { Utils } from '../utils/utils';
import { GraphScanLogic } from './scanGraphLogic';
import { ApplicabilityRunner, ApplicabilityScanResponse } from './scanRunners/applicabilityScan';
import { JasScanner } from './scanRunners/binaryRunner';
import { JasRunner } from './scanRunners/jasRunner';

export interface EntitledScans {
dependencies: boolean;
Expand Down Expand Up @@ -128,7 +125,7 @@ export class ScanManager implements ExtensionComponent {
private getResources(supportedScans: EntitledScans): Resource[] {
let resources: Resource[] = [];
if (supportedScans.applicability || supportedScans.iac || supportedScans.secrets) {
resources.push(JasScanner.getAnalyzerManagerResource(this._logManager));
resources.push(JasRunner.getAnalyzerManagerResource(this._logManager));
} else {
this.logManager.logMessage('You are not entitled to run Advanced Security scans', 'DEBUG');
}
Expand Down Expand Up @@ -208,29 +205,4 @@ export class ScanManager implements ExtensionComponent {
let scanLogic: GraphScanLogic = new GraphScanLogic(this._connectionManager);
return await scanLogic.scan(graphRoot, progress, checkCanceled);
}

/**
* Scan CVE in files for applicability issues.
* @param directory - the directory that will be scan
* @param checkCancel - check if should cancel
* @param cveToRun - the CVE list we want to run applicability scan on
* @returns the applicability scan response
*/
public async scanApplicability(
directory: string,
checkCancel: () => void,
cveToRun: Set<string> = new Set<string>()
): Promise<ApplicabilityScanResponse> {
let applicableRunner: ApplicabilityRunner = new ApplicabilityRunner(this._connectionManager, this._logManager);
if (!applicableRunner.validateSupported()) {
this._logManager.logMessage('Applicability runner could not find binary to run', 'WARN');
return {} as ApplicabilityScanResponse;
}
let skipFiles: string[] = AnalyzerUtils.getAnalyzerManagerExcludePattern(Configuration.getScanExcludePattern());
this._logManager.logMessage(
"Scanning directory '" + directory + "' for CVE issues: " + Array.from(cveToRun.values()) + '. Skipping files: ' + skipFiles,
'DEBUG'
);
return await applicableRunner.scan(directory, checkCancel, cveToRun, skipFiles);
}
}
213 changes: 180 additions & 33 deletions src/main/scanLogic/scanRunners/applicabilityScan.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
import { ConnectionManager } from '../../connect/connectionManager';
import { LogManager } from '../../log/logManager';
import { CveTreeNode } from '../../treeDataProviders/issuesTree/descriptorTree/cveTreeNode';
import { ProjectDependencyTreeNode } from '../../treeDataProviders/issuesTree/descriptorTree/projectDependencyTreeNode';
import { IssueTreeNode } from '../../treeDataProviders/issuesTree/issueTreeNode';
import { AnalyzerUtils } from '../../treeDataProviders/utils/analyzerUtils';
import { StepProgress } from '../../treeDataProviders/utils/stepProgress';
import { Module } from '../../types/jfrogAppsConfig';
import { PackageType } from '../../types/projectType';
import { DependencyScanResults } from '../../types/workspaceIssuesDetails';
import { Configuration } from '../../utils/configuration';
import { Resource } from '../../utils/resource';
import { ScanUtils } from '../../utils/scanUtils';
import { AnalyzeIssue, AnalyzeLocation, AnalyzeScanRequest, AnalyzerScanRun, FileIssues, ScanType } from './analyzerModels';
import { JasScanner } from './binaryRunner';
import { FileScanBundle, ScanUtils } from '../../utils/scanUtils';
import { Utils } from '../../utils/utils';
import { AnalyzeIssue, AnalyzeLocation, AnalyzeScanRequest, AnalyzerScanResponse, AnalyzerScanRun, FileIssues, ScanType } from './analyzerModels';
import { JasRunner } from './jasRunner';

/**
* The request that is sent to the binary to scan applicability
Expand Down Expand Up @@ -36,10 +45,13 @@ export interface CveApplicableDetails {
}

/**
* Describes a runner for the Applicability scan executable file.
* Describes a runner for the Applicability scan.
*/
export class ApplicabilityRunner extends JasScanner {
export class ApplicabilityRunner extends JasRunner {
constructor(
private _bundlesWithIssues: FileScanBundle[],
private _packageType: PackageType,
private _progressManager: StepProgress,
connectionManager: ConnectionManager,
logManager: LogManager,
binary?: Resource,
Expand All @@ -59,48 +71,183 @@ export class ApplicabilityRunner extends JasScanner {
return str.replace('cve_whitelist', 'cve-whitelist');
}

/** @override */
protected logStartScanning(request: ApplicabilityScanArgs): void {
this._logManager.logMessage(
`Scanning directory ' ${request.roots[0]} + ', for ${this._scanType} issues: ${request.cve_whitelist} Skipping folders: ${request.skipped_folders}`,
'DEBUG'
);
}

/**
* Scan for applicability issues
* @param directory - the directory the scan will perform on its files
* @param checkCancel - check if cancel
* @param cvesToRun - the CVEs to run the scan on
* @param skipFolders - the subfolders inside the directory to exclude from the scan
* @returns the response generated from the scan
*/
public async scan(
directory: string,
checkCancel: () => void,
cveToRun: Set<string> = new Set<string>(),
skipFolders: string[] = []
): Promise<ApplicabilityScanResponse> {
const request: ApplicabilityScanArgs = {
type: ScanType.ContextualAnalysis,
roots: [directory],
cve_whitelist: Array.from(cveToRun),
skipped_folders: skipFolders
} as ApplicabilityScanArgs;
return await this.run(checkCancel, request).then(response => this.convertResponse(response?.runs[0]));
public async scan(): Promise<void> {
let filteredBundles: Map<FileScanBundle, Set<string>> = this.filterBundlesWithoutIssuesToScan(this._bundlesWithIssues, this._packageType);
let workspaceToBundles: Map<string, Map<FileScanBundle, Set<string>>> = this.mapBundlesForApplicableScanning(
this._logManager,
filteredBundles
);
if (workspaceToBundles.size == 0) {
return;
}
for (let [workspacePath, bundles] of workspaceToBundles) {
let cveToScan: Set<string> = Utils.combineSets(Array.from(bundles.values()));
// Scan workspace for all cve in relevant bundles
let startApplicableTime: number = Date.now();
let excludePatterns: string[] = AnalyzerUtils.getAnalyzerManagerExcludePatterns(Configuration.getScanExcludePattern());

const request: ApplicabilityScanArgs = {
type: ScanType.ContextualAnalysis,
roots: [workspacePath],
cve_whitelist: Array.from(cveToScan),
skipped_folders: excludePatterns
} as ApplicabilityScanArgs;

this.logStartScanning(request);
let response: AnalyzerScanResponse | undefined = await this.executeRequest(this._progressManager.checkCancel, request);
let applicableIssues: ApplicabilityScanResponse = this.convertResponse(response);
if (applicableIssues?.applicableCve) {
this.transferApplicableResponseToBundles(applicableIssues, bundles, startApplicableTime);
}
}
}

/**
* Filter bundles without direct cve issues, transform the bundle list to have its relevant cve to scan set.
* @param fileScanBundles - Bundles to process and filter if needed
* @param packageType - Package type of the project
* @returns Map of bundles to their set of direct cves issues, with at least one for each bundle
*/
private filterBundlesWithoutIssuesToScan(fileScanBundles: FileScanBundle[], packageType: PackageType): Map<FileScanBundle, Set<string>> {
let filtered: Map<FileScanBundle, Set<string>> = new Map<FileScanBundle, Set<string>>();

for (let fileScanBundle of fileScanBundles) {
if (!(fileScanBundle.dataNode instanceof ProjectDependencyTreeNode)) {
// Filter non dependencies projects
continue;
}
let cvesToScan: Set<string> = new Set<string>();
fileScanBundle.dataNode.issues.forEach((issue: IssueTreeNode) => {
if (!(issue instanceof CveTreeNode) || !issue.cve?.cve) {
return;
}
// For Python projects, all CVEs should be included because in some cases it is impossible to determine whether a dependency is direct.
// Other project types should include only CVEs on direct dependencies.
if (packageType === PackageType.Python || !issue.parent.indirect) {
cvesToScan.add(issue.cve.cve);
}
});
if (cvesToScan.size == 0) {
// Nothing to do in bundle
continue;
}

filtered.set(fileScanBundle, cvesToScan);
}

return filtered;
}

/**
* Create a mapping between a workspace and all the given bundles that relevant to it.
* @param logManager - logger to log added map
* @param filteredBundles - bundles to map
* @returns mapped bundles to similar workspace
*/
private mapBundlesForApplicableScanning(
logManager: LogManager,
filteredBundles: Map<FileScanBundle, Set<string>>
): Map<string, Map<FileScanBundle, Set<string>>> {
let workspaceToScanBundles: Map<string, Map<FileScanBundle, Set<string>>> = new Map<string, Map<FileScanBundle, Set<string>>>();

for (let [fileScanBundle, cvesToScan] of filteredBundles) {
let descriptorIssues: DependencyScanResults = <DependencyScanResults>fileScanBundle.data;
// Map information to similar directory space
let workspacePath: string = AnalyzerUtils.getWorkspacePath(fileScanBundle.dataNode, descriptorIssues.fullPath);
if (!workspaceToScanBundles.has(workspacePath)) {
workspaceToScanBundles.set(workspacePath, new Map<FileScanBundle, Set<string>>());
}
workspaceToScanBundles.get(workspacePath)?.set(fileScanBundle, cvesToScan);
logManager.logMessage('Adding data from descriptor ' + descriptorIssues.fullPath + ' for cve applicability scan', 'INFO');
}

return workspaceToScanBundles;
}

/**
* Transfer and populate information from a given applicable scan to each bundle
* @param applicableIssues - Full scan response with information relevant to all the bundles
* @param bundles - the bundles that will be populated only with their relevant information
* @param startTime - The start time for the applicable scan
*/
private transferApplicableResponseToBundles(
applicableIssues: ApplicabilityScanResponse,
bundles: Map<FileScanBundle, Set<string>>,
startTime: number
) {
for (let [bundle, relevantCve] of bundles) {
let descriptorIssues: DependencyScanResults = <DependencyScanResults>bundle.data;
// Filter only relevant information
descriptorIssues.applicableScanTimestamp = Date.now();
descriptorIssues.applicableIssues = this.filterApplicabilityScanResponse(applicableIssues, relevantCve);
// Populate it in bundle
let issuesCount: number = AnalyzerUtils.populateApplicableIssues(
bundle.rootNode,
<ProjectDependencyTreeNode>bundle.dataNode,
descriptorIssues
);
super.logNumberOfIssues(issuesCount, descriptorIssues.fullPath, startTime, descriptorIssues.applicableScanTimestamp);
bundle.rootNode.apply();
}
}

/**
* For a given full ApplicableScanResponse scan results, filter the results to only contain information relevant to a given cve list
* @param scanResponse - All the applicable information
* @param relevantCve - CVE list to filter information only for them
* @returns ApplicableScanResponse with information relevant only for the given relevant CVEs
*/
private filterApplicabilityScanResponse(scanResponse: ApplicabilityScanResponse, relevantCve: Set<string>): ApplicabilityScanResponse {
let allApplicable: Map<string, CveApplicableDetails> = new Map<string, CveApplicableDetails>(Object.entries(scanResponse.applicableCve));
let relevantScannedCve: string[] = [];
let relevantApplicableCve: Map<string, CveApplicableDetails> = new Map<string, CveApplicableDetails>();

for (let scannedCve of scanResponse.scannedCve) {
if (relevantCve.has(scannedCve)) {
relevantScannedCve.push(scannedCve);
let potential: CveApplicableDetails | undefined = allApplicable.get(scannedCve);
if (potential) {
relevantApplicableCve.set(scannedCve, potential);
}
}
}
return {
scannedCve: Array.from(relevantScannedCve),
applicableCve: Object.fromEntries(relevantApplicableCve.entries())
} as ApplicabilityScanResponse;
}

/**
* Generate response from the run results
* @param run - the run results generated from the binary
* @param response - The run results generated from the binary
* @returns the response generated from the scan run
*/
public convertResponse(run: AnalyzerScanRun | undefined): ApplicabilityScanResponse {
if (!run) {
public convertResponse(response: AnalyzerScanResponse | undefined): ApplicabilityScanResponse {
if (!response) {
return {} as ApplicabilityScanResponse;
}
// Prepare
let analyzerScanRun: AnalyzerScanRun = response.runs[0];
let applicable: Map<string, CveApplicableDetails> = new Map<string, CveApplicableDetails>();
let scanned: Set<string> = new Set<string>();
let rulesFullDescription: Map<string, string> = new Map<string, string>();
for (const rule of run.tool.driver.rules) {
for (const rule of analyzerScanRun.tool.driver.rules) {
if (rule.fullDescription) {
rulesFullDescription.set(rule.id, rule.fullDescription.text);
}
}
let issues: AnalyzeIssue[] = run.results;
let issues: AnalyzeIssue[] = analyzerScanRun.results;
if (issues) {
// Generate applicable data for all the issues
issues.forEach((analyzeIssue: AnalyzeIssue) => {
Expand All @@ -127,8 +274,8 @@ export class ApplicabilityRunner extends JasScanner {

/**
* Get or create if not exists file evidence from the CVE applicable issues
* @param applicableDetails - the CVE applicable issues with the file list
* @param filePath - the file to search or create if not exist
* @param applicableDetails - The CVE applicable issues with the file list
* @param filePath - The file to search or create if not exist
* @returns the object that represents the issues in a file for the CVE
*/
private getOrCreateFileIssues(applicableDetails: CveApplicableDetails, filePath: string): FileIssues {
Expand All @@ -149,9 +296,9 @@ export class ApplicabilityRunner extends JasScanner {

/**
* Get or create CVE applicable issue if not exists from the applicable list.
* @param analyzedIssue - the applicable issue to generate information from
* @param applicable - the list of all the applicable CVEs
* @param fullDescription - the full description of the applicable issue
* @param analyzedIssue - Applicable issue to generate information from
* @param applicable - List of all the applicable CVEs
* @param fullDescription - Full description of the applicable issue
* @returns the CveApplicableDetails object for the analyzedIssue CVE
*/
private getOrCreateApplicableDetails(
Expand Down
Loading

0 comments on commit 0f91072

Please sign in to comment.