Skip to content

Commit

Permalink
Merge pull request #1286 from snyk/DC-803/support-docker-token
Browse files Browse the repository at this point in the history
feat: Support using Docker JWT token for snyk test
  • Loading branch information
RotemS committed Jul 28, 2020
2 parents f35f39e + 28af1c7 commit 87151b8
Show file tree
Hide file tree
Showing 6 changed files with 208 additions and 5 deletions.
12 changes: 10 additions & 2 deletions src/cli/commands/test/index.ts
Expand Up @@ -5,7 +5,7 @@ import chalk from 'chalk';
import * as snyk from '../../../lib';
import * as config from '../../../lib/config';
import { isCI } from '../../../lib/is-ci';
import { apiTokenExists } from '../../../lib/api-token';
import { apiTokenExists, getDockerToken } from '../../../lib/api-token';
import { FAIL_ON, FailOn, SEVERITIES } from '../../../lib/snyk-test/common';
import * as Debug from 'debug';
import {
Expand Down Expand Up @@ -91,7 +91,15 @@ async function test(...args: MethodArgs): Promise<TestCommandResult> {
return Promise.reject(chalk.red.bold(error.message));
}

apiTokenExists();
try {
apiTokenExists();
} catch (err) {
if (options.docker && getDockerToken()) {
options.testDepGraphDockerEndpoint = '/docker-jwt/test-dep-graph';
} else {
throw err;
}
}

// Promise waterfall to test all other paths sequentially
for (const path of args as string[]) {
Expand Down
4 changes: 4 additions & 0 deletions src/lib/api-token.ts
Expand Up @@ -8,6 +8,10 @@ export function api() {
return config.api || config.TOKEN || userConfig.get('api');
}

export function getDockerToken(): string | undefined {
return process.env.SNYK_DOCKER_TOKEN;
}

export function apiTokenExists() {
const configured = api();
if (!configured) {
Expand Down
19 changes: 16 additions & 3 deletions src/lib/snyk-test/run-test.ts
Expand Up @@ -56,6 +56,7 @@ import { Payload, PayloadBody, DepTreeFromResolveDeps } from './types';
import { CallGraphError } from '@snyk/cli-interface/legacy/common';
import * as alerts from '../alerts';
import { abridgeErrorMessage } from '../error-format';
import { getDockerToken } from '../api-token';

const debug = debugModule('snyk');

Expand Down Expand Up @@ -557,14 +558,18 @@ async function assembleLocalPayloads(
});
body.callGraph = callGraph;
}

const reqUrl =
config.API +
(options.testDepGraphDockerEndpoint ||
options.vulnEndpoint ||
'/test-dep-graph');
const payload: Payload = {
method: 'POST',
url: config.API + (options.vulnEndpoint || '/test-dep-graph'),
url: reqUrl,
json: true,
headers: {
'x-is-ci': isCI(),
authorization: 'token ' + (snyk as any).api,
authorization: getAuthHeader(),
},
qs: common.assembleQueryString(options),
body,
Expand Down Expand Up @@ -616,6 +621,14 @@ function addPackageAnalytics(name: string, version: string): void {
analytics.add('package', name + '@' + version);
}

function getAuthHeader() {
const dockerToken = getDockerToken();
if (dockerToken) {
return 'bearer ' + dockerToken;
}
return 'token ' + snyk.api;
}

function countUniqueVulns(vulns: AnnotatedIssue[]): number {
const seen = {};
for (const curr of vulns) {
Expand Down
1 change: 1 addition & 0 deletions src/lib/types.ts
Expand Up @@ -21,6 +21,7 @@ export interface TestOptions {
reachableVulns?: boolean;
reachableVulnsTimeout?: number;
yarnWorkspaces?: boolean;
testDepGraphDockerEndpoint?: string | null;
}

export interface WizardOptions {
Expand Down
167 changes: 167 additions & 0 deletions test/acceptance/docker-token.test.ts
@@ -0,0 +1,167 @@
import * as tap from 'tap';
import * as cli from '../../src/cli/commands';
import { fakeServer } from './fake-server';
import * as sinon from 'sinon';

const { test } = tap;

const port = (process.env.PORT = process.env.SNYK_PORT = '12345');
const BASE_API = '/api/v1';
process.env.SNYK_API = 'http://localhost:' + port + BASE_API;
process.env.SNYK_HOST = 'http://localhost:' + port;
process.env.LOG_LEVEL = '0';
const apiKey = '123456789';
let oldkey;
let oldendpoint;
const server = fakeServer(BASE_API, apiKey);

// This import needs to come after the server init
// it causes the configured API url to be incorrect.
import * as plugins from '../../src/lib/plugins/index';

test('setup', async (t) => {
t.plan(3);

let key = await cli.config('get', 'api');
oldkey = key;
t.pass('existing user config captured: ' + oldkey);

key = await cli.config('get', 'endpoint');
oldendpoint = key;
t.pass('existing user endpoint captured: ' + oldendpoint);

await new Promise((resolve) => {
server.listen(port, resolve);
});
t.pass('started demo server');
t.end();
});

test('prime config', async (t) => {
await cli.config('unset', 'endpoint');
t.pass('endpoint removed');

await cli.config('unset', 'api');
t.pass('api key removed');

process.env.SNYK_DOCKER_TOKEN = 'docker-jwt-token';
t.pass('docker token set');

t.end();
});

test('`snyk test` with docker flag - docker token and no api key', async (t) => {
stubDockerPluginResponse(
plugins,
{
plugin: {
packageManager: 'deb',
},
package: {},
},
t,
);
try {
await cli.test('foo:latest', {
docker: true,
});
const req = server.popRequest();
t.equal(req.method, 'POST', 'makes POST request');
t.match(req.url, 'docker-jwt/test-dep-graph', 'posts to correct url');
} catch (err) {
t.fail('did not expect exception to be thrown ' + err);
}
});

test('`snyk test` with docker flag - docker token and api key', async (t) => {
stubDockerPluginResponse(
plugins,
{
plugin: {
packageManager: 'deb',
},
package: {},
},
t,
);
await cli.config('set', 'api=' + apiKey);
try {
await cli.test('foo:latest', {
docker: true,
});
const req = server.popRequest();
t.equal(req.method, 'POST', 'makes POST request');
t.match(req.url, 'test-dep-graph', 'posts to correct url');
} catch (err) {
t.fail('did not expect exception to be thrown ' + err);
}
await cli.config('unset', 'api');
t.end();
});

test('`snyk test` without docker flag - docker token and no api key', async (t) => {
stubDockerPluginResponse(
plugins,
{
plugin: {
packageManager: 'deb',
},
package: {},
},
t,
);
try {
await cli.test('foo:latest', {
docker: false,
});
t.fail('expected MissingApiTokenError');
} catch (err) {
t.equal(err.name, 'MissingApiTokenError', 'should throw if not docker');
}
});

test('teardown', async (t) => {
t.plan(4);

delete process.env.SNYK_API;
delete process.env.SNYK_HOST;
delete process.env.SNYK_PORT;
delete process.env.SNYK_DOCKER_TOKEN;
t.notOk(process.env.SNYK_PORT, 'fake env values cleared');

await new Promise((resolve) => {
server.close(resolve);
});
t.pass('server shutdown');

if (!oldkey) {
await cli.config('unset', 'api');
} else {
await cli.config('set', 'api=' + oldkey);
}

if (oldendpoint) {
await cli.config('set', `endpoint=${oldendpoint}`);
t.pass('user endpoint restored');
} else {
t.pass('no endpoint');
}
t.pass('user config restored');
t.end();
});

function stubDockerPluginResponse(plugins, fixture: string | object, t) {
const plugin = {
async inspect() {
return typeof fixture === 'object' ? fixture : require(fixture);
},
};
const spyPlugin = sinon.spy(plugin, 'inspect');
const loadPlugin = sinon.stub(plugins, 'loadPlugin');
loadPlugin
.withArgs(sinon.match.any, sinon.match({ docker: true }))
.returns(plugin);
t.teardown(loadPlugin.restore);

return spyPlugin;
}
10 changes: 10 additions & 0 deletions test/acceptance/fake-server.ts
Expand Up @@ -126,6 +126,16 @@ export function fakeServer(root, apikey) {
return next();
});

server.post(root + '/docker-jwt/test-dep-graph', (req, res, next) => {
res.send({
result: {
issuesData: {},
affectedPkgs: {},
},
});
return next();
});

server.post(root + '/test-iac', (req, res, next) => {
if (req.query.org && req.query.org === 'missing-org') {
res.status(404);
Expand Down

0 comments on commit 87151b8

Please sign in to comment.