From a3ec49da0b8164e92735f32e87f15fd22c9fedfa Mon Sep 17 00:00:00 2001 From: anthogez Date: Tue, 11 Jan 2022 12:01:24 +0200 Subject: [PATCH] feat: support unmanaged snyk security url Co-authored-by: @danlucian lucian.rosu@snyk.io Allows users with snykUnmanagedVulnDB ff in org level to visualize https://security.snyk.io/vuln/SNYK-UNMANAGED-CURL-2317584 rather than nvd url Depends on https://github.com/snyk/snyk-cpp-plugin/pull/61 with feature flag Screen Shot 2022-01-11 at 11 53 47 without feature flag Screen Shot 2022-01-11 at 11 53 03 --- package-lock.json | 22 +++++------ package.json | 2 +- src/lib/ecosystems/common.ts | 5 +++ src/lib/ecosystems/monitor.ts | 4 +- src/lib/ecosystems/test.ts | 17 +++++++-- src/lib/feature-flags/index.ts | 17 +++++++++ src/lib/types.ts | 1 + test/jest/unit/lib/ecosystems/common.spec.ts | 15 ++++++++ .../lib/feature-flags/feature-flags.spec.ts | 37 +++++++++++++++++++ 9 files changed, 103 insertions(+), 17 deletions(-) create mode 100644 src/lib/ecosystems/common.ts create mode 100644 test/jest/unit/lib/ecosystems/common.spec.ts create mode 100644 test/jest/unit/lib/feature-flags/feature-flags.spec.ts diff --git a/package-lock.json b/package-lock.json index 814b892c94..bd45fec83d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -64,7 +64,7 @@ "rimraf": "^2.6.3", "semver": "^6.0.0", "snyk-config": "4.0.0", - "snyk-cpp-plugin": "2.14.1", + "snyk-cpp-plugin": "2.15.0", "snyk-docker-plugin": "^4.33.0", "snyk-go-plugin": "1.18.0", "snyk-gradle-plugin": "3.17.0", @@ -22349,9 +22349,9 @@ } }, "node_modules/snyk-cpp-plugin": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/snyk-cpp-plugin/-/snyk-cpp-plugin-2.14.1.tgz", - "integrity": "sha512-Wjp8hQa8vq9MuVue+9xFCyrVlDbBBo+Kteik3czBddR/4RO32v3YjVqnpGKS0Y8B88WliKha4cyX+cAdTwyCTg==", + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/snyk-cpp-plugin/-/snyk-cpp-plugin-2.15.0.tgz", + "integrity": "sha512-VYz6BWpVRJzxdsLKrjVXecc2MqJnc2AWkH1LOjQYXbozAVnE6x7vpbxZiJ39wvuurCqD4ntYQU4EnmlSjhFmdg==", "dependencies": { "@snyk/dep-graph": "^1.19.3", "chalk": "^4.1.0", @@ -44495,7 +44495,7 @@ "sinon": "^4.0.0", "snyk": "file:", "snyk-config": "4.0.0", - "snyk-cpp-plugin": "2.14.1", + "snyk-cpp-plugin": "2.15.0", "snyk-docker-plugin": "^4.33.0", "snyk-go-plugin": "1.18.0", "snyk-gradle-plugin": "3.17.0", @@ -62230,9 +62230,9 @@ } }, "snyk-cpp-plugin": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/snyk-cpp-plugin/-/snyk-cpp-plugin-2.14.1.tgz", - "integrity": "sha512-Wjp8hQa8vq9MuVue+9xFCyrVlDbBBo+Kteik3czBddR/4RO32v3YjVqnpGKS0Y8B88WliKha4cyX+cAdTwyCTg==", + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/snyk-cpp-plugin/-/snyk-cpp-plugin-2.15.0.tgz", + "integrity": "sha512-VYz6BWpVRJzxdsLKrjVXecc2MqJnc2AWkH1LOjQYXbozAVnE6x7vpbxZiJ39wvuurCqD4ntYQU4EnmlSjhFmdg==", "requires": { "@snyk/dep-graph": "^1.19.3", "chalk": "^4.1.0", @@ -65584,9 +65584,9 @@ } }, "snyk-cpp-plugin": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/snyk-cpp-plugin/-/snyk-cpp-plugin-2.14.1.tgz", - "integrity": "sha512-Wjp8hQa8vq9MuVue+9xFCyrVlDbBBo+Kteik3czBddR/4RO32v3YjVqnpGKS0Y8B88WliKha4cyX+cAdTwyCTg==", + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/snyk-cpp-plugin/-/snyk-cpp-plugin-2.15.0.tgz", + "integrity": "sha512-VYz6BWpVRJzxdsLKrjVXecc2MqJnc2AWkH1LOjQYXbozAVnE6x7vpbxZiJ39wvuurCqD4ntYQU4EnmlSjhFmdg==", "requires": { "@snyk/dep-graph": "^1.19.3", "chalk": "^4.1.0", diff --git a/package.json b/package.json index a58445dfc8..1a33cfbe44 100644 --- a/package.json +++ b/package.json @@ -119,7 +119,7 @@ "rimraf": "^2.6.3", "semver": "^6.0.0", "snyk-config": "4.0.0", - "snyk-cpp-plugin": "2.14.1", + "snyk-cpp-plugin": "2.15.0", "snyk-docker-plugin": "^4.33.0", "snyk-go-plugin": "1.18.0", "snyk-gradle-plugin": "3.17.0", diff --git a/src/lib/ecosystems/common.ts b/src/lib/ecosystems/common.ts new file mode 100644 index 0000000000..1f464860a7 --- /dev/null +++ b/src/lib/ecosystems/common.ts @@ -0,0 +1,5 @@ +import { Ecosystem } from './types'; + +export function isUnmanagedEcosystem(ecosystem: Ecosystem): boolean { + return ecosystem === 'cpp'; +} diff --git a/src/lib/ecosystems/monitor.ts b/src/lib/ecosystems/monitor.ts index b147af6192..2159ee57a1 100644 --- a/src/lib/ecosystems/monitor.ts +++ b/src/lib/ecosystems/monitor.ts @@ -31,6 +31,7 @@ import { validateProjectAttributes, validateTags, } from '../../cli/commands/monitor'; +import { isUnmanagedEcosystem } from './common'; const SEPARATOR = '\n-------------------------------------------------------\n'; @@ -81,8 +82,7 @@ async function selectAndExecuteMonitorStrategy( scanResultsByPath: { [dir: string]: ScanResult[] }, options: Options, ): Promise<[EcosystemMonitorResult[], EcosystemMonitorError[]]> { - const isUnmanagedEcosystem = ecosystem === 'cpp'; - return isUnmanagedEcosystem + return isUnmanagedEcosystem(ecosystem) ? await resolveAndMonitorFacts(scanResultsByPath, options) : await monitorDependencies(scanResultsByPath, options); } diff --git a/src/lib/ecosystems/test.ts b/src/lib/ecosystems/test.ts index 811ea0e853..34cf6a8626 100644 --- a/src/lib/ecosystems/test.ts +++ b/src/lib/ecosystems/test.ts @@ -10,6 +10,8 @@ import { TestDependenciesResponse } from '../snyk-test/legacy'; import { assembleQueryString } from '../snyk-test/common'; import { getAuthHeader } from '../api-token'; import { resolveAndTestFacts } from './resolve-test-facts'; +import { hasFeatureFlag } from '../feature-flags'; +import { isUnmanagedEcosystem } from './common'; export async function testEcosystem( ecosystem: Ecosystem, @@ -44,11 +46,21 @@ export async function testEcosystem( } const emptyResults: ScanResult[] = []; const scanResults = emptyResults.concat(...Object.values(scanResultsByPath)); + + const enhancedOptions = { ...options }; + + if (isUnmanagedEcosystem(ecosystem)) { + enhancedOptions.supportUnmanagedVulnDB = await hasFeatureFlag( + 'snykUnmanagedVulnDB', + options, + ); + } + const readableResult = await plugin.display( scanResults, testResults, errors, - options, + enhancedOptions, ); return TestCommandResult.createHumanReadableTestCommandResult( @@ -62,8 +74,7 @@ export async function selectAndExecuteTestStrategy( scanResultsByPath: { [dir: string]: ScanResult[] }, options: Options, ): Promise<[TestResult[], string[]]> { - const isUnmanagedEcosystem = ecosystem === 'cpp'; - return isUnmanagedEcosystem + return isUnmanagedEcosystem(ecosystem) ? await resolveAndTestFacts(ecosystem, scanResultsByPath, options) : await testDependencies(scanResultsByPath, options); } diff --git a/src/lib/feature-flags/index.ts b/src/lib/feature-flags/index.ts index 9c27ccb652..a7ceab4280 100644 --- a/src/lib/feature-flags/index.ts +++ b/src/lib/feature-flags/index.ts @@ -3,6 +3,8 @@ import { getAuthHeader } from '../api-token'; import config from '../config'; import { assembleQueryString } from '../snyk-test/common'; import { OrgFeatureFlagResponse } from './types'; +import { Options } from '../types'; +import { AuthFailedError } from '../errors'; export async function isFeatureFlagSupportedForOrg( featureFlag: string, @@ -21,3 +23,18 @@ export async function isFeatureFlagSupportedForOrg( return (response as any).body; } + +export async function hasFeatureFlag( + featureFlag: string, + options: Options, +): Promise { + const { code, error, ok } = await isFeatureFlagSupportedForOrg( + featureFlag, + options.org, + ); + + if (code === 401 || code === 403) { + throw AuthFailedError(error, code); + } + return ok; +} diff --git a/src/lib/types.ts b/src/lib/types.ts index 92dce2546f..000fb34824 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -90,6 +90,7 @@ export interface Options { tags?: string; 'target-reference'?: string; 'exclude-base-image-vulns'?: boolean; + supportUnmanagedVulnDB?: boolean; } // TODO(kyegupov): catch accessing ['undefined-properties'] via noImplicitAny diff --git a/test/jest/unit/lib/ecosystems/common.spec.ts b/test/jest/unit/lib/ecosystems/common.spec.ts new file mode 100644 index 0000000000..afd81184b7 --- /dev/null +++ b/test/jest/unit/lib/ecosystems/common.spec.ts @@ -0,0 +1,15 @@ +import { isUnmanagedEcosystem } from '../../../../../src/lib/ecosystems/common'; + +describe('isUnmanagedEcosystem fn', () => { + it.each` + actual | expected + ${'cpp'} | ${true} + ${'docker'} | ${false} + ${'code'} | ${false} + `( + 'should validate that given $actual as input, is considered or not an unmanaged ecosystem', + ({ actual, expected }) => { + expect(isUnmanagedEcosystem(actual)).toEqual(expected); + }, + ); +}); diff --git a/test/jest/unit/lib/feature-flags/feature-flags.spec.ts b/test/jest/unit/lib/feature-flags/feature-flags.spec.ts new file mode 100644 index 0000000000..02dbd37eca --- /dev/null +++ b/test/jest/unit/lib/feature-flags/feature-flags.spec.ts @@ -0,0 +1,37 @@ +import { hasFeatureFlag } from '../../../../../src/lib/feature-flags'; +import * as request from '../../../../../src/lib/request'; + +describe('hasFeatureFlag fn', () => { + it.each` + hasFlag | expected + ${true} | ${true} + ${false} | ${false} + `( + 'should validate that given an org with feature flag $hasFlag as input, hasFeatureFlag returns $expected', + async ({ hasFlag, expected }) => { + jest + .spyOn(request, 'makeRequest') + .mockResolvedValue({ body: { code: 200, ok: hasFlag } } as any); + + const result = await hasFeatureFlag('test-ff', { path: 'test-path' }); + expect(result).toEqual(expected); + }, + ); + + it('should throw error if there are authentication/authorization failures', async () => { + jest.spyOn(request, 'makeRequest').mockResolvedValue({ + body: { code: 401, error: 'Unauthorized', ok: false }, + } as any); + + await expect( + hasFeatureFlag('test-ff', { path: 'test-path' }), + ).rejects.toThrowError('Unauthorized'); + + jest.spyOn(request, 'makeRequest').mockResolvedValue({ + body: { code: 403, error: 'Forbidden', ok: false }, + } as any); + await expect( + hasFeatureFlag('test-ff', { path: 'test-path' }), + ).rejects.toThrowError('Forbidden'); + }); +});