From f9af925b897d8af5f7e0026b8bca9346261abc93 Mon Sep 17 00:00:00 2001 From: Koushik Dey <57134947+koushdey@users.noreply.github.com> Date: Mon, 28 Mar 2022 16:14:53 +0530 Subject: [PATCH] Specify Trivy version in workflow file (version 2) (#130) (#138) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * specify trivy version * Trivy version logging * Fix wrongly assigned variable for trivyversion. * Updated readme with new parameter. * Rename variable for Trivy version. * Build JS * Update src/trivyHelper.ts Co-authored-by: Koushik Dey <57134947+koushdey@users.noreply.github.com> * build trivyhelper.js - change debug message * Fix failing tests and add new test Co-authored-by: Koushik Dey <57134947+koushdey@users.noreply.github.com> Co-authored-by: Randi Øyri --- __mocks__/@actions/core.ts | 3 +- __tests__/trivyHelper.test.ts | 68 +++++++++++++++++++++++++++-------- action.yaml | 4 +++ lib/inputHelper.js | 1 + lib/trivyHelper.js | 12 ++++--- src/inputHelper.ts | 1 + src/trivyHelper.ts | 13 ++++--- 7 files changed, 79 insertions(+), 23 deletions(-) diff --git a/__mocks__/@actions/core.ts b/__mocks__/@actions/core.ts index 9b01ef95..d164f4f8 100644 --- a/__mocks__/@actions/core.ts +++ b/__mocks__/@actions/core.ts @@ -6,7 +6,8 @@ let __mockInputValues = { 'username': 'username', 'password': 'password', 'severit-threshold': 'HIGH', - 'run-quality-checks': 'true' + 'run-quality-checks': 'true', + 'trivy-version': 'latest' }; function __setMockInputValues(mockObject) { diff --git a/__tests__/trivyHelper.test.ts b/__tests__/trivyHelper.test.ts index 531fdc89..c6af5b94 100644 --- a/__tests__/trivyHelper.test.ts +++ b/__tests__/trivyHelper.test.ts @@ -1,25 +1,37 @@ + jest.mock('fs'); jest.mock('os'); + + describe('Run Trivy', () => { const mockedFs = require('fs'); const mockedOs = require('os'); - const mockedToolCache = require('@actions/tool-cache'); - const mockedFileHelper = require('../src/fileHelper'); - mockedFileHelper.getContainerScanDirectory = jest.fn().mockImplementation(() =>{ - return 'test/_temp/containerscan_123'; - }); - let mockFile = { - 'releaseDownloadedPath': JSON.stringify({ tag_name: 'v1.1.1' }) - }; - let cachedTools = { - 'trivy': true, - 'dockle': true - }; - mockedFs.__setMockFiles(mockFile); + function setupFileMock() { + const mockedFileHelper = require('../src/fileHelper'); + mockedFileHelper.getContainerScanDirectory = jest.fn().mockImplementation(() => { + return 'test/_temp/containerscan_123'; + }); + + return { + 'releaseDownloadedPath': JSON.stringify({tag_name: 'v1.1.1'}) + }; + } + mockedFs.__setMockFiles(setupFileMock()); + + function toolCache() { + const mockedToolCache = require('@actions/tool-cache'); + let cachedTools = { + 'trivy': true, + 'dockle': true + }; + return {mockedToolCache, cachedTools}; + } + let {mockedToolCache, cachedTools} = toolCache(); mockedToolCache.__setToolCached(cachedTools); + afterAll(() => { jest.clearAllMocks(); jest.resetModules(); @@ -36,7 +48,7 @@ describe('Run Trivy', () => { expect(mockedToolCache.downloadTool).toHaveBeenCalledTimes(1); }); - test('Trivy binaries are not present in the cache', async () => { + test('Trivy binaries are not present in the cache', async () => { cachedTools['trivy'] = false; mockedToolCache.__setToolCached(cachedTools); const runner = require('../src/trivyHelper'); @@ -48,4 +60,32 @@ describe('Run Trivy', () => { expect(mockedToolCache.find).toHaveReturnedWith(undefined); expect(mockedToolCache.downloadTool).toHaveBeenCalledTimes(2); }); + + test('Trivy binaries are not present in the cache and not use latest version', async () => { + jest.resetModules(); + const mockedFs = require('fs'); + const mockedOs = require('os'); + mockedFs.__setMockFiles(setupFileMock()); + let {mockedToolCache, cachedTools} = toolCache(); + cachedTools['trivy'] = false; + mockedToolCache.__setToolCached(cachedTools); + jest.mock('../src/inputHelper', () => { + const inputHelper = jest.requireActual('../src/inputHelper'); + return { + __esModule: true, + ...inputHelper, + trivyVersion: '0.23.0', + }; + }); + + const runner = require('../src/trivyHelper'); + const trivyResult = await runner.runTrivy(); + expect(trivyResult).toHaveProperty('status', 0); + expect(trivyResult).toHaveProperty('timestamp'); + expect(mockedOs.type).toHaveBeenCalledTimes(1); + expect(mockedToolCache.find).toHaveReturnedWith(undefined); + expect(mockedToolCache.downloadTool).toHaveBeenCalledTimes(1); + }); + + }); diff --git a/action.yaml b/action.yaml index 75b32473..b9f635d6 100644 --- a/action.yaml +++ b/action.yaml @@ -18,6 +18,10 @@ inputs: description: 'Github token' default: ${{ github.token }} required: true + trivy-version: + description: 'Version of Trivy to run' + default: 'latest' + required: false run-quality-checks: description: 'Add additional checks to ensure the image is secure and follows best practices and CIS standards' default: 'true' diff --git a/lib/inputHelper.js b/lib/inputHelper.js index 7fec74a3..c01d52c0 100644 --- a/lib/inputHelper.js +++ b/lib/inputHelper.js @@ -12,6 +12,7 @@ exports.imageName = core.getInput("image-name"); exports.githubToken = core.getInput("token"); exports.username = core.getInput("username"); exports.password = core.getInput("password"); +exports.trivyVersion = core.getInput("trivy-version"); exports.severityThreshold = core.getInput("severity-threshold"); exports.runQualityChecks = core.getInput("run-quality-checks"); function isRunQualityChecksEnabled() { diff --git a/lib/trivyHelper.js b/lib/trivyHelper.js index 54ef8aa5..ba8a653b 100644 --- a/lib/trivyHelper.js +++ b/lib/trivyHelper.js @@ -71,11 +71,15 @@ function runTrivy() { exports.runTrivy = runTrivy; function getTrivy() { return __awaiter(this, void 0, void 0, function* () { - const latestTrivyVersion = yield getLatestTrivyVersion(); - let cachedToolPath = toolCache.find(exports.trivyToolName, latestTrivyVersion); + let version = inputHelper.trivyVersion; + if (version == 'latest') { + version = yield getLatestTrivyVersion(); + } + core.debug(util.format('Use Trivy version: %s', version)); + let cachedToolPath = toolCache.find(exports.trivyToolName, version); if (!cachedToolPath) { let trivyDownloadPath; - const trivyDownloadUrl = getTrivyDownloadUrl(latestTrivyVersion); + const trivyDownloadUrl = getTrivyDownloadUrl(version); const trivyDownloadDir = `${process.env['GITHUB_WORKSPACE']}/_temp/tools/trivy`; core.debug(util.format("Could not find trivy in cache, downloading from %s", trivyDownloadUrl)); try { @@ -85,7 +89,7 @@ function getTrivy() { throw new Error(util.format("Failed to download trivy from %s: %s", trivyDownloadUrl, error.toString())); } const untarredTrivyPath = yield toolCache.extractTar(trivyDownloadPath); - cachedToolPath = yield toolCache.cacheDir(untarredTrivyPath, exports.trivyToolName, latestTrivyVersion); + cachedToolPath = yield toolCache.cacheDir(untarredTrivyPath, exports.trivyToolName, version); } const trivyToolPath = cachedToolPath + "/" + exports.trivyToolName; fs.chmodSync(trivyToolPath, "777"); diff --git a/src/inputHelper.ts b/src/inputHelper.ts index 03bcece4..1096196d 100644 --- a/src/inputHelper.ts +++ b/src/inputHelper.ts @@ -4,6 +4,7 @@ export const imageName = core.getInput("image-name"); export const githubToken = core.getInput("token"); export const username = core.getInput("username"); export const password = core.getInput("password"); +export const trivyVersion = core.getInput("trivy-version"); export const severityThreshold = core.getInput("severity-threshold"); export const runQualityChecks = core.getInput("run-quality-checks"); diff --git a/src/trivyHelper.ts b/src/trivyHelper.ts index f9a31520..1ea0e822 100644 --- a/src/trivyHelper.ts +++ b/src/trivyHelper.ts @@ -44,6 +44,7 @@ export async function runTrivy(): Promise { const trivyCommand = "image"; const imageName = inputHelper.imageName; + const trivyOptions: ExecOptions = await getTrivyExecOptions(); console.log(`Scanning for vulnerabilties in image: ${imageName}`); const trivyToolRunner = new ToolRunner(trivyPath, [trivyCommand, imageName], trivyOptions); @@ -58,12 +59,16 @@ export async function runTrivy(): Promise { } export async function getTrivy(): Promise { - const latestTrivyVersion = await getLatestTrivyVersion(); - let cachedToolPath = toolCache.find(trivyToolName, latestTrivyVersion); + let version = inputHelper.trivyVersion; + if(version == 'latest'){ + version = await getLatestTrivyVersion(); + } + core.debug(util.format('Use Trivy version: %s', version)); + let cachedToolPath = toolCache.find(trivyToolName, version); if (!cachedToolPath) { let trivyDownloadPath; - const trivyDownloadUrl = getTrivyDownloadUrl(latestTrivyVersion); + const trivyDownloadUrl = getTrivyDownloadUrl(version); const trivyDownloadDir = `${process.env['GITHUB_WORKSPACE']}/_temp/tools/trivy`; core.debug(util.format("Could not find trivy in cache, downloading from %s", trivyDownloadUrl)); @@ -74,7 +79,7 @@ export async function getTrivy(): Promise { } const untarredTrivyPath = await toolCache.extractTar(trivyDownloadPath); - cachedToolPath = await toolCache.cacheDir(untarredTrivyPath, trivyToolName, latestTrivyVersion); + cachedToolPath = await toolCache.cacheDir(untarredTrivyPath, trivyToolName, version); } const trivyToolPath = cachedToolPath + "/" + trivyToolName;