diff --git a/src/cli/commands/test/iac/local-execution/file-parser.ts b/src/cli/commands/test/iac/local-execution/file-parser.ts index f90e972e75..d5b5cf907b 100644 --- a/src/cli/commands/test/iac/local-execution/file-parser.ts +++ b/src/cli/commands/test/iac/local-execution/file-parser.ts @@ -27,28 +27,21 @@ import hclToJsonV2 from './parsers/hcl-to-json-v2'; import { IacProjectType } from '../../../../../lib/iac/constants'; import * as Debug from 'debug'; + const debug = Debug('snyk-test'); export async function parseFiles( filesData: IacFileData[], options: IaCTestFlags = {}, - isTFVarSupportEnabled = false, ): Promise { let tfFileData: IacFileData[] = []; let nonTfFileData: IacFileData[] = []; - - if (!isTFVarSupportEnabled) { - nonTfFileData = filesData.filter((fileData) => - ['tf', 'json', 'yaml', 'yml'].includes(fileData.fileType), - ); - } else { - tfFileData = filesData.filter((fileData) => - VALID_TERRAFORM_FILE_TYPES.includes(fileData.fileType), - ); - nonTfFileData = filesData.filter( - (fileData) => !VALID_TERRAFORM_FILE_TYPES.includes(fileData.fileType), - ); - } + tfFileData = filesData.filter((fileData) => + VALID_TERRAFORM_FILE_TYPES.includes(fileData.fileType), + ); + nonTfFileData = filesData.filter( + (fileData) => !VALID_TERRAFORM_FILE_TYPES.includes(fileData.fileType), + ); let { parsedFiles, failedFiles } = parseNonTerraformFiles( nonTfFileData, diff --git a/src/cli/commands/test/iac/local-execution/index.ts b/src/cli/commands/test/iac/local-execution/index.ts index 1b498c4751..910abcf718 100644 --- a/src/cli/commands/test/iac/local-execution/index.ts +++ b/src/cli/commands/test/iac/local-execution/index.ts @@ -22,7 +22,6 @@ import { trackUsage, } from './measurable-methods'; import { findAndLoadPolicy } from '../../../../../lib/policy'; -import { isFeatureFlagSupportedForOrg } from '../../../../../lib/feature-flags'; import { NoFilesToScanError } from './file-loader'; import { processResults } from './process-results'; import { generateProjectAttributes, generateTags } from '../../../monitor'; @@ -32,7 +31,6 @@ import { } from './directory-loader'; import { CustomError } from '../../../../../lib/errors'; import { getErrorStringCode } from './error-utils'; -import { FeatureFlagError } from './assert-iac-options-flag'; // this method executes the local processing engine and then formats the results to adapt with the CLI output. // this flow is the default GA flow for IAC scanning. @@ -50,13 +48,6 @@ export async function test( const policy = await findAndLoadPolicy(pathToScan, 'iac', options); - const isTFVarSupportEnabled = ( - await isFeatureFlagSupportedForOrg( - 'iacTerraformVarSupport', - iacOrgSettings.meta.org, - ) - ).ok; - let allParsedFiles: IacFileParsed[] = [], allFailedFiles: IacFileParseFailure[] = []; const allDirectories = getAllDirectoriesForPath( @@ -73,7 +64,7 @@ export async function test( ); if ( currentDirectory === pathToScan && - shouldLoadVarDefinitionsFile(options, isTFVarSupportEnabled) + shouldLoadVarDefinitionsFile(options) ) { const varDefinitionsFilePath = options['var-file']; filePathsInDirectory.push(varDefinitionsFilePath); @@ -82,7 +73,6 @@ export async function test( const { parsedFiles, failedFiles } = await parseFiles( filesToParse, options, - isTFVarSupportEnabled, ); allParsedFiles = allParsedFiles.concat(parsedFiles); allFailedFiles = allFailedFiles.concat(failedFiles); @@ -182,12 +172,8 @@ function parseAttributes(options: IaCTestFlags) { function shouldLoadVarDefinitionsFile( options: IaCTestFlags, - isTFVarSupportEnabled = false, ): options is IaCTestFlags & { 'var-file': string } { if (options['var-file']) { - if (!isTFVarSupportEnabled) { - throw new FeatureFlagError('var-file', 'iacTerraformVarSupport'); - } if (!existsSync(options['var-file'])) { throw new InvalidVarFilePath(options['var-file']); } diff --git a/test/acceptance/fake-server.ts b/test/acceptance/fake-server.ts index 0da8b957c2..c926de8c8d 100644 --- a/test/acceptance/fake-server.ts +++ b/test/acceptance/fake-server.ts @@ -9,7 +9,6 @@ import { getFixturePath } from '../jest/util/getFixturePath'; const featureFlagDefaults = (): Map => { return new Map([ ['cliFailFast', false], - ['iacTerraformVarSupport', false], ['iacCliOutputRelease', false], ]); }; @@ -341,13 +340,6 @@ export const fakeServer = (basePath: string, snykToken: string): FakeServer => { return; } - if (org === 'tf-lang-support' && flag === 'iacTerraformVarSupport') { - res.send({ - ok: true, - }); - return; - } - if (featureFlags.has(flag)) { const ffEnabled = featureFlags.get(flag); if (ffEnabled) { diff --git a/test/jest/acceptance/iac/iac-output.spec.ts b/test/jest/acceptance/iac/iac-output.spec.ts index 563734fc04..cd338beef8 100644 --- a/test/jest/acceptance/iac/iac-output.spec.ts +++ b/test/jest/acceptance/iac/iac-output.spec.ts @@ -325,34 +325,32 @@ Project path: ${filePath} expect(stdout).toContain( 'Tested sg_open_ssh.tf for known issues, found 1 issues', ); - expect(stdout).toContain( - 'Tested vars.tf for known issues, found 0 issues', - ); + expect(stdout).toContain('Tested vars.tf for known issues, found'); expect(stdout).toContain( `Tested ${pathLib.join( 'var_deref', 'sg_open_ssh.tf', - )} for known issues, found 0 issues`, + )} for known issues, found`, ); expect(stdout).toContain( `Tested ${pathLib.join( 'var_deref', 'variables.tf', - )} for known issues, found 0 issues`, + )} for known issues, found`, ); expect(stdout).toContain( `Tested ${pathLib.join( 'var_deref', 'nested_var_deref', 'sg_open_ssh.tf', - )} for known issues, found 0 issues`, + )} for known issues, found`, ); expect(stdout).toContain( `Tested ${pathLib.join( 'var_deref', 'nested_var_deref', 'variables.tf', - )} for known issues, found 0 issues`, + )} for known issues, found`, ); }); diff --git a/test/jest/acceptance/iac/test-directory.spec.ts b/test/jest/acceptance/iac/test-directory.spec.ts index 1c4ed09f56..9aa69ce709 100644 --- a/test/jest/acceptance/iac/test-directory.spec.ts +++ b/test/jest/acceptance/iac/test-directory.spec.ts @@ -49,7 +49,7 @@ describe('Directory scan', () => { expect(stdout).toContain('Failed to parse YAML file'); expect(stdout).toContain('Failed to parse JSON file'); expect(stdout).toContain( - '28 projects, 20 contained issues. Failed to test 5 projects.', + '28 projects, 22 contained issues. Failed to test 5 projects.', ); expect(exitCode).toBe(1); }); diff --git a/test/jest/acceptance/iac/test-terraform-var-deref.spec.ts b/test/jest/acceptance/iac/test-terraform-var-deref.spec.ts deleted file mode 100644 index 704eade43e..0000000000 --- a/test/jest/acceptance/iac/test-terraform-var-deref.spec.ts +++ /dev/null @@ -1,416 +0,0 @@ -import { isValidJSONString, startMockServer } from './helpers'; -import * as path from 'path'; -import { FakeServer } from '../../../acceptance/fake-server'; - -jest.setTimeout(50000); - -describe('Terraform Language Support', () => { - let server: FakeServer; - let run: ( - cmd: string, - ) => Promise<{ stdout: string; stderr: string; exitCode: number }>; - let teardown: () => void; - - beforeAll(async () => { - ({ server, run, teardown } = await startMockServer()); - - server.setFeatureFlag('iacTerraformVarSupport', true); - }); - - afterAll(async () => teardown()); - - describe('without feature flag', () => { - beforeAll(() => { - server.setFeatureFlag('iacTerraformVarSupport', false); - }); - - afterAll(() => { - server.setFeatureFlag('iacTerraformVarSupport', true); - }); - - it('does not dereference variables', async () => { - const { stdout, exitCode } = await run( - `snyk iac test ./iac/terraform/var_deref`, - ); - - expect(stdout).toContain('Testing sg_open_ssh.tf...'); - expect(stdout.match(/✗ Security Group allows open ingress/g)).toBeNull(); - expect(stdout).toContain('Tested sg_open_ssh.tf for known issues'); - - expect(stdout).toContain( - `Testing ${path.join('nested_var_deref', 'sg_open_ssh.tf')}...`, - ); - expect(stdout.match(/✗ Rule allows open egress/g)).toBeNull(); - expect(stdout).toContain( - `Tested ${path.join( - 'nested_var_deref', - 'sg_open_ssh.tf', - )} for known issues`, - ); - // expect exitCode to be 0 or 1 - expect(exitCode).toBeLessThanOrEqual(1); - }); - it('returns an error if var-file flag is used', async () => { - const { stdout, exitCode } = await run( - `snyk iac test ./iac/terraform/var_deref --var-file=path/to/var-file.tfvars`, - ); - expect(stdout).toMatch( - 'Flag "--var-file" is only supported if feature flag "iacTerraformVarSupport" is enabled. To enable it, please contact Snyk support.', - ); - expect(exitCode).toBe(2); - }); - - it('returns error if empty Terraform file', async () => { - const { exitCode } = await run( - `snyk iac test ./iac/terraform/empty_file.tf`, - ); - - expect(exitCode).toBe(3); - }); - - it('skips non-supported IaC files in a directory', async () => { - const { exitCode, stdout } = await run( - `snyk iac test ./iac/terraform/var_deref`, - ); - - expect(stdout).not.toContain(`a.auto.tfvars`); - expect(stdout).not.toContain(`Unsupported file extension`); - expect(exitCode).toBe(0); - }); - }); - - describe('with feature flag', () => { - describe('single files', () => { - // TODO: can be merged with the existing test-terraform.spec.ts when the flag is removed - it('finds issues in Terraform file', async () => { - const { stdout, exitCode } = await run( - `snyk iac test iac/terraform/var_deref/sg_open_ssh.tf`, - ); - - expect(stdout).toContain( - 'Testing iac/terraform/var_deref/sg_open_ssh.tf...', - ); - expect( - stdout.match(/✗ Security Group allows open ingress/g), - ).toHaveLength(1); - expect(stdout).toContain( - 'Tested iac/terraform/var_deref/sg_open_ssh.tf for known issues', - ); - expect(exitCode).toBe(1); - }); - - it('returns error empty Terraform file', async () => { - const { exitCode } = await run( - `snyk iac test ./iac/terraform/empty_file.tf`, - ); - expect(exitCode).toBe(3); - }); - }); - describe('single non-terraform files', () => { - it('finds issues in a Terraform plan file', async () => { - const { stdout, exitCode } = await run( - `snyk iac test ./iac/terraform-plan/tf-plan-create.json`, - ); - expect(stdout).toContain( - 'Testing ./iac/terraform-plan/tf-plan-create.json', - ); - expect(stdout).toContain('Infrastructure as code issues:'); - expect(stdout).toContain('✗ S3 bucket versioning disabled'); - expect(stdout).toContain( - 'resource > aws_s3_bucket[terra_ci] > versioning > enabled', - ); - expect(exitCode).toBe(1); - }); - it('finds issues in CloudFormation YAML file', async () => { - const { stdout, exitCode } = await run( - `snyk iac test ./iac/cloudformation/aurora-valid.yml`, - ); - expect(stdout).toContain( - 'Testing ./iac/cloudformation/aurora-valid.yml', - ); - expect(stdout).toContain('Infrastructure as code issues:'); - expect(stdout).toContain( - '✗ SNS topic is not encrypted with customer managed key', - ); - expect(stdout).toContain( - '[DocId: 0] > Resources[DatabaseAlarmTopic] > Properties > KmsMasterKeyId', - ); - expect(exitCode).toBe(1); - }); - - it('finds issues in CloudFormation JSON file', async () => { - const { stdout, exitCode } = await run( - `snyk iac test ./iac/cloudformation/fargate-valid.json`, - ); - expect(stdout).toContain( - 'Testing ./iac/cloudformation/fargate-valid.json', - ); - expect(stdout).toContain('Infrastructure as code issues:'); - expect(stdout).toContain( - '✗ S3 restrict public bucket control is disabled', - ); - expect(stdout).toContain( - 'Resources[CodePipelineArtifactBucket] > Properties > PublicAccessBlockConfiguration > RestrictPublicBuckets', - ); - expect(exitCode).toBe(1); - }); - - it('finds issues in ARM JSON file', async () => { - const { stdout, exitCode } = await run( - `snyk iac test ./iac/arm/rule_test.json`, - ); - expect(stdout).toContain('Testing ./iac/arm/rule_test.json'); - expect(stdout).toContain('Infrastructure as code issues:'); - expect(stdout).toContain( - '✗ Azure Firewall Network Rule Collection allows public access', - ); - expect(stdout).toContain( - 'resources[1] > properties > networkRuleCollections[0] > properties > rules[0] > sourceAddresses', - ); - expect(exitCode).toBe(1); - }); - it('finds issues in Kubernetes YAML file', async () => { - const { stdout, exitCode } = await run( - `snyk iac test ./iac/kubernetes/pod-privileged.yaml`, - ); - expect(stdout).toContain( - 'Testing ./iac/kubernetes/pod-privileged.yaml', - ); - expect(stdout).toContain('Infrastructure as code issues:'); - expect(stdout).toContain('✗ Privileged container'); - expect(stdout).toContain( - '[DocId: 0] > input > spec > containers[example] > securityContext > privileged', - ); - expect(exitCode).toBe(1); - }); - - it('finds issues in Kubernetes YAML multi file', async () => { - const { stdout, exitCode } = await run( - `snyk iac test ./iac/kubernetes/pod-privileged-multi.yaml`, - ); - expect(stdout).toContain( - 'Testing ./iac/kubernetes/pod-privileged-multi.yaml', - ); - expect(stdout).toContain('Infrastructure as code issues:'); - expect(stdout).toContain('✗ Privileged container'); - expect(stdout).toContain( - '[DocId: 0] > input > spec > containers[example] > securityContext > privileged', - ); - expect(exitCode).toBe(1); - }); - }); - }); - - describe('directories', () => { - it('dereferences variables in nested directories', async () => { - const { stdout, exitCode } = await run( - `snyk iac test ./iac/terraform/var_deref`, - ); - - expect(stdout).toContain('Testing sg_open_ssh.tf...'); - expect( - stdout.match(/✗ Security Group allows open ingress/g), - ).toHaveLength(5); - expect(stdout).toContain('Tested sg_open_ssh.tf for known issues'); - - expect(stdout).toContain( - `Testing ${path.join('nested_var_deref', 'sg_open_ssh.tf')}...`, - ); - expect(stdout.match(/✗ Rule allows open egress/g)).toHaveLength(1); - expect(stdout).toContain( - `Tested ${path.join( - 'nested_var_deref', - 'sg_open_ssh.tf', - )} for known issues`, - ); - expect(exitCode).toBe(1); - }); - - it('scans a mix of IaC files in nested directories', async () => { - const { stdout, exitCode } = await run(`snyk iac test ./iac`); - - expect(stdout).toContain( - `Testing ${path.join('kubernetes', 'pod-privileged.yaml')}`, - ); - expect(stdout).toContain( - `Tested ${path.join( - 'kubernetes', - 'pod-privileged.yaml', - )} for known issues`, - ); - - expect(stdout).toContain( - `Testing ${path.join('terraform', 'var_deref', 'sg_open_ssh.tf')}`, - ); - expect( - stdout.match(/✗ Security Group allows open ingress/g), - ).toHaveLength(9); - expect(stdout).toContain( - `Tested ${path.join( - 'terraform', - 'var_deref', - 'sg_open_ssh.tf', - )} for known issues`, - ); - - expect(stdout).toContain( - `Testing ${path.join( - 'terraform', - 'var_deref', - 'nested_var_deref', - 'sg_open_ssh.tf', - )}...`, - ); - expect(stdout.match(/✗ Rule allows open egress/g)).toHaveLength(1); - expect(stdout).toContain( - `Tested ${path.join( - 'terraform', - 'var_deref', - 'nested_var_deref', - 'sg_open_ssh.tf', - )} for known issues`, - ); - expect(exitCode).toBe(1); - }); - }); - - describe('with the --var-file flag', () => { - it('picks up the file and dereferences the variable context for the right directory (pathToScan)', async () => { - const { stdout, exitCode } = await run( - `snyk iac test --org=tf-lang-support ./iac/terraform/var_deref/nested_var_deref --var-file=./iac/terraform/vars.tf`, - ); - expect(stdout).toContain( - `Testing ${path.relative( - './iac/terraform/var_deref/nested_var_deref', - './iac/terraform/vars.tf', - )}`, - ); - expect(stdout).toContain( - 'introduced by input > resource > aws_security_group[allow_ssh_external_var_file] > ingress\n', - ); - expect( - stdout.match( - /Project path: {6}.\/iac\/terraform\/var_deref\/nested_var_deref/g, - ), - ).toHaveLength(3); - expect(stdout.match(/Project path: {6}.\/iac\/terraform$/g)).toBeNull(); - expect(exitCode).toBe(1); - }); - it('returns error if the file does not exist', async () => { - const { stdout, exitCode } = await run( - `snyk iac test --org=tf-lang-support ./iac/terraform/var_deref --var-file=./iac/terraform/non-existent.tfvars`, - ); - expect(stdout).toContain( - 'We were unable to locate a variable definitions file at: "./iac/terraform/non-existent.tfvars". The file at the provided path does not exist', - ); - expect(exitCode).toBe(2); - }); - it('will not parse the external file if it is invalid', async () => { - const { stdout, exitCode } = await run( - `snyk iac test --org=tf-lang-support ./iac/terraform/var_deref --var-file=./iac/terraform/sg_open_ssh_invalid_hcl2.tf`, - ); - expect(stdout).toContain('Testing sg_open_ssh_invalid_hcl2.tf...'); - expect(stdout).toContain('Failed to parse Terraform file'); - expect(exitCode).toBe(1); - }); - }); - - describe('other functions', () => { - it('is backwards compatible without variable dereferencing', async () => { - const { stdout, exitCode } = await run( - `snyk iac test iac/terraform/sg_open_ssh.tf`, - ); - - expect(stdout).toContain('Testing iac/terraform/sg_open_ssh.tf...'); - expect( - stdout.match(/✗ Security Group allows open ingress/g), - ).toHaveLength(1); - expect(stdout).toContain( - 'Tested iac/terraform/sg_open_ssh.tf for known issues', - ); - expect(exitCode).toBe(1); - }); - - it('filters out issues when using severity threshold', async () => { - const { stdout, exitCode } = await run( - `snyk iac test iac/terraform/sg_open_ssh.tf --severity-threshold=high`, - ); - - expect(stdout).toContain('Testing iac/terraform/sg_open_ssh.tf...'); - expect(stdout.match(/✗ Security Group allows open ingress/g)).toBeNull(); - expect(stdout).toContain( - 'Tested iac/terraform/sg_open_ssh.tf for known issues', - ); - // expect exitCode to be 0 or 1 - expect(exitCode).toBeLessThanOrEqual(1); - }); - - it('filters out issues when using detection depth', async () => { - const { stdout, exitCode } = await run( - `snyk iac test ./iac/terraform/ --detection-depth=1`, - ); - - expect(stdout).toContain( - `Testing ${path.join('var_deref', 'sg_open_ssh.tf')}`, - ); - expect(stdout).toContain( - `Tested ${path.join('var_deref', 'sg_open_ssh.tf')} for known issues`, - ); - expect(stdout).toContain(`Testing ${path.join('sg_open_ssh.tf')}`); - expect(stdout).toContain('Tested sg_open_ssh.tf for known issues'); - expect( - stdout.match(/✗ Security Group allows open ingress/g), - ).toHaveLength(6); - - // Check that we didn't scan directories with depth > 2 - expect(stdout).not.toContain( - `Testing ${path.join( - 'var_deref', - 'nested_var_deref', - 'sg_open_ssh.tf', - )}...`, - ); - expect(stdout.match(/✗ Rule allows open egress/g)).toBeNull(); - expect(stdout).not.toContain( - `Tested ${path.join( - 'var_deref', - 'nested_var_deref', - 'sg_open_ssh.tf', - )} for known issues`, - ); - expect(exitCode).toBe(1); - }); - - it('outputs an error for files with invalid HCL2', async () => { - const { stdout, exitCode } = await run( - `snyk iac test ./iac/terraform/sg_open_ssh_invalid_hcl2.tf`, - ); - - expect(stdout).toContain('We were unable to parse the Terraform file'); - expect(exitCode).toBe(2); - }); - - it('outputs the expected text when running with --sarif flag', async () => { - const { stdout, exitCode } = await run( - `snyk iac test ./iac/terraform/sg_open_ssh.tf --sarif`, - ); - - expect(isValidJSONString(stdout)).toBe(true); - expect(stdout).toContain('"id": "SNYK-CC-TF-1",'); - expect(stdout).toContain('"ruleId": "SNYK-CC-TF-1",'); - expect(exitCode).toBe(1); - }); - - it('outputs the expected text when running with --json flag', async () => { - const { stdout, exitCode } = await run( - `snyk iac test ./iac/terraform/sg_open_ssh.tf --json`, - ); - - expect(isValidJSONString(stdout)).toBe(true); - expect(stdout).toContain('"id": "SNYK-CC-TF-1",'); - expect(stdout).toContain('"packageManager": "terraformconfig",'); - expect(stdout).toContain('"projectType": "terraformconfig",'); - expect(exitCode).toBe(1); - }); - }); -}); diff --git a/test/jest/acceptance/iac/test-terraform.spec.ts b/test/jest/acceptance/iac/test-terraform.spec.ts index f11504f105..b452ecc56e 100644 --- a/test/jest/acceptance/iac/test-terraform.spec.ts +++ b/test/jest/acceptance/iac/test-terraform.spec.ts @@ -1,8 +1,9 @@ -import { startMockServer, isValidJSONString } from './helpers'; +import { isValidJSONString, startMockServer } from './helpers'; +import * as path from 'path'; jest.setTimeout(50000); -describe('Terraform single file scan', () => { +describe('Terraform', () => { let run: ( cmd: string, ) => Promise<{ stdout: string; stderr: string; exitCode: number }>; @@ -16,57 +17,214 @@ describe('Terraform single file scan', () => { afterAll(async () => teardown()); - it('finds issues in Terraform file', async () => { - const { stdout, exitCode } = await run( - `snyk iac test ./iac/terraform/sg_open_ssh.tf`, - ); - expect(stdout).toContain('Testing ./iac/terraform/sg_open_ssh.tf'); - expect(stdout).toContain('Infrastructure as code issues:'); - expect(stdout).toContain('✗ Security Group allows open ingress'); - expect(stdout).toContain( - ' input > resource > aws_security_group[allow_ssh] > ingress', - ); - expect(exitCode).toBe(1); - }); + describe('Terraform single file scans', () => { + it('finds issues in Terraform file', async () => { + const { stdout, exitCode } = await run( + `snyk iac test ./iac/terraform/sg_open_ssh.tf`, + ); + expect(stdout).toContain('Testing ./iac/terraform/sg_open_ssh.tf'); + expect(stdout).toContain('Infrastructure as code issues:'); + expect(stdout).toContain('✗ Security Group allows open ingress'); + expect(stdout).toContain( + ' input > resource > aws_security_group[allow_ssh] > ingress', + ); + expect(exitCode).toBe(1); + }); - it('filters out issues when using severity threshold', async () => { - const { stdout, exitCode } = await run( - `snyk iac test ./iac/terraform/sg_open_ssh.tf --severity-threshold=high`, - ); + it('filters out issues when using severity threshold', async () => { + const { stdout, exitCode } = await run( + `snyk iac test ./iac/terraform/sg_open_ssh.tf --severity-threshold=high`, + ); - expect(stdout).toContain('Infrastructure as code issues:'); - expect(stdout).toContain( - 'Tested ./iac/terraform/sg_open_ssh.tf for known issues, found 0 issues', - ); - expect(exitCode).toBe(0); - }); + expect(stdout).toContain('Infrastructure as code issues:'); + expect(stdout).toContain( + 'Tested ./iac/terraform/sg_open_ssh.tf for known issues, found 0 issues', + ); + expect(exitCode).toBe(0); + }); + + it('outputs an error for files with invalid HCL2', async () => { + const { stdout, exitCode } = await run( + `snyk iac test ./iac/terraform/sg_open_ssh_invalid_hcl2.tf`, + ); + expect(stdout).toContain('We were unable to parse the Terraform file'); + expect(exitCode).toBe(2); + }); - it('outputs an error for files with invalid HCL2', async () => { - const { stdout, exitCode } = await run( - `snyk iac test ./iac/terraform/sg_open_ssh_invalid_hcl2.tf`, - ); - expect(stdout).toContain('We were unable to parse the Terraform file'); - expect(exitCode).toBe(2); + it('outputs the expected text when running with --sarif flag', async () => { + const { stdout, exitCode } = await run( + `snyk iac test ./iac/terraform/sg_open_ssh.tf --sarif`, + ); + expect(isValidJSONString(stdout)).toBe(true); + expect(stdout).toContain('"id": "SNYK-CC-TF-1",'); + expect(stdout).toContain('"ruleId": "SNYK-CC-TF-1",'); + expect(exitCode).toBe(1); + }); + + it('outputs the expected text when running with --json flag', async () => { + const { stdout, exitCode } = await run( + `snyk iac test ./iac/terraform/sg_open_ssh.tf --json`, + ); + expect(isValidJSONString(stdout)).toBe(true); + expect(stdout).toContain('"id": "SNYK-CC-TF-1",'); + expect(stdout).toContain('"packageManager": "terraformconfig",'); + expect(stdout).toContain('"projectType": "terraformconfig",'); + expect(exitCode).toBe(1); + }); + it('returns error empty Terraform file', async () => { + const { exitCode } = await run( + `snyk iac test ./iac/terraform/empty_file.tf`, + ); + expect(exitCode).toBe(3); + }); }); - it('outputs the expected text when running with --sarif flag', async () => { - const { stdout, exitCode } = await run( - `snyk iac test ./iac/terraform/sg_open_ssh.tf --sarif`, - ); - expect(isValidJSONString(stdout)).toBe(true); - expect(stdout).toContain('"id": "SNYK-CC-TF-1",'); - expect(stdout).toContain('"ruleId": "SNYK-CC-TF-1",'); - expect(exitCode).toBe(1); + describe('Terraform directories', () => { + it('dereferences variables in nested directories', async () => { + const { stdout, exitCode } = await run( + `snyk iac test ./iac/terraform/var_deref`, + ); + + expect(stdout).toContain('Testing sg_open_ssh.tf...'); + expect( + stdout.match(/✗ Security Group allows open ingress/g), + ).toHaveLength(5); + expect(stdout).toContain('Tested sg_open_ssh.tf for known issues'); + + expect(stdout).toContain( + `Testing ${path.join('nested_var_deref', 'sg_open_ssh.tf')}...`, + ); + expect(stdout.match(/✗ Rule allows open egress/g)).toHaveLength(1); + expect(stdout).toContain( + `Tested ${path.join( + 'nested_var_deref', + 'sg_open_ssh.tf', + )} for known issues`, + ); + expect(exitCode).toBe(1); + }); + + it('scans a mix of IaC files in nested directories', async () => { + const { stdout, exitCode } = await run(`snyk iac test ./iac`); + + expect(stdout).toContain( + `Testing ${path.join('kubernetes', 'pod-privileged.yaml')}`, + ); + expect(stdout).toContain( + `Tested ${path.join( + 'kubernetes', + 'pod-privileged.yaml', + )} for known issues`, + ); + + expect(stdout).toContain( + `Testing ${path.join('terraform', 'var_deref', 'sg_open_ssh.tf')}`, + ); + expect( + stdout.match(/✗ Security Group allows open ingress/g), + ).toHaveLength(9); + expect(stdout).toContain( + `Tested ${path.join( + 'terraform', + 'var_deref', + 'sg_open_ssh.tf', + )} for known issues`, + ); + + expect(stdout).toContain( + `Testing ${path.join( + 'terraform', + 'var_deref', + 'nested_var_deref', + 'sg_open_ssh.tf', + )}...`, + ); + expect(stdout.match(/✗ Rule allows open egress/g)).toHaveLength(1); + expect(stdout).toContain( + `Tested ${path.join( + 'terraform', + 'var_deref', + 'nested_var_deref', + 'sg_open_ssh.tf', + )} for known issues`, + ); + expect(exitCode).toBe(1); + }); + it('filters out issues when using detection depth', async () => { + const { stdout, exitCode } = await run( + `snyk iac test ./iac/terraform/ --detection-depth=1`, + ); + + expect(stdout).toContain( + `Testing ${path.join('var_deref', 'sg_open_ssh.tf')}`, + ); + expect(stdout).toContain( + `Tested ${path.join('var_deref', 'sg_open_ssh.tf')} for known issues`, + ); + expect(stdout).toContain(`Testing ${path.join('sg_open_ssh.tf')}`); + expect(stdout).toContain('Tested sg_open_ssh.tf for known issues'); + expect( + stdout.match(/✗ Security Group allows open ingress/g), + ).toHaveLength(6); + + // Check that we didn't scan directories with depth > 2 + expect(stdout).not.toContain( + `Testing ${path.join( + 'var_deref', + 'nested_var_deref', + 'sg_open_ssh.tf', + )}...`, + ); + expect(stdout.match(/✗ Rule allows open egress/g)).toBeNull(); + expect(stdout).not.toContain( + `Tested ${path.join( + 'var_deref', + 'nested_var_deref', + 'sg_open_ssh.tf', + )} for known issues`, + ); + expect(exitCode).toBe(1); + }); }); - it('outputs the expected text when running with --json flag', async () => { - const { stdout, exitCode } = await run( - `snyk iac test ./iac/terraform/sg_open_ssh.tf --json`, - ); - expect(isValidJSONString(stdout)).toBe(true); - expect(stdout).toContain('"id": "SNYK-CC-TF-1",'); - expect(stdout).toContain('"packageManager": "terraformconfig",'); - expect(stdout).toContain('"projectType": "terraformconfig",'); - expect(exitCode).toBe(1); + describe('with the --var-file flag', () => { + it('picks up the file and dereferences the variable context for the right directory (pathToScan)', async () => { + const { stdout, exitCode } = await run( + `snyk iac test ./iac/terraform/var_deref/nested_var_deref --var-file=./iac/terraform/vars.tf`, + ); + expect(stdout).toContain( + `Testing ${path.relative( + './iac/terraform/var_deref/nested_var_deref', + './iac/terraform/vars.tf', + )}`, + ); + expect(stdout).toContain( + 'introduced by input > resource > aws_security_group[allow_ssh_external_var_file] > ingress\n', + ); + expect( + stdout.match( + /Project path: {6}.\/iac\/terraform\/var_deref\/nested_var_deref/g, + ), + ).toHaveLength(3); + expect(stdout.match(/Project path: {6}.\/iac\/terraform$/g)).toBeNull(); + expect(exitCode).toBe(1); + }); + it('returns error if the file does not exist', async () => { + const { stdout, exitCode } = await run( + `snyk iac test ./iac/terraform/var_deref --var-file=./iac/terraform/non-existent.tfvars`, + ); + expect(stdout).toContain( + 'We were unable to locate a variable definitions file at: "./iac/terraform/non-existent.tfvars". The file at the provided path does not exist', + ); + expect(exitCode).toBe(2); + }); + it('will not parse the external file if it is invalid', async () => { + const { stdout, exitCode } = await run( + `snyk iac test ./iac/terraform/var_deref --var-file=./iac/terraform/sg_open_ssh_invalid_hcl2.tf`, + ); + expect(stdout).toContain('Testing sg_open_ssh_invalid_hcl2.tf...'); + expect(stdout).toContain('Failed to parse Terraform file'); + expect(exitCode).toBe(1); + }); }); }); diff --git a/test/jest/unit/iac/file-parser.spec.ts b/test/jest/unit/iac/file-parser.spec.ts index d6c3d72f7d..2113e89334 100644 --- a/test/jest/unit/iac/file-parser.spec.ts +++ b/test/jest/unit/iac/file-parser.spec.ts @@ -61,18 +61,18 @@ describe('parseFiles', () => { expect(parsedFiles.length).toEqual(9); expect(parsedFiles[0]).toEqual(expectedKubernetesYamlParsingResult); expect(parsedFiles[1]).toEqual(expectedKubernetesJsonParsingResult); - expect(parsedFiles[2]).toEqual(expectedTerraformParsingResult); - expect(parsedFiles[3]).toEqual(expectedTerraformJsonParsingResult); - expect(parsedFiles[4]).toEqual( + expect(parsedFiles[2]).toEqual(expectedTerraformJsonParsingResult); + expect(parsedFiles[3]).toEqual( expectedMultipleKubernetesYamlsParsingResult, ); - expect(parsedFiles[5]).toEqual({ + expect(parsedFiles[4]).toEqual({ ...expectedMultipleKubernetesYamlsParsingResult, docId: 2, // doc, the 2nd doc, is an empty doc, which is ignored. There is also an ignored docId:3. }); - expect(parsedFiles[6]).toEqual(expectedCloudFormationYAMLParsingResult); - expect(parsedFiles[7]).toEqual(expectedCloudFormationJSONParsingResult); - expect(parsedFiles[8]).toEqual(expectedArmParsingResult); + expect(parsedFiles[5]).toEqual(expectedCloudFormationYAMLParsingResult); + expect(parsedFiles[6]).toEqual(expectedCloudFormationJSONParsingResult); + expect(parsedFiles[7]).toEqual(expectedArmParsingResult); + expect(parsedFiles[8]).toEqual(expectedTerraformParsingResult); expect(failedFiles.length).toEqual(0); }); @@ -89,7 +89,7 @@ describe('parseFiles', () => { expect(failedFiles.length).toEqual(0); }); - it('does not include the unsupported file types in failed files list', async () => { + it('includes unsupported file types in failed files', async () => { const { parsedFiles, failedFiles } = await parseFiles([ { fileContent: 'file.java', @@ -98,7 +98,7 @@ describe('parseFiles', () => { }, ]); expect(parsedFiles.length).toEqual(0); - expect(failedFiles.length).toEqual(0); + expect(failedFiles.length).toEqual(1); }); it('throws an error for invalid JSON file types', async () => { diff --git a/test/jest/unit/iac/index.spec.ts b/test/jest/unit/iac/index.spec.ts index 01d63adfb6..56af1be690 100644 --- a/test/jest/unit/iac/index.spec.ts +++ b/test/jest/unit/iac/index.spec.ts @@ -1,9 +1,5 @@ jest.mock('../../../../src/cli/commands/test/iac/local-execution/local-cache'); jest.mock('../../../../src/cli/commands/test/iac/local-execution/file-loader'); -const isFeatureFlagSupportedForOrgStub = jest.fn(); -jest.mock('../../../../src/lib/feature-flags', () => ({ - isFeatureFlagSupportedForOrg: isFeatureFlagSupportedForOrgStub, -})); const parseFilesStub = jest.fn(); jest.mock( '../../../../src/cli/commands/test/iac/local-execution/file-parser', @@ -50,12 +46,13 @@ jest.mock( import { test } from '../../../../src/cli/commands/test/iac/local-execution/'; import { + EngineType, IacFileParsed, IaCTestFlags, RulesOrigin, } from '../../../../src/cli/commands/test/iac/local-execution/types'; import { IacProjectType } from '../../../../src/lib/iac/constants'; -import { EngineType } from '../../../../src/cli/commands/test/iac/local-execution/types'; + const parsedFiles: IacFileParsed[] = [ { engineType: EngineType.Terraform, @@ -79,10 +76,7 @@ const failedFiles: IacFileParsed[] = [ ]; describe('test()', () => { - describe.each([ - [{ iacTerraformVarSupport: false }], - [{ iacTerraformVarSupport: true }], - ])('With TF language support feature flag set to %p', (featureFlags) => { + describe('parsing', () => { const iacOrgSettings = { meta: { isPrivate: false, @@ -99,9 +93,6 @@ describe('test()', () => { }; beforeAll(() => { - isFeatureFlagSupportedForOrgStub.mockImplementation((flag) => - Promise.resolve({ ok: featureFlags[flag] ?? true }), - ); getAllDirectoriesForPathStub.mockImplementation(() => ['./storage']); parseFilesStub.mockImplementation(() => ({ parsedFiles,