Skip to content

Commit c6d1329

Browse files
authoredMay 4, 2021
Merge pull request #1865 from snyk/feat/tf-plan-full-scan
feat: tf plan full scan flag support
2 parents b971f4a + 8800697 commit c6d1329

File tree

7 files changed

+132
-13
lines changed

7 files changed

+132
-13
lines changed
 

‎help/commands-docs/iac.md

+7
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,10 @@ Find security issues in your Infrastructure as Code files.
4848
(only in `test` command)
4949
Enable an experimental feature to scan configuration files locally on your machine.
5050
This feature also gives you the ability to scan terraform plan JSON files.
51+
52+
- `--scan=`<TERRAFORM_PLAN_SCAN_MODE>:
53+
Dedicated flag for Terraform plan scanning modes (available only under `--experimental` mode).
54+
It enables to control whether the scan should analyse the full final state (e.g. `planned-values`), or the proposed changes only (e.g. `resource-changes`).
55+
Default: If the `--scan` flag is not provided it would scan the proposed changes only by default.
56+
Example #1: `--scan=planned-values` (full state scan)
57+
Example #2: `--scan=resource-changes` (proposed changes scan)

‎src/cli/commands/test/iac-local-execution/assert-iac-options-flag.ts

+41-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { CustomError } from '../../../../lib/errors';
22
import { args } from '../../../args';
3-
import { IaCErrorCodes, IaCTestFlags } from './types';
3+
import { IaCErrorCodes, IaCTestFlags, TerraformPlanScanMode } from './types';
44

