Skip to content

Commit

Permalink
feat: improve IAC test output
Browse files Browse the repository at this point in the history
  • Loading branch information
orkamara committed Jun 30, 2020
1 parent 9cb4e4b commit 2a9c71b
Show file tree
Hide file tree
Showing 11 changed files with 148 additions and 70 deletions.
3 changes: 2 additions & 1 deletion .github/CODEOWNERS
Validating CODEOWNERS rules …
Expand Up @@ -10,4 +10,5 @@ src/lib/iac/ @snyk/cloudconfig
src/lib/snyk-test/iac-test-result.ts @snyk/cloudconfig
src/lib/snyk-test/payload-schema.ts @snyk/cloudconfig
src/lib/snyk-test/run-iac-test.ts @snyk/cloudconfig
test/acceptance/cli-test/cli-test.iac-k8s.spec.ts @snyk/cloudconfig
test/acceptance/cli-test/cli-test.iac-k8s.spec.ts @snyk/cloudconfig
src/lib/errors/invalid-iac-file.ts @snyk/cloudconfig
3 changes: 1 addition & 2 deletions help/iac.txt
Expand Up @@ -13,12 +13,11 @@ Options:
-h, --help
--json .................................. Return results in JSON format.
--project-name=<string> ................. Specify a custom Snyk project name.
--policy-path=<path> .................... Manually pass a path to a snyk policy file.
--severity-threshold=<low|medium|high>... Only report issues of provided level or higher.

Examples:

$ snyk iac test /path/to/Kubernetes.yaml
$ snyk iac test --file=/path/to/Kubernetes.yaml


For more information see https://support.snyk.io/hc/en-us/categories/360001342678-Infrastructure-as-code
14 changes: 12 additions & 2 deletions src/cli/commands/test/formatters/format-test-meta.ts
Expand Up @@ -3,6 +3,7 @@ import { rightPadWithSpaces } from '../../../../lib/right-pad';
import { TestOptions, Options } from '../../../../lib/types';
import { TestResult } from '../../../../lib/snyk-test/legacy';
import { IacTestResult } from '../../../../lib/snyk-test/iac-test-result';
import { capitalizePackageManager } from '../iac-output';

