Skip to content

Commit

Permalink
feat: support IaC configs test (K8s only)
Browse files Browse the repository at this point in the history
  • Loading branch information
orkamara committed Jun 29, 2020
1 parent 501b58b commit 5dc2c44
Show file tree
Hide file tree
Showing 10 changed files with 350 additions and 69 deletions.
60 changes: 60 additions & 0 deletions src/cli/commands/test/cloud-config-output.ts
@@ -0,0 +1,60 @@
import chalk from 'chalk';
import * as Debug from 'debug';
import { Options, TestOptions } from '../../../lib/types';
import { CloudConfigTestResult } from '../../../lib/snyk-test/cloud-config-test-result';
import { getSeverityValue } from './formatters';
import { formatIssue } from './formatters/remediation-based-format-issues';
import { AnnotatedCloudConfigIssue } from '../../../lib/snyk-test/cloud-config-test-result';

const debug = Debug('cloud-config-output');

export function getCloudConfigDisplayedOutput(
res: CloudConfigTestResult,
testOptions: Options & TestOptions,
testedInfoText: string,
meta: string,
prefix: string,
): string {
const issuesTextArray = [
chalk.bold.white('\nInfrastructure as code issues:'),
];

const NoNote = false;
const NotNew = false;

const issues: AnnotatedCloudConfigIssue[] = res.result.cloudConfigResults;
debug(`iac display output - ${issues.length} issues`);

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,
),
);
});

const issuesInfoOutput: string[] = [];
debug(`Cloud Config display output - ${issuesTextArray.length} issues text`);
if (issuesTextArray.length > 0) {
issuesInfoOutput.push(issuesTextArray.join('\n'));
}

let body = issuesInfoOutput.join('\n\n') + '\n\n' + meta;

const vulnCountText = `found ${issues.length} issues`;
const summary = testedInfoText + ', ' + chalk.red.bold(vulnCountText);

body = body + '\n\n' + summary;

return prefix + body;
}
51 changes: 29 additions & 22 deletions src/cli/commands/test/formatters/format-test-meta.ts
Expand Up @@ -2,9 +2,10 @@ import chalk from 'chalk';
import { rightPadWithSpaces } from '../../../../lib/right-pad';
import { TestOptions, Options } from '../../../../lib/types';
import { TestResult } from '../../../../lib/snyk-test/legacy';
import { CloudConfigTestResult } from '../../../../lib/snyk-test/cloud-config-test-result';

