Skip to content

Commit

Permalink
feat: container static scanning in test command
Browse files Browse the repository at this point in the history
The CLI no longer scans container images by using Docker.
Distroless scanning is also enabled by default.

This is enabled for both "snyk test --docker" and "snyk container test".

Additionally, clean up the code by moving ecosystems.ts and splitting to its own module. Allows to better organize it as the module is going to grow with more functionality (currently has test but next up is monitor).

Additional changes:
- extract types from ecosystems module
- move ecosystems plugins to their own file
- move ecosystem test in its own file
  • Loading branch information
ivanstanev committed Oct 9, 2020
1 parent 6d0076e commit 9ee6641
Show file tree
Hide file tree
Showing 14 changed files with 608 additions and 247 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
"snyk-config": "3.1.1",
"snyk-cpp-plugin": "2.0.0",
"legacy-snyk-docker-plugin": "snyk/snyk-docker-plugin#v3.26.2",
"snyk-docker-plugin": "4.1.1",
"snyk-go-plugin": "1.16.2",
"snyk-gradle-plugin": "3.9.0",
"snyk-module": "3.1.0",
Expand Down
4 changes: 2 additions & 2 deletions src/cli/commands/test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ import {
} from './formatters';
import * as utils from './utils';
import { getIacDisplayedOutput, createSarifOutputForIac } from './iac-output';
import { getEcosystem, testEcosystem } from '../../../lib/ecosystems';
import { getEcosystemForTest, testEcosystem } from '../../../lib/ecosystems';
import { TestLimitReachedError } from '../../../lib/errors';
import { isMultiProjectScan } from '../../../lib/is-multi-project-scan';
import { createSarifOutputForContainers } from './sarif-output';
Expand Down Expand Up @@ -115,7 +115,7 @@ async function test(...args: MethodArgs): Promise<TestCommandResult> {
}
}

const ecosystem = getEcosystem(options);
const ecosystem = getEcosystemForTest(options);
if (ecosystem) {
try {
const commandResult = await testEcosystem(
Expand Down
160 changes: 0 additions & 160 deletions src/lib/ecosystems.ts

This file was deleted.

32 changes: 32 additions & 0 deletions src/lib/ecosystems/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Options } from '../types';
import { Ecosystem } from './types';

export { testEcosystem } from './test';
export { getPlugin } from './plugins';

/**
* Ecosystems are listed here if you opt in to the new plugin test flow.
* This is a breaking change to the old plugin formats, so only a select few
* plugins currently work with it.
*
* Currently container scanning is not yet ready to work with this flow,
* hence this is in a separate function from getEcosystem().
*/
export function getEcosystemForTest(options: Options): Ecosystem | null {
if (options.source) {
return 'cpp';
}
return null;
}

export function getEcosystem(options: Options): Ecosystem | null {
if (options.source) {
return 'cpp';
}

const isDockerDesktopIntegration = options['isDockerUser'];
if (options.docker && !isDockerDesktopIntegration) {
return 'docker';
}
return null;
}
15 changes: 15 additions & 0 deletions src/lib/ecosystems/plugins.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import * as cppPlugin from 'snyk-cpp-plugin';
import * as dockerPlugin from 'snyk-docker-plugin';
import { Ecosystem, EcosystemPlugin } from './types';

const EcosystemPlugins: {
readonly [ecosystem in Ecosystem]: EcosystemPlugin;
} = {
cpp: cppPlugin,
// TODO: not any
docker: dockerPlugin as any,
};

export function getPlugin(ecosystem: Ecosystem): EcosystemPlugin {
return EcosystemPlugins[ecosystem];
}
33 changes: 33 additions & 0 deletions src/lib/ecosystems/policy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import * as path from 'path';

import { SupportedPackageManagers } from '../package-managers';
import { findAndLoadPolicy } from '../policy';
import { Options, PolicyOptions } from '../types';
import { ScanResult } from './types';

export async function findAndLoadPolicyForScanResult(
scanResult: ScanResult,
options: Options & PolicyOptions,
): Promise<object | undefined> {
const targetFileRelativePath = scanResult.identity.targetFile
? path.join(path.resolve(`${options.path}`), scanResult.identity.targetFile)
: undefined;
const targetFileDir = targetFileRelativePath
? path.parse(targetFileRelativePath).dir
: undefined;
const scanType = options.docker
? 'docker'
: (scanResult.identity.type as SupportedPackageManagers);
// TODO: fix this and send only send when we used resolve-deps for node
// it should be a ExpandedPkgTree type instead
const packageExpanded = undefined;

const policy = (await findAndLoadPolicy(
options.path,
scanType,
options,
packageExpanded,
targetFileDir,
)) as object | undefined; // TODO: findAndLoadPolicy() does not return a string!
return policy;
}
89 changes: 89 additions & 0 deletions src/lib/ecosystems/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import * as snyk from '../index';
import * as config from '../config';
import { isCI } from '../is-ci';
import { makeRequest } from '../request/promise';
import { Options } from '../types';
import { TestCommandResult } from '../../cli/commands/types';
import * as spinner from '../../lib/spinner';
import { Ecosystem, ScanResult, TestResult } from './types';
import { getPlugin } from './plugins';
import { TestDependenciesResponse } from '../snyk-test/legacy';
import { assembleQueryString } from '../snyk-test/common';

export async function testEcosystem(
ecosystem: Ecosystem,
paths: string[],
options: Options,
): Promise<TestCommandResult> {
const plugin = getPlugin(ecosystem);
const scanResultsByPath: { [dir: string]: ScanResult[] } = {};
for (const path of paths) {
options.path = path;
const pluginResponse = await plugin.scan(options);
scanResultsByPath[path] = pluginResponse.scanResults;
}
const [testResults, errors] = await testDependencies(
scanResultsByPath,
options,
);
const stringifiedData = JSON.stringify(testResults, null, 2);
if (options.json) {
return TestCommandResult.createJsonTestCommandResult(stringifiedData);
}
const emptyResults: ScanResult[] = [];
const scanResults = emptyResults.concat(...Object.values(scanResultsByPath));
const readableResult = await plugin.display(
scanResults,
testResults,
errors,
options,
);

return TestCommandResult.createHumanReadableTestCommandResult(
readableResult,
stringifiedData,
);
}

async function testDependencies(
scans: {
[dir: string]: ScanResult[];
},
options: Options,
): Promise<[TestResult[], string[]]> {
const results: TestResult[] = [];
const errors: string[] = [];
for (const [path, scanResults] of Object.entries(scans)) {
await spinner(`Testing dependencies in ${path}`);
for (const scanResult of scanResults) {
const payload = {
method: 'POST',
url: `${config.API}/test-dependencies`,
json: true,
headers: {
'x-is-ci': isCI(),
authorization: 'token ' + snyk.api,
},
body: {
scanResult,
},
qs: assembleQueryString(options),
};
try {
const response = await makeRequest<TestDependenciesResponse>(payload);
results.push({
issues: response.result.issues,
issuesData: response.result.issuesData,
depGraphData: response.result.depGraphData,
});
} catch (error) {
if (error.code >= 400 && error.code < 500) {
throw new Error(error.message);
}
errors.push('Could not test dependencies in ' + path);
}
}
}
spinner.clearAll();
return [results, errors];
}

0 comments on commit 9ee6641

Please sign in to comment.