export function formatTestMeta(
res: TestResult | IacTestResult,
Expand All @@ -14,9 +15,18 @@ export function formatTestMeta(
const openSource = res.isPrivate ? 'no' : 'yes';
const meta = [
chalk.bold(rightPadWithSpaces('Organization: ', padToLength)) + res.org,
chalk.bold(rightPadWithSpaces('Package manager: ', padToLength)) +
packageManager,
];
if (options.iac) {
meta.push(
chalk.bold(rightPadWithSpaces('Type: ', padToLength)) +
capitalizePackageManager(packageManager),
);
} else {
meta.push(
chalk.bold(rightPadWithSpaces('Package manager: ', padToLength)) +
packageManager,
);
}
if (targetFile) {
meta.push(
chalk.bold(rightPadWithSpaces('Target file: ', padToLength)) + targetFile,
Expand Down
2 changes: 1 addition & 1 deletion src/cli/commands/test/formatters/legacy-format-issue.ts
Expand Up @@ -107,7 +107,7 @@ function createSeverityBasedIssueHeading(severity, type, packageName, isNew) {
);
}

function titleCaseText(text) {
export function titleCaseText(text) {
return text[0].toUpperCase() + text.slice(1);
}

Expand Down
Expand Up @@ -414,7 +414,7 @@ function constructUnfixableText(
return unfixableIssuesTextArray;
}

function printPath(path: string[]) {
export function printPath(path: string[]) {
return path.slice(1).join(' > ');
}

Expand Down
94 changes: 78 additions & 16 deletions src/cli/commands/test/iac-output.ts
@@ -1,16 +1,72 @@
import chalk from 'chalk';
import * as Debug from 'debug';
import { Options, TestOptions } from '../../../lib/types';
import { IacTestResult } from '../../../lib/snyk-test/iac-test-result';
import { getSeverityValue } from './formatters';
import { formatIssue } from './formatters/remediation-based-format-issues';
import { printPath } from './formatters/remediation-based-format-issues';
import { AnnotatedIacIssue } from '../../../lib/snyk-test/iac-test-result';

import { titleCaseText } from './formatters/legacy-format-issue';
const debug = Debug('iac-output');

function formatIacIssue(
issue: AnnotatedIacIssue,
isNew: boolean,
path: string[],
): string {
const severitiesColourMapping = {
low: {
colorFunc(text) {
return chalk.blueBright(text);
},
},
medium: {
colorFunc(text) {
return chalk.yellowBright(text);
},
},
high: {
colorFunc(text) {
return chalk.redBright(text);
},
},
};
const newBadge = isNew ? ' (new)' : '';
const name = issue.subType ? ` in ${chalk.bold(issue.subType)}` : '';

let introducedBy = '';
if (path) {
// In this mode, we show only one path by default, for compactness
const pathStr = printPath(path);
introducedBy = `\n introduced by ${pathStr}`;
}

const description = extractOverview(issue.description).trim();
const descriptionLine = `\n ${description}\n`;

return (
severitiesColourMapping[issue.severity].colorFunc(
` ✗ ${chalk.bold(issue.title)}${newBadge} [${titleCaseText(
issue.severity,
)} Severity]`,
) +
` [${issue.id}]` +
name +
introducedBy +
descriptionLine
);
}

function extractOverview(description: string): string {
if (!description) {
return '';
}

const overviewRegExp = /## Overview([\s\S]*?)(?=##|(# Details))/m;
const overviewMatches = overviewRegExp.exec(description);
return (overviewMatches && overviewMatches[1]) || '';
}

export function getIacDisplayedOutput(
res: IacTestResult,
testOptions: Options & TestOptions,
testedInfoText: string,
meta: string,
prefix: string,
Expand All @@ -19,7 +75,6 @@ export function getIacDisplayedOutput(
chalk.bold.white('\nInfrastructure as code issues:'),
];

const NoNote = false;
const NotNew = false;

const issues: AnnotatedIacIssue[] = res.result.cloudConfigResults;
Expand All @@ -28,18 +83,8 @@ export function getIacDisplayedOutput(
issues
.sort((a, b) => getSeverityValue(b.severity) - getSeverityValue(a.severity))
.forEach((issue) => {
const path: string[][] = [issue.cloudConfigPath];
issuesTextArray.push(
formatIssue(
issue.id,
issue.title,
issue.severity,
NotNew,
issue.subType,
path,
testOptions,
NoNote,
),
formatIacIssue(issue, NotNew, issue.cloudConfigPath),
);
});

Expand All @@ -58,3 +103,20 @@ export function getIacDisplayedOutput(

return prefix + body;
}

export function capitalizePackageManager(type) {
switch (type) {
case 'k8sconfig': {
return 'Kubernetes';
}
case 'helmconfig': {
return 'Helm';
}
case 'terraformconfig': {
return 'Terraform';
}
default: {
return 'Infrastracture as Code';
}
}
}
9 changes: 6 additions & 3 deletions src/cli/commands/test/index.ts
Expand Up @@ -399,7 +399,11 @@ function displayResult(
const projectType =
(res.packageManager as SupportedProjectTypes) || options.packageManager;
const localPackageTest = isLocalFolder(options.path);
const prefix = chalk.bold.white('\nTesting ' + options.path + '...\n\n');
let testingPath = options.path;
if (options.iac && options.file) {
testingPath = options.file;
}
const prefix = chalk.bold.white('\nTesting ' + testingPath + '...\n\n');

// handle errors by extracting their message
if (res instanceof Error) {
Expand All @@ -411,7 +415,7 @@ function displayResult(
: 'vulnerabilities';
let pathOrDepsText = '';

if (res.hasOwnProperty('dependencyCount')) {
if (res.dependencyCount) {
pathOrDepsText += res.dependencyCount + ' dependencies';
} else {
pathOrDepsText += options.path;
Expand Down Expand Up @@ -464,7 +468,6 @@ function displayResult(
if (res.packageManager === 'k8sconfig') {
return getIacDisplayedOutput(
(res as any) as IacTestResult,
options,
testedInfoText,
meta,
prefix,
Expand Down
14 changes: 6 additions & 8 deletions src/lib/errors/invalid-iac-file.ts
Expand Up @@ -4,12 +4,11 @@ import { CustomError } from './custom-error';
export function NotSupportedIacFileError(atLocations: string[]) {
const locationsStr = atLocations.join(', ');
const errorMsg =
'Not supported infrastruction as code target files in ' +
'Not supported infrastructure as code target files in ' +
locationsStr +
'.\nPlease see our documentation for supported languages and ' +
'target files: ' +
'.\nPlease see our documentation for supported target files: ' +
chalk.underline(
'https://support.snyk.io/hc/en-us/articles/360000911957-Language-support',
'https://support.snyk.io/hc/en-us/articles/360006368877-Scan-and-fix-security-issues-in-your-Kubernetes-configuration-files',
) +
' and make sure you are in the right directory.';

Expand All @@ -22,12 +21,11 @@ export function NotSupportedIacFileError(atLocations: string[]) {
export function IllegalIacFileError(atLocations: string[]): CustomError {
const locationsStr = atLocations.join(', ');
const errorMsg =
'Illegal infrastruction as code target file ' +
'Illegal infrastructure as code target file ' +
locationsStr +
'.\nPlease see our documentation for supported languages and ' +
'target files: ' +
'.\nPlease see our documentation for supported target files: ' +
chalk.underline(
'https://support.snyk.io/hc/en-us/articles/360000911957-Language-support',
'https://support.snyk.io/hc/en-us/articles/360006368877-Scan-and-fix-security-issues-in-your-Kubernetes-configuration-files',
) +
' and make sure you are in the right directory.';

Expand Down
2 changes: 1 addition & 1 deletion src/lib/snyk-test/run-test.ts
Expand Up @@ -331,7 +331,7 @@ async function assembleLocalPayloads(
if (options.docker) {
analysisTypeText = 'docker dependencies for ';
} else if (options.iac) {
analysisTypeText = 'Infrastruction as code configurations for ';
analysisTypeText = 'Infrastructure as code configurations for ';
} else if (options.packageManager) {
analysisTypeText = options.packageManager + ' dependencies for ';
}
Expand Down
69 changes: 37 additions & 32 deletions test/acceptance/cli-test/cli-test.iac-k8s.spec.ts
@@ -1,9 +1,6 @@
import * as sinon from 'sinon';
import * as _ from '@snyk/lodash';
import { getWorkspaceJSON } from '../workspace-helper';
import { CommandResult } from '../../../src/cli/commands/types';
// import * as fs from 'fs';
// import * as path from 'path';

import { AcceptanceTests } from './cli-test.acceptance.test';

Expand Down Expand Up @@ -82,11 +79,7 @@ export const IacK8sTests: AcceptanceTests = {

const meta = res.slice(res.indexOf('Organization:')).split('\n');
t.match(meta[0], /Organization:\s+test-org/, 'organization displayed');
t.match(
meta[1],
/Package manager:\s+k8sconfig/,
'package manager displayed',
);
t.match(meta[1], /Type:\s+Kubernetes/, 'Type displayed');
t.match(
meta[2],
/Target file:\s+multi-file.yaml/,
Expand Down Expand Up @@ -124,17 +117,41 @@ export const IacK8sTests: AcceptanceTests = {

t.match(
res,
'Tested 0 dependencies for known issues, found 3 issues',
'Tested iac-kubernetes for known issues, found 3 issues',
'3 issue',
);

const issues = res
.slice(
res.indexOf('Infrastructure as code issues:'),
res.indexOf('Organization:'),
)
.split('\n');
t.ok(issues[1].includes('[SNYK-CC-K8S-'), 'Snyk id');

t.ok(
issues[2].trim().startsWith('introduced by'),
'Introduced by line',
);

t.ok(issues[3], 'description');

t.ok(issues[4] === '', 'Empty line after description');

t.ok(issues[5].includes('[SNYK-CC-K8S-'), 'Snyk id');

t.ok(
issues[6].trim().startsWith('introduced by'),
'Introduced by line',
);

t.ok(issues[7], 'description');

t.ok(issues[8] === '', 'Empty line after description');

const meta = res.slice(res.indexOf('Organization:')).split('\n');
t.match(meta[0], /Organization:\s+test-org/, 'organization displayed');
t.match(
meta[1],
/Package manager:\s+k8sconfig/,
'package manager displayed',
);
t.match(meta[1], /Type:\s+Kubernetes/, 'Type displayed');
t.match(
meta[2],
/Target file:\s+multi-file.yaml/,
Expand Down Expand Up @@ -177,17 +194,13 @@ export const IacK8sTests: AcceptanceTests = {

t.match(
res,
'Tested 0 dependencies for known issues, found 3 issues',
'Tested iac-kubernetes for known issues, found 3 issues',
'3 issue',
);

const meta = res.slice(res.indexOf('Organization:')).split('\n');
t.match(meta[0], /Organization:\s+test-org/, 'organization displayed');
t.match(
meta[1],
/Package manager:\s+k8sconfig/,
'package manager displayed',
);
t.match(meta[1], /Type:\s+Kubernetes/, 'Type displayed');
t.match(
meta[2],
/Target file:\s+multi-file.yaml/,
Expand Down Expand Up @@ -275,17 +288,13 @@ export const IacK8sTests: AcceptanceTests = {

t.match(
res,
'Tested 0 dependencies for known issues, found 2 issues',
'Tested iac-kubernetes for known issues, found 2 issues',
'2 issue',
);

const meta = res.slice(res.indexOf('Organization:')).split('\n');
t.match(meta[0], /Organization:\s+test-org/, 'organization displayed');
t.match(
meta[1],
/Package manager:\s+k8sconfig/,
'package manager displayed',
);
t.match(meta[1], /Type:\s+Kubernetes/, 'Type displayed');
t.match(
meta[2],
/Target file:\s+multi-file.yaml/,
Expand Down Expand Up @@ -373,17 +382,13 @@ export const IacK8sTests: AcceptanceTests = {

t.match(
res,
'Tested 0 dependencies for known issues, found 1 issues',
'Tested iac-kubernetes for known issues, found 1 issues',
'1 issue',
);

const meta = res.slice(res.indexOf('Organization:')).split('\n');
t.match(meta[0], /Organization:\s+test-org/, 'organization displayed');
t.match(
meta[1],
/Package manager:\s+k8sconfig/,
'package manager displayed',
);
t.match(meta[1], /Type:\s+Kubernetes/, 'Type displayed');
t.match(
meta[2],
/Target file:\s+multi-file.yaml/,
Expand Down

0 comments on commit 2a9c71b

Please sign in to comment.