55
const keys: (keyof IaCTestFlags)[] = [
66
'debug',
@@ -18,24 +18,42 @@ const keys: (keyof IaCTestFlags)[] = [
1818
'help',
1919
'q',
2020
'quiet',
21+
'scan',
2122
];
2223
const allowed = new Set<string>(keys);
2324

2425
function camelcaseToDash(key: string) {
2526
return key.replace(/[A-Z]/g, (m) => '-' + m.toLowerCase());
2627
}
2728

29+
function getFlagName(key: string) {
30+
const dashes = key.length === 1 ? '-' : '--';
31+
const flag = camelcaseToDash(key);
32+
return `${dashes}${flag}`;
33+
}
34+
2835
class FlagError extends CustomError {
2936
constructor(key: string) {
30-
const dashes = key.length === 1 ? '-' : '--';
31-
const flag = camelcaseToDash(key);
32-
const msg = `Unsupported flag "${dashes}${flag}" provided. Run snyk iac test --help for supported flags.`;
37+
const flag = getFlagName(key);
38+
const msg = `Unsupported flag "${flag}" provided. Run snyk iac test --help for supported flags.`;
3339
super(msg);
3440
this.code = IaCErrorCodes.FlagError;
3541
this.userMessage = msg;
3642
}
3743
}
3844

45+
export class FlagValueError extends CustomError {
46+
constructor(key: string, value: string) {
47+
const flag = getFlagName(key);
48+
const msg = `Unsupported value "${value}" provided to flag "${flag}".\nSupported values are: ${SUPPORTED_TF_PLAN_SCAN_MODES.join(
49+
', ',
50+
)}`;
51+
super(msg);
52+
this.code = IaCErrorCodes.FlagValueError;
53+
this.userMessage = msg;
54+
}
55+
}
56+
3957
/**
4058
* Validates the command line flags passed to the snyk iac test
4159
* command. The current argument parsing is very permissive and
@@ -58,4 +76,23 @@ export function assertIaCOptionsFlags(argv: string[]) {
5876
throw new FlagError(key);
5977
}
6078
}
79+
80+
if (parsed.options.scan) {
81+
assertTerraformPlanModes(parsed.options.scan as string);
82+
}
83+
}
84+
85+
const SUPPORTED_TF_PLAN_SCAN_MODES = [
86+
TerraformPlanScanMode.DeltaScan,
87+
TerraformPlanScanMode.FullScan,
88+
];
89+
90+
function assertTerraformPlanModes(scanModeArgValue: string) {
91+
if (
92+
!SUPPORTED_TF_PLAN_SCAN_MODES.includes(
93+
scanModeArgValue as TerraformPlanScanMode,
94+
)
95+
) {
96+
throw new FlagValueError('scan', scanModeArgValue);
97+
}
6198
}

‎src/cli/commands/test/iac-local-execution/file-parser.ts

+11-3
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,21 @@ import {
1616
ParsingResults,
1717
IacFileParseFailure,
1818
IaCErrorCodes,
19+
IaCTestFlags,
20+
TerraformPlanScanMode,
1921
} from './types';
2022
import * as analytics from '../../../../lib/analytics';
2123
import { CustomError } from '../../../../lib/errors';
2224

2325
export async function parseFiles(
2426
filesData: IacFileData[],
27+
options: IaCTestFlags = {},
2528
): Promise<ParsingResults> {
2629
const parsedFiles: IacFileParsed[] = [];
2730
const failedFiles: IacFileParseFailure[] = [];
2831
for (const fileData of filesData) {
2932
try {
30-
parsedFiles.push(...tryParseIacFile(fileData));
33+
parsedFiles.push(...tryParseIacFile(fileData, options));
3134
} catch (err) {
3235
if (filesData.length === 1) {
3336
throw err;
@@ -75,7 +78,10 @@ function parseYAMLOrJSONFileData(fileData: IacFileData): any[] {
7578
return yamlDocuments;
7679
}
7780

78-
export function tryParseIacFile(fileData: IacFileData): IacFileParsed[] {
81+
export function tryParseIacFile(
82+
fileData: IacFileData,
83+
options: IaCTestFlags = {},
84+
): IacFileParsed[] {
7985
analytics.add('iac-terraform-plan', false);
8086
switch (fileData.fileType) {
8187
case 'yaml':
@@ -89,7 +95,9 @@ export function tryParseIacFile(fileData: IacFileData): IacFileParsed[] {
8995
// but the Terraform plan can only have one
9096
if (parsedIacFile.length === 1 && isTerraformPlan(parsedIacFile[0])) {
9197
analytics.add('iac-terraform-plan', true);
92-
return tryParsingTerraformPlan(fileData, parsedIacFile[0]);
98+
return tryParsingTerraformPlan(fileData, parsedIacFile[0], {
99+
isFullScan: options.scan === TerraformPlanScanMode.FullScan,
100+
});
93101
} else {
94102
try {
95103
return tryParsingKubernetesFile(fileData, parsedIacFile);

‎src/cli/commands/test/iac-local-execution/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export async function test(
3030
}> {
3131
await initLocalCache();
3232
const filesToParse = await loadFiles(pathToScan, options);
33-
const { parsedFiles, failedFiles } = await parseFiles(filesToParse);
33+
const { parsedFiles, failedFiles } = await parseFiles(filesToParse, options);
3434
const scannedFiles = await scanFiles(parsedFiles);
3535
const iacOrgSettings = await getIacOrgSettings();
3636
const resultsWithCustomSeverities = await applyCustomSeverities(

‎src/cli/commands/test/iac-local-execution/types.ts

+12-1
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,17 @@ export type IaCTestFlags = Pick<
130130
help?: 'help';
131131
q?: boolean;
132132
quiet?: boolean;
133-
};
133+
} & TerraformPlanFlags;
134+
135+
// Flags specific for Terraform plan scanning
136+
interface TerraformPlanFlags {
137+
scan?: TerraformPlanScanMode;
138+
}
139+
140+
export enum TerraformPlanScanMode {
141+
DeltaScan = 'resource-changes', // default value
142+
FullScan = 'planned-values',
143+
}
134144

135145
// Includes all IaCTestOptions plus additional properties
136146
// that are added at runtime and not part of the parsed
@@ -233,4 +243,5 @@ export enum IaCErrorCodes {
233243

234244
// assert-iac-options-flag
235245
FlagError = 1090,
246+
FlagValueError = 1091,
236247
}

‎test/jest/unit/iac-unit-tests/assert-iac-options-flag.spec.ts

+32-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import { assertIaCOptionsFlags } from '../../../../src/cli/commands/test/iac-local-execution/assert-iac-options-flag';
1+
import {
2+
assertIaCOptionsFlags,
3+
FlagValueError,
4+
} from '../../../../src/cli/commands/test/iac-local-execution/assert-iac-options-flag';
25

36
describe('assertIaCOptionsFlags()', () => {
47
const command = ['node', 'cli', 'iac', 'test'];
@@ -44,4 +47,32 @@ describe('assertIaCOptionsFlags()', () => {
4447
assertIaCOptionsFlags([...command, ...options, ...files]),
4548
).toThrow();
4649
});
50+
51+
describe('Terraform plan scan modes', () => {
52+
it('throws an error if the scan flag has no value', () => {
53+
const options = ['--scan'];
54+
expect(() =>
55+
assertIaCOptionsFlags([...command, ...options, ...files]),
56+
).toThrow(FlagValueError);
57+
});
58+
59+
it('throws an error if the scan flag has an unsupported value', () => {
60+
const options = ['--scan=rsrce-changes'];
61+
expect(() =>
62+
assertIaCOptionsFlags([...command, ...options, ...files]),
63+
).toThrow(FlagValueError);
64+
});
65+
66+
it.each([
67+
['--scan=resource-changes', 'delta-scan'],
68+
['--scan=planned-values', 'full-scan'],
69+
])(
70+
'does not throw an error if the scan flag has a valid value of %s',
71+
(options) => {
72+
expect(() =>
73+
assertIaCOptionsFlags([...command, ...options, ...files]),
74+
).not.toThrow(FlagValueError);
75+
},
76+
);
77+
});
4778
});

‎test/smoke/spec/iac/snyk_test_local_exec_spec.sh

+28-3
Original file line numberDiff line numberDiff line change
@@ -236,9 +236,8 @@ Describe "Snyk iac test --experimental command"
236236
The output should include "tf-plan.json for known issues, found"
237237
End
238238

239-
# The test below should be enabled once we add the full scan flag
240-
xIt "finds issues in a Terraform plan file - full scan flag"
241-
When run snyk iac test ../fixtures/iac/terraform-plan/tf-plan.json --experimental
239+
It "finds issues in a Terraform plan file - full scan flag"
240+
When run snyk iac test ../fixtures/iac/terraform-plan/tf-plan.json --experimental --scan=planned-values
242241
The status should equal 1 # issues found
243242
The output should include "Testing tf-plan.json"
244243

@@ -250,5 +249,31 @@ Describe "Snyk iac test --experimental command"
250249

251250
The output should include "tf-plan.json for known issues, found"
252251
End
252+
253+
It "finds issues in a Terraform plan file - explicit delta scan with flag"
254+
When run snyk iac test ../fixtures/iac/terraform-plan/tf-plan.json --experimental --scan=resource-changes
255+
The status should equal 1 # issues found
256+
The output should include "Testing tf-plan.json"
257+
258+
# Outputs issues
259+
The output should include "Infrastructure as code issues:"
260+
# Root module
261+
The output should include ""
262+
The output should include " introduced by"
263+
264+
The output should include "tf-plan.json for known issues, found"
265+
End
266+
267+
It "errors when a wrong value is passed to the --scan flag"
268+
When run snyk iac test ../fixtures/iac/terraform-plan/tf-plan.json --experimental --scan=rsrc-changes
269+
The status should equal 2 # failure
270+
The output should include "Unsupported value"
271+
End
272+
273+
It "errors when no value is provided to the --scan flag"
274+
When run snyk iac test ../fixtures/iac/terraform-plan/tf-plan.json --experimental --scan
275+
The status should equal 2 # failure
276+
The output should include "Unsupported value"
277+
End
253278
End
254279
End

0 commit comments

Comments
 (0)
Please sign in to comment.