export function formatTestMeta(
res: TestResult,
res: TestResult | CloudConfigTestResult,
options: Options & TestOptions,
): string {
const padToLength = 19; // chars to align
Expand Down Expand Up @@ -41,31 +42,37 @@ export function formatTestMeta(
options.path,
);
}
if (res.docker && res.docker.baseImage) {
meta.push(
chalk.bold(rightPadWithSpaces('Base image: ', padToLength)) +
res.docker.baseImage,
);
}
if (res.payloadType !== 'k8sconfig') {
const legacyRes: TestResult = res as TestResult;
if (legacyRes.docker && legacyRes.docker.baseImage) {
meta.push(
chalk.bold(rightPadWithSpaces('Base image: ', padToLength)) +
legacyRes.docker.baseImage,
);
}

if (res.filesystemPolicy) {
meta.push(
chalk.bold(rightPadWithSpaces('Local Snyk policy: ', padToLength)) +
chalk.green('found'),
);
if (res.ignoreSettings && res.ignoreSettings.disregardFilesystemIgnores) {
if (legacyRes.filesystemPolicy) {
meta.push(
chalk.bold(
rightPadWithSpaces('Local Snyk policy ignored: ', padToLength),
) + chalk.red('yes'),
chalk.bold(rightPadWithSpaces('Local Snyk policy: ', padToLength)) +
chalk.green('found'),
);
if (
legacyRes.ignoreSettings &&
legacyRes.ignoreSettings.disregardFilesystemIgnores
) {
meta.push(
chalk.bold(
rightPadWithSpaces('Local Snyk policy ignored: ', padToLength),
) + chalk.red('yes'),
);
}
}
if (legacyRes.licensesPolicy) {
meta.push(
chalk.bold(rightPadWithSpaces('Licenses: ', padToLength)) +
chalk.green('enabled'),
);
}
}
if (res.licensesPolicy) {
meta.push(
chalk.bold(rightPadWithSpaces('Licenses: ', padToLength)) +
chalk.green('enabled'),
);
}

return meta.join('\n');
Expand Down
48 changes: 44 additions & 4 deletions src/cli/commands/test/index.ts
Expand Up @@ -24,6 +24,7 @@ import {
TestResult,
VulnMetaData,
} from '../../../lib/snyk-test/legacy';
import { CloudConfigTestResult } from '../../../lib/snyk-test/cloud-config-test-result';
import {
SupportedPackageManagers,
WIZARD_SUPPORTED_PACKAGE_MANAGERS,
Expand All @@ -43,6 +44,7 @@ import {
summariseVulnerableResults,
} from './formatters';
import * as utils from './utils';
import { getCloudConfigDisplayedOutput } from './cloud-config-output';

const debug = Debug('snyk-test');
const SEPARATOR = '\n-------------------------------------------------------\n';
Expand Down Expand Up @@ -156,7 +158,11 @@ async function test(...args: MethodArgs): Promise<TestCommandResult> {
}

const vulnerableResults = results.filter(
(res) => res.vulnerabilities && res.vulnerabilities.length,
(res) =>
(res.vulnerabilities && res.vulnerabilities.length) ||
(res.result &&
res.result.cloudConfigResults &&
res.result.cloudConfigResults.length),
);
const errorResults = results.filter((res) => res instanceof Error);
const notSuccess = errorResults.length > 0;
Expand All @@ -165,7 +171,9 @@ async function test(...args: MethodArgs): Promise<TestCommandResult> {
// resultOptions is now an array of 1 or more options used for
// the tests results is now an array of 1 or more test results
// values depend on `options.json` value - string or object
const errorMappedResults = createErrorMappedResultsForJsonOutput(results);
const errorMappedResults = !options.iac
? createErrorMappedResultsForJsonOutput(results)
: createErrorMappedResultsForJsonOutputForCloudConfig(results);
// backwards compat - strip array IFF only one result
const dataToSend =
errorMappedResults.length === 1
Expand Down Expand Up @@ -295,6 +303,25 @@ function createErrorMappedResultsForJsonOutput(results) {
return errorMappedResults;
}

function createErrorMappedResultsForJsonOutputForCloudConfig(results) {
const errorMappedResults = results.map((result) => {
// add json for when thrown exception
if (result instanceof Error) {
return {
ok: false,
error: result.message,
path: (result as any).path,
};
}
const res = { ...result, ...result.result };
delete res.result;
delete res.meta;
return res;
});

return errorMappedResults;
}

function shouldFail(vulnerableResults: any[], failOn: FailOn) {
// find reasons not to fail
if (failOn === 'all') {
Expand Down Expand Up @@ -378,7 +405,10 @@ function displayResult(
if (res instanceof Error) {
return prefix + res.message;
}
const issuesText = res.licensesPolicy ? 'issues' : 'vulnerabilities';
const issuesText =
res.licensesPolicy || projectType === 'k8sconfig'
? 'issues'
: 'vulnerabilities';
let pathOrDepsText = '';

if (res.hasOwnProperty('dependencyCount')) {
Expand Down Expand Up @@ -431,10 +461,20 @@ function displayResult(
);
}

if (res.packageManager === 'k8sconfig') {
return getCloudConfigDisplayedOutput(
(res as any) as CloudConfigTestResult,
options,
testedInfoText,
meta,
prefix,
);
}

// NOT OK => We found some vulns, let's format the vulns info

return getDisplayedOutput(
res,
res as TestResult,
options,
testedInfoText,
localPackageTest,
Expand Down
23 changes: 23 additions & 0 deletions src/lib/snyk-test/cloud-config-test-result.ts
@@ -0,0 +1,23 @@
import { BasicResultData, SEVERITY } from './legacy';

export interface AnnotatedCloudConfigIssue {
id: string;
title: string;
description: string;
severity: SEVERITY;
isIgnored: boolean;
cloudConfigPath: string[];
type: string;
subType: string;
}

export interface CloudConfigTestResult extends BasicResultData {
targetFile: string;
projectName: string;
displayTargetFile: string; // used for display only
foundProjectCount: number;
result: {
cloudConfigResults: AnnotatedCloudConfigIssue[];
projectType: string;
};
}
24 changes: 22 additions & 2 deletions src/lib/snyk-test/index.js
Expand Up @@ -4,7 +4,11 @@ const detect = require('../detect');
const runTest = require('./run-test');
const chalk = require('chalk');
const pm = require('../package-managers');
const { UnsupportedPackageManagerError } = require('../errors');
const cloudConfigProjects = require('../cloud-config/cloud-config-projects');
const {
UnsupportedPackageManagerError,
NoSupportedCloudConfigFileError,
} = require('../errors');

async function test(root, options, callback) {
if (typeof options === 'function') {
Expand All @@ -28,7 +32,11 @@ async function test(root, options, callback) {
function executeTest(root, options) {
try {
if (!options.allProjects) {
options.packageManager = detect.detectPackageManager(root, options);
if (options.iac) {
options.packageManager = detect.isCloudConfigProject(root, options);
} else {
options.packageManager = detect.detectPackageManager(root, options);
}
}
return run(root, options).then((results) => {
for (const res of results) {
Expand All @@ -49,6 +57,18 @@ function executeTest(root, options) {
}

function run(root, options) {
if (options.iac) {
const projectType = options.packageManager;
if (
!cloudConfigProjects.TEST_SUPPORTED_CLOUD_CONFIG_PROJECTS.includes(
projectType,
)
) {
throw new NoSupportedCloudConfigFileError(projectType);
}
return runTest(projectType, root, options);
}

const packageManager = options.packageManager;
if (
!(
Expand Down
19 changes: 11 additions & 8 deletions src/lib/snyk-test/legacy.ts
Expand Up @@ -122,24 +122,27 @@ export interface IgnoreSettings {
disregardFilesystemIgnores: boolean;
}

export interface LegacyVulnApiResult {
vulnerabilities: AnnotatedIssue[];
export interface BasicResultData {
ok: boolean;
dependencyCount: number;
payloadType?: string;
org: string;
policy: string;
isPrivate: boolean;
summary: string;
packageManager?: SupportedProjectTypes;
severityThreshold?: string;
}

export interface LegacyVulnApiResult extends BasicResultData {
vulnerabilities: AnnotatedIssue[];
dependencyCount: number;
policy: string;
licensesPolicy: object | null;
packageManager: string;
ignoreSettings: IgnoreSettings | null;
summary: string;
docker?: {
baseImage?: any;
binariesVulns?: unknown;
baseImageRemediation?: BaseImageRemediation;
};
severityThreshold?: string;

filesystemPolicy?: boolean;
uniqueCount?: any;
remediation?: RemediationChanges;
Expand Down
23 changes: 23 additions & 0 deletions src/lib/snyk-test/payload-schema.ts
@@ -0,0 +1,23 @@
//TODO(orka): future - change this file
import { SupportedCloudConfigs } from '../cloud-config/cloud-config-projects';

interface Scan {
type: string;
targetFile: string;
data: any;
}

export interface CloudConfigFile {
fileContent: string;
fileType: 'yaml' | 'yml' | 'json';
}

export interface CloudConfigScan extends Scan {
type: SupportedCloudConfigs;
targetFile: string;
data: CloudConfigFile;
targetFileRelativePath: string;
originalProjectName: string;
policy: string;
projectNameOverride?: string;
}

0 comments on commit 5dc2c44

Please sign in to comment.