diff --git a/src/cli/commands/test/iac/index.ts b/src/cli/commands/test/iac/index.ts index a9b6e66bae..abcde621ff 100644 --- a/src/cli/commands/test/iac/index.ts +++ b/src/cli/commands/test/iac/index.ts @@ -48,8 +48,9 @@ export default async function(...args: MethodArgs): Promise { isNewIacOutputSupported, }); + const projectRoot = process.cwd(); + printHeader({ - paths, options, isNewIacOutputSupported, }); @@ -67,6 +68,7 @@ export default async function(...args: MethodArgs): Promise { paths, orgPublicId, buildOciRegistry, + projectRoot, ); return buildOutput({ diff --git a/src/cli/commands/test/iac/local-execution/measurable-methods.ts b/src/cli/commands/test/iac/local-execution/measurable-methods.ts index 7fae234170..178d37ff5c 100644 --- a/src/cli/commands/test/iac/local-execution/measurable-methods.ts +++ b/src/cli/commands/test/iac/local-execution/measurable-methods.ts @@ -1,7 +1,6 @@ import { parseFiles } from './file-parser'; import { scanFiles } from './file-scanner'; -import { formatScanResults as formatScanResultsV1 } from './process-results/v1/results-formatter'; -import { formatScanResults as formatScanResultsV2 } from './process-results/v2/results-formatter'; +import { formatScanResults } from './process-results/results-formatter'; import { trackUsage } from './usage-tracking'; import { cleanLocalCache, initLocalCache } from './local-cache'; import { applyCustomSeverities } from './org-settings/apply-custom-severities'; @@ -83,13 +82,8 @@ const measurableCleanLocalCache = performanceAnalyticsDecorator( PerformanceAnalyticsKey.CacheCleanup, ); -const measurableFormatScanResultsV1 = performanceAnalyticsDecorator( - formatScanResultsV1, - PerformanceAnalyticsKey.ResultFormatting, -); - -const measurableFormatScanResultsV2 = performanceAnalyticsDecorator( - formatScanResultsV2, +const measurableFormatScanResults = performanceAnalyticsDecorator( + formatScanResults, PerformanceAnalyticsKey.ResultFormatting, ); @@ -115,8 +109,7 @@ export { measurableScanFiles as scanFiles, measurableGetIacOrgSettings as getIacOrgSettings, measurableApplyCustomSeverities as applyCustomSeverities, - measurableFormatScanResultsV1 as formatScanResultsV1, - measurableFormatScanResultsV2 as formatScanResultsV2, + measurableFormatScanResults as formatScanResultsV2, measurableTrackUsage as trackUsage, measurableCleanLocalCache as cleanLocalCache, measurableLocalTest as localTest, diff --git a/src/cli/commands/test/iac/local-execution/process-results/v2/cli-share-results.ts b/src/cli/commands/test/iac/local-execution/process-results/cli-share-results.ts similarity index 68% rename from src/cli/commands/test/iac/local-execution/process-results/v2/cli-share-results.ts rename to src/cli/commands/test/iac/local-execution/process-results/cli-share-results.ts index 222b7c785c..95a5ee54ba 100644 --- a/src/cli/commands/test/iac/local-execution/process-results/v2/cli-share-results.ts +++ b/src/cli/commands/test/iac/local-execution/process-results/cli-share-results.ts @@ -1,29 +1,26 @@ -import config from '../../../../../../../lib/config'; -import { makeRequest } from '../../../../../../../lib/request'; -import { getAuthHeader } from '../../../../../../../lib/api-token'; +import config from '../../../../../../lib/config'; +import { makeRequest } from '../../../../../../lib/request'; +import { getAuthHeader } from '../../../../../../lib/api-token'; import { IacShareResultsFormat, IaCTestFlags, ShareResultsOutput, -} from '../../types'; -import { convertIacResultToScanResult } from '../../../../../../../lib/iac/envelope-formatters'; -import { Policy } from '../../../../../../../lib/policy/find-and-load-policy'; -import { getInfo } from '../../../../../../../lib/project-metadata/target-builders/git'; -import { GitTarget } from '../../../../../../../lib/ecosystems/types'; -import { Contributor } from '../../../../../../../lib/types'; -import * as analytics from '../../../../../../../lib/analytics'; -import { getContributors } from '../../../../../../../lib/monitor/dev-count-analysis'; +} from '../types'; +import { convertIacResultToScanResult } from '../../../../../../lib/iac/envelope-formatters'; +import { Policy } from '../../../../../../lib/policy/find-and-load-policy'; +import { getInfo } from '../../../../../../lib/project-metadata/target-builders/git'; +import { GitTarget } from '../../../../../../lib/ecosystems/types'; +import { Contributor } from '../../../../../../lib/types'; +import * as analytics from '../../../../../../lib/analytics'; +import { getContributors } from '../../../../../../lib/monitor/dev-count-analysis'; import * as Debug from 'debug'; -import { - AuthFailedError, - ValidationError, -} from '../../../../../../../lib/errors'; +import { AuthFailedError, ValidationError } from '../../../../../../lib/errors'; import * as pathLib from 'path'; const debug = Debug('iac-cli-share-results'); -import { ProjectAttributes, Tag } from '../../../../../../../lib/types'; -import { TestLimitReachedError } from '../../usage-tracking'; -import { getRepositoryRootForPath } from '../../../../../../../lib/iac/git'; +import { ProjectAttributes, Tag } from '../../../../../../lib/types'; +import { TestLimitReachedError } from '../usage-tracking'; +import { getRepositoryRootForPath } from '../../../../../../lib/iac/git'; export async function shareResults({ results, diff --git a/src/cli/commands/test/iac/local-execution/process-results/v1/extract-line-number.ts b/src/cli/commands/test/iac/local-execution/process-results/extract-line-number.ts similarity index 82% rename from src/cli/commands/test/iac/local-execution/process-results/v1/extract-line-number.ts rename to src/cli/commands/test/iac/local-execution/process-results/extract-line-number.ts index 7c8a3c30f1..1ed5045ad7 100644 --- a/src/cli/commands/test/iac/local-execution/process-results/v1/extract-line-number.ts +++ b/src/cli/commands/test/iac/local-execution/process-results/extract-line-number.ts @@ -1,14 +1,14 @@ -import { IaCErrorCodes } from '../../types'; -import { CustomError } from '../../../../../../../lib/errors'; +import { IaCErrorCodes } from '../types'; +import { CustomError } from '../../../../../../lib/errors'; import { CloudConfigFileTypes, MapsDocIdToTree, getLineNumber, } from '@snyk/cloud-config-parser'; -import { UnsupportedFileTypeError } from '../../file-parser'; -import * as analytics from '../../../../../../../lib/analytics'; +import { UnsupportedFileTypeError } from '../file-parser'; +import * as analytics from '../../../../../../lib/analytics'; import * as Debug from 'debug'; -import { getErrorStringCode } from '../../error-utils'; +import { getErrorStringCode } from '../error-utils'; const debug = Debug('iac-extract-line-number'); export function getFileTypeForParser(fileType: string): CloudConfigFileTypes { diff --git a/src/cli/commands/test/iac/local-execution/process-results/index.ts b/src/cli/commands/test/iac/local-execution/process-results/index.ts index d2aa6233bd..a4ac4177a2 100644 --- a/src/cli/commands/test/iac/local-execution/process-results/index.ts +++ b/src/cli/commands/test/iac/local-execution/process-results/index.ts @@ -6,8 +6,7 @@ import { IacOrgSettings, IaCTestFlags, } from '../types'; -import { processResults as processResultsV2 } from './v2'; -import { processResults as processResultsV1 } from './v1'; +import { processResults } from './process-results'; export interface ResultsProcessor { processResults( @@ -35,7 +34,7 @@ export class SingleGroupResultsProcessor implements ResultsProcessor { tags: Tag[] | undefined, attributes: ProjectAttributes | undefined, ): Promise<{ filteredIssues: FormattedResult[]; ignoreCount: number }> { - return processResultsV2( + return processResults( resultsWithCustomSeverities, this.orgPublicId, this.iacOrgSettings, @@ -47,30 +46,3 @@ export class SingleGroupResultsProcessor implements ResultsProcessor { ); } } - -export class MultipleGroupsResultsProcessor implements ResultsProcessor { - constructor( - private pathToScan: string, - private orgPublicId: string, - private iacOrgSettings: IacOrgSettings, - private options: IaCTestFlags, - ) {} - - processResults( - resultsWithCustomSeverities: IacFileScanResult[], - policy: Policy | undefined, - tags: Tag[] | undefined, - attributes: ProjectAttributes | undefined, - ): Promise<{ filteredIssues: FormattedResult[]; ignoreCount: number }> { - return processResultsV1( - resultsWithCustomSeverities, - this.orgPublicId, - this.iacOrgSettings, - policy, - tags, - attributes, - this.options, - this.pathToScan, - ); - } -} diff --git a/src/cli/commands/test/iac/local-execution/process-results/v2/policy.ts b/src/cli/commands/test/iac/local-execution/process-results/policy.ts similarity index 95% rename from src/cli/commands/test/iac/local-execution/process-results/v2/policy.ts rename to src/cli/commands/test/iac/local-execution/process-results/policy.ts index eda5271e5c..d55a1e779a 100644 --- a/src/cli/commands/test/iac/local-execution/process-results/v2/policy.ts +++ b/src/cli/commands/test/iac/local-execution/process-results/policy.ts @@ -1,5 +1,5 @@ -import { FormattedResult, PolicyMetadata } from '../../types'; -import { Policy } from '../../../../../../../lib/policy/find-and-load-policy'; +import { FormattedResult, PolicyMetadata } from '../types'; +import { Policy } from '../../../../../../lib/policy/find-and-load-policy'; export function filterIgnoredIssues( policy: Policy | undefined, diff --git a/src/cli/commands/test/iac/local-execution/process-results/v2/index.ts b/src/cli/commands/test/iac/local-execution/process-results/process-results.ts similarity index 82% rename from src/cli/commands/test/iac/local-execution/process-results/v2/index.ts rename to src/cli/commands/test/iac/local-execution/process-results/process-results.ts index 10c636ed00..cb7654a7fc 100644 --- a/src/cli/commands/test/iac/local-execution/process-results/v2/index.ts +++ b/src/cli/commands/test/iac/local-execution/process-results/process-results.ts @@ -1,14 +1,14 @@ import { filterIgnoredIssues } from './policy'; import { formatAndShareResults } from './share-results'; -import { formatScanResultsV2 } from '../../measurable-methods'; -import { Policy } from '../../../../../../../lib/policy/find-and-load-policy'; -import { ProjectAttributes, Tag } from '../../../../../../../lib/types'; +import { formatScanResultsV2 } from '../measurable-methods'; +import { Policy } from '../../../../../../lib/policy/find-and-load-policy'; +import { ProjectAttributes, Tag } from '../../../../../../lib/types'; import { FormattedResult, IacFileScanResult, IacOrgSettings, IaCTestFlags, -} from '../../types'; +} from '../types'; export async function processResults( resultsWithCustomSeverities: IacFileScanResult[], diff --git a/src/cli/commands/test/iac/local-execution/process-results/v2/results-formatter.ts b/src/cli/commands/test/iac/local-execution/process-results/results-formatter.ts similarity index 94% rename from src/cli/commands/test/iac/local-execution/process-results/v2/results-formatter.ts rename to src/cli/commands/test/iac/local-execution/process-results/results-formatter.ts index 8324f0d481..cd69f5cfdc 100644 --- a/src/cli/commands/test/iac/local-execution/process-results/v2/results-formatter.ts +++ b/src/cli/commands/test/iac/local-execution/process-results/results-formatter.ts @@ -6,22 +6,19 @@ import { IaCTestFlags, PolicyMetadata, TestMeta, -} from '../../types'; -import { - SEVERITY, - SEVERITIES, -} from '../../../../../../../lib/snyk-test/common'; -import { IacProjectType } from '../../../../../../../lib/iac/constants'; -import { CustomError } from '../../../../../../../lib/errors'; +} from '../types'; +import { SEVERITY, SEVERITIES } from '../../../../../../lib/snyk-test/common'; +import { IacProjectType } from '../../../../../../lib/iac/constants'; +import { CustomError } from '../../../../../../lib/errors'; import { extractLineNumber, getFileTypeForParser } from './extract-line-number'; -import { getErrorStringCode } from '../../error-utils'; +import { getErrorStringCode } from '../error-utils'; import { MapsDocIdToTree, getTrees, parsePath, } from '@snyk/cloud-config-parser'; import * as path from 'path'; -import { isLocalFolder } from '../../../../../../../lib/detect'; +import { isLocalFolder } from '../../../../../../lib/detect'; const severitiesArray = SEVERITIES.map((s) => s.verboseName); diff --git a/src/cli/commands/test/iac/local-execution/process-results/v2/share-results-formatter.ts b/src/cli/commands/test/iac/local-execution/process-results/share-results-formatter.ts similarity index 96% rename from src/cli/commands/test/iac/local-execution/process-results/v2/share-results-formatter.ts rename to src/cli/commands/test/iac/local-execution/process-results/share-results-formatter.ts index 5d42aa2de8..9ca79b4521 100644 --- a/src/cli/commands/test/iac/local-execution/process-results/v2/share-results-formatter.ts +++ b/src/cli/commands/test/iac/local-execution/process-results/share-results-formatter.ts @@ -1,4 +1,4 @@ -import { IacFileScanResult, IacShareResultsFormat } from '../../types'; +import { IacFileScanResult, IacShareResultsFormat } from '../types'; import * as path from 'path'; export function formatShareResults( diff --git a/src/cli/commands/test/iac/local-execution/process-results/v2/share-results.ts b/src/cli/commands/test/iac/local-execution/process-results/share-results.ts similarity index 74% rename from src/cli/commands/test/iac/local-execution/process-results/v2/share-results.ts rename to src/cli/commands/test/iac/local-execution/process-results/share-results.ts index 1368dc6837..92ff12fbd1 100644 --- a/src/cli/commands/test/iac/local-execution/process-results/v2/share-results.ts +++ b/src/cli/commands/test/iac/local-execution/process-results/share-results.ts @@ -1,14 +1,10 @@ -import { isFeatureFlagSupportedForOrg } from '../../../../../../../lib/feature-flags'; +import { isFeatureFlagSupportedForOrg } from '../../../../../../lib/feature-flags'; import { shareResults } from './cli-share-results'; -import { Policy } from '../../../../../../../lib/policy/find-and-load-policy'; -import { ProjectAttributes, Tag } from '../../../../../../../lib/types'; -import { FeatureFlagError } from '../../assert-iac-options-flag'; +import { Policy } from '../../../../../../lib/policy/find-and-load-policy'; +import { ProjectAttributes, Tag } from '../../../../../../lib/types'; +import { FeatureFlagError } from '../assert-iac-options-flag'; import { formatShareResults } from './share-results-formatter'; -import { - IacFileScanResult, - IaCTestFlags, - ShareResultsOutput, -} from '../../types'; +import { IacFileScanResult, IaCTestFlags, ShareResultsOutput } from '../types'; export async function formatAndShareResults({ results, diff --git a/src/cli/commands/test/iac/local-execution/process-results/v1/index.ts b/src/cli/commands/test/iac/local-execution/process-results/v1/index.ts deleted file mode 100644 index 6c728797cf..0000000000 --- a/src/cli/commands/test/iac/local-execution/process-results/v1/index.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { filterIgnoredIssues } from './policy'; -import { formatAndShareResults } from './share-results'; -import { formatScanResultsV1 } from '../../measurable-methods'; -import { Policy } from '../../../../../../../lib/policy/find-and-load-policy'; -import { ProjectAttributes, Tag } from '../../../../../../../lib/types'; -import { - FormattedResult, - IacFileScanResult, - IacOrgSettings, - IaCTestFlags, -} from '../../types'; - -export async function processResults( - resultsWithCustomSeverities: IacFileScanResult[], - orgPublicId: string, - iacOrgSettings: IacOrgSettings, - policy: Policy | undefined, - tags: Tag[] | undefined, - attributes: ProjectAttributes | undefined, - options: IaCTestFlags, - pathToScan: string, -): Promise<{ filteredIssues: FormattedResult[]; ignoreCount: number }> { - let projectPublicIds: Record = {}; - let gitRemoteUrl: string | undefined; - - if (options.report) { - ({ projectPublicIds, gitRemoteUrl } = await formatAndShareResults({ - results: resultsWithCustomSeverities, - options, - orgPublicId, - policy, - tags, - attributes, - pathToScan, - })); - } - - const formattedResults = formatScanResultsV1( - resultsWithCustomSeverities, - options, - iacOrgSettings.meta, - projectPublicIds, - gitRemoteUrl, - ); - - return filterIgnoredIssues(policy, formattedResults); -} diff --git a/src/cli/commands/test/iac/local-execution/process-results/v1/policy.ts b/src/cli/commands/test/iac/local-execution/process-results/v1/policy.ts deleted file mode 100644 index eda5271e5c..0000000000 --- a/src/cli/commands/test/iac/local-execution/process-results/v1/policy.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { FormattedResult, PolicyMetadata } from '../../types'; -import { Policy } from '../../../../../../../lib/policy/find-and-load-policy'; - -export function filterIgnoredIssues( - policy: Policy | undefined, - results: FormattedResult[], -) { - if (!policy) { - return { filteredIssues: results, ignoreCount: 0 }; - } - const vulns = results.map((res) => - policy.filter(toIaCVulnAdapter(res), undefined, 'exact'), - ); - const ignoreCount: number = vulns.reduce( - (totalIgnored, vuln) => totalIgnored + vuln.filtered.ignore.length, - 0, - ); - const filteredIssues = vulns.map((vuln) => toFormattedResult(vuln)); - return { filteredIssues, ignoreCount }; -} - -type IacVulnAdapter = { - vulnerabilities: { - id: string; - from: string[]; - }[]; - originalResult: FormattedResult; - filtered?: { ignore: any[] }; -}; - -// This is a total cop-out. The type I really want is AnnotatedIacIssue from -// src/lib/snyk-test/iac-test-result.ts, but that appears to only be used in the -// legacy flow and I gave up on adapting it to work in both flows. By the time -// this code is called, cloudConfigPath is present on the result object. -type AnnotatedResult = PolicyMetadata & { - cloudConfigPath: string[]; -}; - -function toIaCVulnAdapter(result: FormattedResult): IacVulnAdapter { - return { - vulnerabilities: result.result.cloudConfigResults.map( - (cloudConfigResult) => { - const annotatedResult = cloudConfigResult as AnnotatedResult; - - // Copy the cloudConfigPath array to avoid modifying the original with - // splice. - // Insert the targetFile into the path so that it is taken into account - // when determining whether an ignore rule should be applied. - const path = [...annotatedResult.cloudConfigPath]; - path.splice(0, 0, result.targetFile); - - return { - id: cloudConfigResult.id as string, - from: path, - }; - }, - ), - originalResult: result, - }; -} - -function toFormattedResult(adapter: IacVulnAdapter): FormattedResult { - const original = adapter.originalResult; - const filteredCloudConfigResults = original.result.cloudConfigResults.filter( - (res) => { - return adapter.vulnerabilities.some((vuln) => { - if (vuln.id !== res.id) { - return false; - } - - // Unfortunately we are forced to duplicate the logic in - // toIaCVulnAdapter so that we're comparing path components properly, - // including target file context. As that logic changes, so must this. - const annotatedResult = res as AnnotatedResult; - const significantPath = [...annotatedResult.cloudConfigPath]; - significantPath.splice(0, 0, original.targetFile); - - if (vuln.from.length !== significantPath.length) { - return false; - } - for (let i = 0; i < vuln.from.length; i++) { - if (vuln.from[i] !== significantPath[i]) { - return false; - } - } - return true; - }); - }, - ); - original.result.cloudConfigResults = filteredCloudConfigResults; - return original; -} diff --git a/src/cli/commands/test/iac/local-execution/process-results/v1/results-formatter.ts b/src/cli/commands/test/iac/local-execution/process-results/v1/results-formatter.ts deleted file mode 100644 index a8e5cecce0..0000000000 --- a/src/cli/commands/test/iac/local-execution/process-results/v1/results-formatter.ts +++ /dev/null @@ -1,203 +0,0 @@ -import { - EngineType, - FormattedResult, - IaCErrorCodes, - IacFileScanResult, - IaCTestFlags, - PolicyMetadata, - TestMeta, -} from '../../types'; -import { - SEVERITY, - SEVERITIES, -} from '../../../../../../../lib/snyk-test/common'; -import { IacProjectType } from '../../../../../../../lib/iac/constants'; -import { CustomError } from '../../../../../../../lib/errors'; -import { extractLineNumber, getFileTypeForParser } from './extract-line-number'; -import { getErrorStringCode } from '../../error-utils'; -import { - MapsDocIdToTree, - getTrees, - parsePath, -} from '@snyk/cloud-config-parser'; -import * as path from 'path'; -import { isLocalFolder } from '../../../../../../../lib/detect'; - -const severitiesArray = SEVERITIES.map((s) => s.verboseName); - -export function formatScanResults( - scanResults: IacFileScanResult[], - options: IaCTestFlags, - meta: TestMeta, - projectPublicIds: Record, - gitRemoteUrl?: string, -): FormattedResult[] { - try { - const groupedByFile = scanResults.reduce((memo, scanResult) => { - const res = formatScanResult(scanResult, meta, options); - - if (memo[scanResult.filePath]) { - memo[scanResult.filePath].result.cloudConfigResults.push( - ...res.result.cloudConfigResults, - ); - } else { - res.meta.gitRemoteUrl = gitRemoteUrl; - res.meta.projectId = projectPublicIds[res.targetFile]; - memo[scanResult.filePath] = res; - } - return memo; - }, {} as { [key: string]: FormattedResult }); - return Object.values(groupedByFile); - } catch (e) { - throw new FailedToFormatResults(); - } -} - -const engineTypeToProjectType = { - [EngineType.Kubernetes]: IacProjectType.K8S, - [EngineType.Terraform]: IacProjectType.TERRAFORM, - [EngineType.CloudFormation]: IacProjectType.CLOUDFORMATION, - [EngineType.ARM]: IacProjectType.ARM, - [EngineType.Custom]: IacProjectType.CUSTOM, -}; - -function formatScanResult( - scanResult: IacFileScanResult, - meta: TestMeta, - options: IaCTestFlags, -): FormattedResult { - const fileType = getFileTypeForParser(scanResult.fileType); - const isGeneratedByCustomRule = scanResult.engineType === EngineType.Custom; - let treeByDocId: MapsDocIdToTree; - try { - treeByDocId = getTrees(fileType, scanResult.fileContent); - } catch (err) { - // we do nothing intentionally. - // Even if the building of the tree fails in the external parser, - // we still pass an undefined tree and not calculated line number for those - } - - const formattedIssues = scanResult.violatedPolicies.map((policy) => { - const cloudConfigPath = - scanResult.docId !== undefined - ? [`[DocId: ${scanResult.docId}]`].concat(parsePath(policy.msg)) - : policy.msg.split('.'); - - const lineNumber: number = treeByDocId - ? extractLineNumber(cloudConfigPath, fileType, treeByDocId) - : -1; - - return { - ...policy, - id: policy.publicId, - name: policy.title, - cloudConfigPath, - isIgnored: false, - iacDescription: { - issue: policy.issue, - impact: policy.impact, - resolve: policy.resolve, - }, - severity: policy.severity, - lineNumber, - documentation: !isGeneratedByCustomRule - ? `https://snyk.io/security-rules/${policy.publicId}` - : undefined, - isGeneratedByCustomRule, - }; - }); - - const { targetFilePath, projectName, targetFile } = computePaths( - scanResult.filePath, - options.path, - ); - return { - result: { - cloudConfigResults: filterPoliciesBySeverity( - formattedIssues, - options.severityThreshold, - ), - projectType: scanResult.projectType, - }, - meta: { - ...meta, - projectId: '', // we do not have a project at this stage - policy: '', // we do not have the concept of policy - }, - filesystemPolicy: false, // we do not have the concept of policy - vulnerabilities: [], - dependencyCount: 0, - licensesPolicy: null, // we do not have the concept of license policies - ignoreSettings: null, - targetFile, - projectName, - org: meta.org, - policy: '', // we do not have the concept of policy - isPrivate: true, - targetFilePath, - packageManager: engineTypeToProjectType[scanResult.engineType], - }; -} - -export function filterPoliciesBySeverity( - violatedPolicies: PolicyMetadata[], - severityThreshold?: SEVERITY, -): PolicyMetadata[] { - if (!severityThreshold || severityThreshold === SEVERITY.LOW) { - return violatedPolicies.filter((violatedPolicy) => { - return violatedPolicy.severity !== 'none'; - }); - } - - const severitiesToInclude = severitiesArray.slice( - severitiesArray.indexOf(severityThreshold), - ); - return violatedPolicies.filter((policy) => { - return ( - policy.severity !== 'none' && - severitiesToInclude.includes(policy.severity) - ); - }); -} - -export class FailedToFormatResults extends CustomError { - constructor(message?: string) { - super(message || 'Failed to format results'); - this.code = IaCErrorCodes.FailedToFormatResults; - this.strCode = getErrorStringCode(this.code); - this.userMessage = - 'We failed printing the results, please contact support@snyk.io'; - } -} - -function computePaths( - filePath: string, - pathArg = '.', -): { targetFilePath: string; projectName: string; targetFile: string } { - const targetFilePath = path.resolve(filePath, '.'); - - // the absolute path is needed to compute the full project path - const cmdPath = path.resolve(pathArg); - - let projectPath: string; - let targetFile: string; - if (!isLocalFolder(cmdPath)) { - // if the provided path points to a file, then the project starts at the parent folder of that file - // and the target file was provided as the path argument - projectPath = path.dirname(cmdPath); - targetFile = path.isAbsolute(pathArg) - ? path.relative(process.cwd(), pathArg) - : pathArg; - } else { - // otherwise, the project starts at the provided path - // and the target file must be the relative path from the project path to the path of the scanned file - projectPath = cmdPath; - targetFile = path.relative(projectPath, targetFilePath); - } - - return { - targetFilePath, - projectName: path.basename(projectPath), - targetFile, - }; -} diff --git a/src/cli/commands/test/iac/local-execution/process-results/v1/share-results-formatter.ts b/src/cli/commands/test/iac/local-execution/process-results/v1/share-results-formatter.ts deleted file mode 100644 index 4855b3c12e..0000000000 --- a/src/cli/commands/test/iac/local-execution/process-results/v1/share-results-formatter.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { IacFileScanResult, IacShareResultsFormat } from '../../types'; -import * as path from 'path'; -import { - getRepositoryRootForPath, - getWorkingDirectoryForPath, -} from '../../../../../../../lib/iac/git'; - -export function formatShareResults( - scanResults: IacFileScanResult[], -): IacShareResultsFormat[] { - const resultsGroupedByFilePath = groupByFilePath(scanResults); - - return resultsGroupedByFilePath.map((result) => { - const { projectName, targetFile } = computePaths(result.filePath); - - return { - projectName, - targetFile, - filePath: result.filePath, - fileType: result.fileType, - projectType: result.projectType, - violatedPolicies: result.violatedPolicies, - }; - }); -} - -function groupByFilePath(scanResults: IacFileScanResult[]) { - const groupedByFilePath = scanResults.reduce((memo, scanResult) => { - scanResult.violatedPolicies.forEach((violatedPolicy) => { - violatedPolicy.docId = scanResult.docId; - }); - if (memo[scanResult.filePath]) { - memo[scanResult.filePath].violatedPolicies.push( - ...scanResult.violatedPolicies, - ); - } else { - memo[scanResult.filePath] = scanResult; - } - return memo; - }, {} as Record); - - return Object.values(groupedByFilePath); -} - -function computePaths( - filePath: string, -): { targetFilePath: string; projectName: string; targetFile: string } { - const currentDirectory = getGitRootOrCwd(path.resolve(filePath)); - const currentDirectoryName = path.basename(currentDirectory); - const absoluteFilePath = path.resolve(filePath); - const relativeFilePath = path.relative(currentDirectory, absoluteFilePath); - const unixRelativeFilePath = relativeFilePath.split(path.sep).join('/'); - - return { - targetFilePath: absoluteFilePath, - projectName: currentDirectoryName, - targetFile: unixRelativeFilePath, - }; -} - -function getGitRootOrCwd(currentPath: string): string { - try { - return getRepositoryRootForPath(currentPath); - } catch (e) { - return getWorkingDirectoryForPath(currentPath); - } -} diff --git a/src/cli/commands/test/iac/local-execution/process-results/v1/share-results.ts b/src/cli/commands/test/iac/local-execution/process-results/v1/share-results.ts deleted file mode 100644 index 138f8e3ced..0000000000 --- a/src/cli/commands/test/iac/local-execution/process-results/v1/share-results.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { isFeatureFlagSupportedForOrg } from '../../../../../../../lib/feature-flags'; -import { shareResults } from '../../../../../../../lib/iac/cli-share-results'; -import { Policy } from '../../../../../../../lib/policy/find-and-load-policy'; -import { ProjectAttributes, Tag } from '../../../../../../../lib/types'; -import { FeatureFlagError } from '../../assert-iac-options-flag'; -import { formatShareResults } from './share-results-formatter'; -import { - IacFileScanResult, - IaCTestFlags, - ShareResultsOutput, -} from '../../types'; - -export async function formatAndShareResults({ - results, - options, - orgPublicId, - policy, - tags, - attributes, - pathToScan, -}: { - results: IacFileScanResult[]; - options: IaCTestFlags; - orgPublicId: string; - policy: Policy | undefined; - tags?: Tag[]; - attributes?: ProjectAttributes; - pathToScan: string; -}): Promise { - const isCliReportEnabled = await isFeatureFlagSupportedForOrg( - 'iacCliShareResults', - orgPublicId, - ); - if (!isCliReportEnabled.ok) { - throw new FeatureFlagError('report', 'iacCliShareResults'); - } - - const formattedResults = formatShareResults(results); - - return await shareResults({ - results: formattedResults, - policy, - tags, - attributes, - options, - pathToScan, - }); -} diff --git a/src/cli/commands/test/iac/local-execution/process-results/v2/extract-line-number.ts b/src/cli/commands/test/iac/local-execution/process-results/v2/extract-line-number.ts deleted file mode 100644 index 7c8a3c30f1..0000000000 --- a/src/cli/commands/test/iac/local-execution/process-results/v2/extract-line-number.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { IaCErrorCodes } from '../../types'; -import { CustomError } from '../../../../../../../lib/errors'; -import { - CloudConfigFileTypes, - MapsDocIdToTree, - getLineNumber, -} from '@snyk/cloud-config-parser'; -import { UnsupportedFileTypeError } from '../../file-parser'; -import * as analytics from '../../../../../../../lib/analytics'; -import * as Debug from 'debug'; -import { getErrorStringCode } from '../../error-utils'; -const debug = Debug('iac-extract-line-number'); - -export function getFileTypeForParser(fileType: string): CloudConfigFileTypes { - switch (fileType) { - case 'yaml': - case 'yml': - return CloudConfigFileTypes.YAML; - case 'json': - return CloudConfigFileTypes.JSON; - case 'tf': - return CloudConfigFileTypes.TF; - default: - throw new UnsupportedFileTypeError(fileType); - } -} - -export function extractLineNumber( - cloudConfigPath: string[], - fileType: CloudConfigFileTypes, - treeByDocId: MapsDocIdToTree, -): number { - try { - return getLineNumber(cloudConfigPath, fileType, treeByDocId); - } catch { - const err = new FailedToExtractLineNumberError(); - analytics.add('error-code', err.code); - debug('Parser library failed. Could not assign lineNumber to issue'); - return -1; - } -} - -class FailedToExtractLineNumberError extends CustomError { - constructor(message?: string) { - super( - message || 'Parser library failed. Could not assign lineNumber to issue', - ); - this.code = IaCErrorCodes.FailedToExtractLineNumberError; - this.strCode = getErrorStringCode(this.code); - this.userMessage = ''; // Not a user facing error. - } -} diff --git a/src/cli/commands/test/iac/output.ts b/src/cli/commands/test/iac/output.ts index a5b51cd3fe..ec3c56cb18 100644 --- a/src/cli/commands/test/iac/output.ts +++ b/src/cli/commands/test/iac/output.ts @@ -37,7 +37,6 @@ import { Options, TestOptions, } from '../../../../lib/types'; -import * as path from 'path'; import { IaCTestFlags } from './local-execution/types'; import { shareCustomRulesDisclaimer, @@ -61,20 +60,14 @@ export function buildSpinner({ } export function printHeader({ - paths, options, isNewIacOutputSupported, }: { - paths: string[]; options: Options & TestOptions; isNewIacOutputSupported?: boolean; }) { if (shouldLogUserMessages(options, isNewIacOutputSupported)) { console.log(EOL + iacTestTitle + EOL); - - if (paths.some(isOutsideCurrentWorkingDirectory)) { - printCurrentWorkingDirectoryTraversalWarning(); - } } } @@ -323,27 +316,6 @@ export function buildOutput({ ); } -function isOutsideCurrentWorkingDirectory(p: string): boolean { - return path.relative(process.cwd(), p).includes('..'); -} - -function printCurrentWorkingDirectoryTraversalWarning() { - let msg = ''; - - msg += - 'Warning: Scanning paths outside the current working directory is deprecated and' + - EOL; - msg += - 'will be removed in the future. Please see the documentation for further details:' + - EOL + - EOL; - msg += - ' https://docs.snyk.io/products/snyk-infrastructure-as-code/snyk-cli-for-infrastructure-as-code/test-your-configuration-files' + - EOL; - - console.log(chalk.yellow(msg)); -} - function buildShareResultsSummary({ iacOutputMeta, options, diff --git a/src/cli/commands/test/iac/scan.ts b/src/cli/commands/test/iac/scan.ts index a8043cdec8..3512cc0da7 100644 --- a/src/cli/commands/test/iac/scan.ts +++ b/src/cli/commands/test/iac/scan.ts @@ -21,11 +21,7 @@ import { IaCErrorCodes, IacOrgSettings } from './local-execution/types'; import * as pathLib from 'path'; import { CustomError } from '../../../../lib/errors'; import { OciRegistry } from './local-execution/rules/oci-registry'; -import { - MultipleGroupsResultsProcessor, - ResultsProcessor, - SingleGroupResultsProcessor, -} from './local-execution/process-results'; +import { SingleGroupResultsProcessor } from './local-execution/process-results'; import { getErrorStringCode } from './local-execution/error-utils'; export async function scan( @@ -35,7 +31,7 @@ export async function scan( paths: string[], orgPublicId: string, buildOciRules: () => OciRegistry, - projectRoot?: string, + projectRoot: string, ): Promise<{ iacOutputMeta: IacOutputMeta | undefined; iacScanFailures: IacFileInDirectory[]; @@ -72,28 +68,17 @@ export async function scan( try { assertIaCOptionsFlags(process.argv); - let resultsProcessor: ResultsProcessor; - - if (projectRoot) { - if (pathLib.relative(projectRoot, path).includes('..')) { - throw new CurrentWorkingDirectoryTraversalError(); - } - - resultsProcessor = new SingleGroupResultsProcessor( - projectRoot, - orgPublicId, - iacOrgSettings, - testOpts, - ); - } else { - resultsProcessor = new MultipleGroupsResultsProcessor( - path, - orgPublicId, - iacOrgSettings, - testOpts, - ); + if (pathLib.relative(projectRoot, path).includes('..')) { + throw new CurrentWorkingDirectoryTraversalError(path); } + const resultsProcessor = new SingleGroupResultsProcessor( + projectRoot, + orgPublicId, + iacOrgSettings, + testOpts, + ); + const { results, failures, ignoreCount } = await iacTest( resultsProcessor, path, @@ -173,10 +158,13 @@ function formatTestError(error) { } class CurrentWorkingDirectoryTraversalError extends CustomError { - constructor() { + public filename: string; + + constructor(path: string) { super('Path is outside the current working directory'); this.code = IaCErrorCodes.CurrentWorkingDirectoryTraversalError; this.strCode = getErrorStringCode(this.code); this.userMessage = `Path is outside the current working directory`; + this.filename = path; } } diff --git a/src/cli/commands/test/iac/v2/index.ts b/src/cli/commands/test/iac/v2/index.ts index d12d7a5f90..96089500cf 100644 --- a/src/cli/commands/test/iac/v2/index.ts +++ b/src/cli/commands/test/iac/v2/index.ts @@ -23,7 +23,6 @@ export async function test( }); printHeader({ - paths, options, isNewIacOutputSupported: true, }); diff --git a/test/jest/acceptance/iac/cli-share-results.spec.ts b/test/jest/acceptance/iac/cli-share-results.spec.ts index fbdc5b078d..f03a57697a 100644 --- a/test/jest/acceptance/iac/cli-share-results.spec.ts +++ b/test/jest/acceptance/iac/cli-share-results.spec.ts @@ -1,10 +1,5 @@ import { FakeServer } from '../../../acceptance/fake-server'; import { startMockServer } from './helpers'; -import * as path from 'path'; - -const projectDirectoryName = path.basename( - path.resolve(__dirname, '..', '..', '..', '..'), -); jest.setTimeout(50000); @@ -56,8 +51,9 @@ describe('CLI Share Results', () => { ); expect(stdout).toContain( - `Your test results are available at: http://localhost:${server.getPort()}/org/test-org/projects under the name snyk/cli`, + `Your test results are available at: http://localhost:${server.getPort()}/org/test-org/projects`, ); + expect(stdout).toContain('under the name fixtures'); expect(exitCode).toBe(1); }); @@ -78,14 +74,14 @@ describe('CLI Share Results', () => { { identity: { type: 'armconfig', - targetFile: 'test/fixtures/iac/arm/rule_test.json', + targetFile: 'iac/arm/rule_test.json', }, facts: [], findings: expect.any(Array), policy: '', - name: projectDirectoryName, + name: 'fixtures', target: { - remoteUrl: 'http://github.com/snyk/cli.git', + name: 'fixtures', }, }, ], @@ -203,13 +199,13 @@ describe('CLI Share Results', () => { { identity: { type: 'armconfig', - targetFile: 'test/fixtures/iac/arm/rule_test.json', + targetFile: 'iac/arm/rule_test.json', }, facts: [], findings: expect.arrayContaining([]), - name: projectDirectoryName, + name: 'fixtures', target: { - remoteUrl: 'http://github.com/snyk/cli.git', + name: 'fixtures', }, targetReference: testTargetRef, }, diff --git a/test/jest/acceptance/iac/file-output.spec.ts b/test/jest/acceptance/iac/file-output.spec.ts index 8e3e54df7d..c7a565bb5c 100644 --- a/test/jest/acceptance/iac/file-output.spec.ts +++ b/test/jest/acceptance/iac/file-output.spec.ts @@ -52,7 +52,7 @@ describe('iac test --json-file-output', () => { path.resolve('./test/fixtures/iac/file-output/sg_open_ssh.tf'), ); expect(actualTargetFile).toEqual('./iac/file-output/sg_open_ssh.tf'); - expect(actualProjectName).toEqual('file-output'); + expect(actualProjectName).toEqual('fixtures'); }); }); diff --git a/test/jest/acceptance/iac/github-action/iac.spec.ts b/test/jest/acceptance/iac/github-action/iac.spec.ts index 0d63117a75..2c96619594 100644 --- a/test/jest/acceptance/iac/github-action/iac.spec.ts +++ b/test/jest/acceptance/iac/github-action/iac.spec.ts @@ -29,10 +29,6 @@ describe('GitHub action - IaC', () => { relativeDir: 'iac', inputPath: '', // current directory provided by default }, - { - relativeDir: 'iac/file-output', - inputPath: '../../iac', // one folder up - }, ])('when provided config: %j', ({ relativeDir, inputPath }) => { let githubActionTestRunner: GithubActionTestRunner; diff --git a/test/jest/acceptance/iac/iac-output.spec.ts b/test/jest/acceptance/iac/iac-output.spec.ts index 4ad0136677..7afa2d2281 100644 --- a/test/jest/acceptance/iac/iac-output.spec.ts +++ b/test/jest/acceptance/iac/iac-output.spec.ts @@ -321,7 +321,7 @@ Target file: ${dirPath}/`); Organization: test-org Type: ARM Target file: ${filePath} -Project name: arm +Project name: fixtures Open source: no Project path: ${filePath} `); diff --git a/test/jest/unit/iac/index.spec.ts b/test/jest/unit/iac/index.spec.ts index 0a54cf5d71..7dcc2266ff 100644 --- a/test/jest/unit/iac/index.spec.ts +++ b/test/jest/unit/iac/index.spec.ts @@ -53,7 +53,7 @@ import { } from '../../../../src/cli/commands/test/iac/local-execution/types'; import { IacProjectType } from '../../../../src/lib/iac/constants'; -import { MultipleGroupsResultsProcessor } from '../../../../src/cli/commands/test/iac/local-execution/process-results'; +import { SingleGroupResultsProcessor } from '../../../../src/cli/commands/test/iac/local-execution/process-results'; const parsedFiles: IacFileParsed[] = [ { engineType: EngineType.Terraform, @@ -105,7 +105,7 @@ describe('test()', () => { it('returns the unparsable files excluding content', async () => { const opts: IaCTestFlags = {}; - const resultsProcessor = new MultipleGroupsResultsProcessor( + const resultsProcessor = new SingleGroupResultsProcessor( './storage/', 'org-name', iacOrgSettings, diff --git a/test/jest/unit/iac/process-results/policy.spec.ts b/test/jest/unit/iac/process-results/policy.spec.ts index 49a1a8962c..9c9551ce3b 100644 --- a/test/jest/unit/iac/process-results/policy.spec.ts +++ b/test/jest/unit/iac/process-results/policy.spec.ts @@ -1,4 +1,4 @@ -import { filterIgnoredIssues } from '../../../../../src/cli/commands/test/iac/local-execution/process-results/v1/policy'; +import { filterIgnoredIssues } from '../../../../../src/cli/commands/test/iac/local-execution/process-results/policy'; import { FormattedResult } from '../../../../../src/cli/commands/test/iac/local-execution/types'; import * as fs from 'fs'; import * as path from 'path'; diff --git a/test/jest/unit/iac/process-results/results-formatter.spec.ts b/test/jest/unit/iac/process-results/results-formatter.spec.ts index 121b191bcd..bf96099f7b 100644 --- a/test/jest/unit/iac/process-results/results-formatter.spec.ts +++ b/test/jest/unit/iac/process-results/results-formatter.spec.ts @@ -1,7 +1,7 @@ import { filterPoliciesBySeverity, formatScanResults, -} from '../../../../../src/cli/commands/test/iac/local-execution/process-results/v1/results-formatter'; +} from '../../../../../src/cli/commands/test/iac/local-execution/process-results/results-formatter'; import { SEVERITY } from '../../../../../src/lib/snyk-test/common'; import { expectedFormattedResultsWithLineNumber, @@ -83,6 +83,7 @@ describe('formatScanResults', () => { optionsObject.formatOptions, meta, {}, + process.cwd(), ); expect(formattedResults.length).toEqual(1); @@ -108,6 +109,7 @@ describe('parser failures should return -1 for lineNumber', () => { { severityThreshold: SEVERITY.HIGH }, meta, {}, + process.cwd(), ); expect(formattedResults.length).toEqual(1); @@ -125,6 +127,7 @@ describe('parser failures should return -1 for lineNumber', () => { { severityThreshold: SEVERITY.HIGH }, meta, {}, + process.cwd(), ); expect(formattedResults.length).toEqual(1); diff --git a/test/jest/unit/iac/process-results/share-results-formatters.spec.ts b/test/jest/unit/iac/process-results/share-results-formatters.spec.ts index 0f5eb5df8f..f436cf1948 100644 --- a/test/jest/unit/iac/process-results/share-results-formatters.spec.ts +++ b/test/jest/unit/iac/process-results/share-results-formatters.spec.ts @@ -1,4 +1,4 @@ -import { formatShareResults } from '../../../../../src/cli/commands/test/iac/local-execution/process-results/v1/share-results-formatter'; +import { formatShareResults } from '../../../../../src/cli/commands/test/iac/local-execution/process-results/share-results-formatter'; import { generateScanResults } from '../results-formatter.fixtures'; import { expectedFormattedResultsForShareResults } from './share-results-formatters.fixtures'; import * as git from '../../../../../src/lib/iac/git'; @@ -12,6 +12,7 @@ describe('formatShareResults', () => { it('returns the formatted results', () => { const IacShareResultsFormatResults = formatShareResults( + process.cwd(), generateScanResults(), ); expect(IacShareResultsFormatResults).toStrictEqual(