Skip to content

Commit

Permalink
feat: Load TF variable definitions files via --var-file
Browse files Browse the repository at this point in the history
This commit adds the ability to load an external file by using 'var-file' flag:
- The user can point to a single filepath, relative to the 'pathToScan'
- The filepath will be checked for existence, and any vars will be de-referenced into the context of the 'pathToScan' directory.

CFG-1663
  • Loading branch information
Ilianna Papastefanou committed Apr 11, 2022
1 parent f567a74 commit 141b6c0
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 20 deletions.
35 changes: 24 additions & 11 deletions src/cli/commands/test/iac-local-execution/index.ts
@@ -1,3 +1,4 @@
import { existsSync } from 'fs';
import { isLocalFolder } from '../../../../lib/detect';
import {
EngineType,
Expand Down Expand Up @@ -33,7 +34,6 @@ import {
getAllDirectoriesForPath,
getFilesForDirectory,
} from './directory-loader';
import { existsSync } from 'fs';
import { CustomError } from '../../../../lib/errors';
import { getErrorStringCode } from './error-utils';
import { FeatureFlagError } from './assert-iac-options-flag';
Expand Down Expand Up @@ -75,22 +75,20 @@ export async function test(
options.detectionDepth,
);

if (options['var-file']) {
if (!isTFVarSupportEnabled) {
throw new FeatureFlagError('var-file', 'iacTerraformVarSupport');
}
if (!existsSync(options['var-file'])) {
throw new InvalidVarFilePath(options['var-file']);
}
}

// we load and parse files directory by directory
// because we need all files in the same directory to share the same variable context for Terraform
for (const currentDirectory of allDirectories) {
const filePathsInDirectory = getFilesForDirectory(
pathToScan,
currentDirectory,
);
if (
currentDirectory === pathToScan &&
shouldLoadVarDefinitionsFile(options, isTFVarSupportEnabled)
) {
const varDefinitionsFilePath = options['var-file'];
filePathsInDirectory.push(varDefinitionsFilePath);
}
const filesToParse = await loadContentForFiles(filePathsInDirectory);
const { parsedFiles, failedFiles } = await parseFiles(
filesToParse,
Expand Down Expand Up @@ -121,7 +119,6 @@ export async function test(
);
}

// TODO: decide if this should go into scanFiles or stay here
const scannedFiles = await scanFiles(allParsedFiles);
const resultsWithCustomSeverities = await applyCustomSeverities(
scannedFiles,
Expand Down Expand Up @@ -191,6 +188,22 @@ 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']);
}
return true;
}
return false;
}

export class InvalidVarFilePath extends CustomError {
constructor(path: string, message?: string) {
super(message || 'Invalid path to variable definitions file');
Expand Down
Expand Up @@ -6,3 +6,16 @@ resource "aws_security_group_rule" "egress" {
cidr_blocks = [var.remote_user_addr]
security_group_id = aws_security_group.allow.id
}

resource "aws_security_group" "allow_ssh_external_var_file" {
name = "allow_ssh"
description = "Allow SSH inbound from anywhere"
vpc_id = "${aws_vpc.main.id}"

ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = var.remote_user_addr_external_var_file
}
}
4 changes: 4 additions & 0 deletions test/fixtures/iac/terraform/vars.tf
@@ -0,0 +1,4 @@
variable "remote_user_addr_external_var_file" {
type = list(string)
default = ["0.0.0.0/0"]
}
7 changes: 3 additions & 4 deletions test/jest/acceptance/iac/test-directory.spec.ts
Expand Up @@ -18,8 +18,6 @@ describe('Directory scan', () => {

it('scans all files in a directory with Kubernetes files', async () => {
const { stdout, exitCode } = await run(`snyk iac test ./iac/kubernetes/`);
expect(exitCode).toBe(1);

expect(stdout).toContain('Testing pod-privileged.yaml'); //directory scan shows relative path to cwd in output
expect(stdout).toContain('Testing pod-privileged-multi.yaml');
expect(stdout).toContain('Testing pod-valid.json');
Expand All @@ -28,11 +26,11 @@ describe('Directory scan', () => {
expect(stdout).toContain(
'Tested 3 projects, 3 contained issues. Failed to test 1 project.',
);
expect(exitCode).toBe(1);
});

it('scans all files in a directory with a mix of IaC files', async () => {
const { stdout, exitCode } = await run(`snyk iac test ./iac/`);
expect(exitCode).toBe(1);
//directory scan shows relative path to cwd in output
// here we assert just on the filename to avoid the different slashes (/) for Unix/Windows on the CI runner
expect(stdout).toContain('pod-privileged.yaml');
Expand All @@ -47,8 +45,9 @@ describe('Directory scan', () => {
expect(stdout).toContain('Failed to parse YAML file');
expect(stdout).toContain('Failed to parse JSON file');
expect(stdout).toContain(
'22 projects, 15 contained issues. Failed to test 8 projects.',
'23 projects, 15 contained issues. Failed to test 8 projects.',
);
expect(exitCode).toBe(1);
});

it('filters out issues when using severity threshold', async () => {
Expand Down
30 changes: 25 additions & 5 deletions test/jest/acceptance/iac/test-terraform-var-deref.spec.ts
@@ -1,4 +1,4 @@
import { startMockServer, isValidJSONString } from './helpers';
import { isValidJSONString, startMockServer } from './helpers';
import * as path from 'path';

jest.setTimeout(50000);
Expand Down Expand Up @@ -256,14 +256,26 @@ describe('Terraform Language Support', () => {
});
});

describe('with --var-file', () => {
it.skip('picks up the file if it exists', async () => {
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 --var-file=./iac/terraform/example_var.tfvars`,
`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.join('terraform', 'example_var.tfvars')}`,
`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 () => {
Expand All @@ -275,6 +287,14 @@ describe('Terraform Language Support', () => {
);
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', () => {
Expand Down

0 comments on commit 141b6c0

Please sign in to comment.