From 7f5d88b26006938539b744c8397b9d898a0e3364 Mon Sep 17 00:00:00 2001 From: homoluctus Date: Wed, 27 Nov 2019 15:30:39 +0900 Subject: [PATCH 1/3] Modify creating issue is optional --- action.yml | 10 +++++++--- src/index.ts | 26 +++++++++++++++++++------- src/interface.ts | 1 + src/trivy.ts | 14 ++++++-------- 4 files changed, 33 insertions(+), 18 deletions(-) diff --git a/action.yml b/action.yml index d963deb..8cae495 100644 --- a/action.yml +++ b/action.yml @@ -2,9 +2,6 @@ name: 'Trivy Action' description: 'Scan docker image vulnerability using Trivy and create GitHub Issue' author: 'homoluctus' inputs: - token: - description: 'GitHub access token' - required: true trivy_version: description: 'Trivy version' default: 'latest' @@ -24,6 +21,13 @@ inputs: description: 'Ignore unfixed vulnerabilities [true, false]' default: 'false' required: false + issue: + description: 'Decide whether to create a issue when vulnerabilities are found [true, false]' + default: 'true' + required: false + token: + description: 'GitHub access token used to create a issue' + required: false issue_title: description: 'Issue title' default: 'Security Alert' diff --git a/src/index.ts b/src/index.ts index 9d67632..043af89 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,31 +10,42 @@ import { async function run() { try { - const token: string = core.getInput('token', { required: true }); const trivyVersion: string = core .getInput('trivy_version') .replace(/^v/, ''); const image: string | undefined = core.getInput('image') || process.env.IMAGE_NAME; + const issueFlag: boolean = core.getInput('issue').toLowerCase() == 'true'; if (image === undefined || image === '') { throw new Error('Please specify scan target image name'); } - const trivyOptions: TrivyOption = { + const trivyOption: TrivyOption = { severity: core.getInput('severity').replace(/\s+/g, ''), vulnType: core.getInput('vuln_type').replace(/\s+/g, ''), ignoreUnfixed: core.getInput('ignore_unfixed').toLowerCase() === 'true', + format: issueFlag ? 'json' : 'table', }; const downloader = new Downloader(); const trivyCmdPath: string = await downloader.download(trivyVersion); - const result: Vulnerability[] = Trivy.scan( + const result: Vulnerability[] | string = Trivy.scan( trivyCmdPath, image, - trivyOptions + trivyOption ); - const issueContent: string = Trivy.parse(result); + + if (!issueFlag) { + core.info( + `Not create a issue because issue parameter is false. + Vulnerabilities: + ${result}` + ); + return; + } + + const issueContent: string = Trivy.parse(result as Vulnerability[]); if (issueContent === '') { core.info( @@ -43,7 +54,7 @@ async function run() { return; } - const issueOptions: IssueOption = { + const issueOption: IssueOption = { title: core.getInput('issue_title'), body: issueContent, labels: core @@ -55,7 +66,8 @@ async function run() { .replace(/\s+/g, '') .split(','), }; - const output: IssueResponse = await createIssue(token, issueOptions); + const token: string = core.getInput('token', { required: true }); + const output: IssueResponse = await createIssue(token, issueOption); core.setOutput('html_url', output.htmlUrl); core.setOutput('issue_number', output.issueNumber.toString()); } catch (error) { diff --git a/src/interface.ts b/src/interface.ts index 89498d0..267bbf8 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -14,6 +14,7 @@ export interface TrivyOption { severity: string; vulnType: string; ignoreUnfixed: boolean; + format: string; } export interface Vulnerability { diff --git a/src/trivy.ts b/src/trivy.ts index 8cd7c31..c1c1b23 100644 --- a/src/trivy.ts +++ b/src/trivy.ts @@ -123,7 +123,7 @@ export class Trivy { trivyPath: string, image: string, option: TrivyOption - ): Vulnerability[] { + ): Vulnerability[] | string { Trivy.validateOption(option); const args: string[] = [ @@ -132,22 +132,21 @@ export class Trivy { '--vuln-type', option.vulnType, '--format', - 'json', + option.format, '--quiet', '--no-progress', ]; - if (option.ignoreUnfixed) { - args.push('--ignore-unfixed'); - } - + if (option.ignoreUnfixed) args.push('--ignore-unfixed'); args.push(image); + const result: SpawnSyncReturns = spawnSync(trivyPath, args, { encoding: 'utf-8', }); if (result.stdout && result.stdout.length > 0) { - const vulnerabilities: Vulnerability[] = JSON.parse(result.stdout); + const vulnerabilities: Vulnerability[] | string = + option.format === 'json' ? JSON.parse(result.stdout) : result.stdout; if (vulnerabilities.length > 0) { return vulnerabilities; } @@ -185,7 +184,6 @@ export class Trivy { } issueContent += `${vulnTable}\n\n`; } - console.debug(issueContent); return issueContent; } From 4fbc427dec063873a6dd3d6661f1e4d80eefe303 Mon Sep 17 00:00:00 2001 From: homoluctus Date: Wed, 27 Nov 2019 15:30:44 +0900 Subject: [PATCH 2/3] Add test fot issue option --- .github/workflows/test.yml | 30 ++++++++++++++++++++++++++++++ __tests__/trivy.test.ts | 34 ++++++++++++++++++++++++++++++++-- dist/index.js | 26 ++++++++++++++++---------- 3 files changed, 78 insertions(+), 12 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 23fb4d4..475b988 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -94,3 +94,33 @@ jobs: job_name: ':ts: *test gitrivy*' channel: '#develop' url: ${{ secrets.SLACK_WEBHOOK }} + + test3: + name: Test not to create issue + runs-on: ubuntu-18.04 + steps: + - uses: actions/checkout@v1 + + - name: Install dependencies + run: npm install + + # - name: Test + # run: npm run test + + - name: Build + run: npm run build + + - name: Pull docker image + run: docker pull alpine:3.10.3 + + - uses: ./ + with: + issue: 'false' + + - uses: homoluctus/slatify@v1.9.0 + if: always() + with: + type: ${{ job.status }} + job_name: ':ts: *test gitrivy*' + channel: '#develop' + url: ${{ secrets.SLACK_WEBHOOK }} diff --git a/__tests__/trivy.test.ts b/__tests__/trivy.test.ts index 79f4423..447874a 100644 --- a/__tests__/trivy.test.ts +++ b/__tests__/trivy.test.ts @@ -128,9 +128,15 @@ describe('Scan', () => { severity: 'HIGH,CRITICAL', vulnType: 'os,library', ignoreUnfixed: true, + format: 'json', }; - const result: Vulnerability[] = Trivy.scan(trivyPath, image, options); + const result: Vulnerability[] | string = Trivy.scan( + trivyPath, + image, + options + ); expect(result.length).toBeGreaterThanOrEqual(1); + expect(result).toBeInstanceOf(Object); }); test('without ignoreUnfixed', () => { @@ -138,9 +144,31 @@ describe('Scan', () => { severity: 'HIGH,CRITICAL', vulnType: 'os,library', ignoreUnfixed: false, + format: 'json', }; - const result: Vulnerability[] = Trivy.scan(trivyPath, image, options); + const result: Vulnerability[] | string = Trivy.scan( + trivyPath, + image, + options + ); + expect(result.length).toBeGreaterThanOrEqual(1); + expect(result).toBeInstanceOf(Object); + }); + + test('with table format', () => { + const options: TrivyOption = { + severity: 'HIGH,CRITICAL', + vulnType: 'os,library', + ignoreUnfixed: false, + format: 'table', + }; + const result: Vulnerability[] | string = Trivy.scan( + trivyPath, + image, + options + ); expect(result.length).toBeGreaterThanOrEqual(1); + expect(result).toMatch(/alpine:3\.10/); }); test('with invalid severity', () => { @@ -148,6 +176,7 @@ describe('Scan', () => { severity: 'INVALID', vulnType: 'os,library', ignoreUnfixed: true, + format: 'json', }; expect(() => { Trivy.scan(trivyPath, image, invalidOption); @@ -159,6 +188,7 @@ describe('Scan', () => { severity: 'HIGH', vulnType: 'INVALID', ignoreUnfixed: true, + format: 'json', }; expect(() => { Trivy.scan(trivyPath, image, invalidOption); diff --git a/dist/index.js b/dist/index.js index 567cef9..257458f 100644 --- a/dist/index.js +++ b/dist/index.js @@ -6572,28 +6572,35 @@ const issue_1 = __webpack_require__(163); function run() { return __awaiter(this, void 0, void 0, function* () { try { - const token = core.getInput('token', { required: true }); const trivyVersion = core .getInput('trivy_version') .replace(/^v/, ''); const image = core.getInput('image') || process.env.IMAGE_NAME; + const issueFlag = core.getInput('issue').toLowerCase() == 'true'; if (image === undefined || image === '') { throw new Error('Please specify scan target image name'); } - const trivyOptions = { + const trivyOption = { severity: core.getInput('severity').replace(/\s+/g, ''), vulnType: core.getInput('vuln_type').replace(/\s+/g, ''), ignoreUnfixed: core.getInput('ignore_unfixed').toLowerCase() === 'true', + format: issueFlag ? 'json' : 'table', }; const downloader = new trivy_1.Downloader(); const trivyCmdPath = yield downloader.download(trivyVersion); - const result = trivy_1.Trivy.scan(trivyCmdPath, image, trivyOptions); + const result = trivy_1.Trivy.scan(trivyCmdPath, image, trivyOption); + if (!issueFlag) { + core.info(`Not create a issue because issue parameter is false. + Vulnerabilities: + ${result}`); + return; + } const issueContent = trivy_1.Trivy.parse(result); if (issueContent === '') { core.info('Vulnerabilities were not found.\nYour maintenance looks good 👍'); return; } - const issueOptions = { + const issueOption = { title: core.getInput('issue_title'), body: issueContent, labels: core @@ -6605,7 +6612,8 @@ function run() { .replace(/\s+/g, '') .split(','), }; - const output = yield issue_1.createIssue(token, issueOptions); + const token = core.getInput('token', { required: true }); + const output = yield issue_1.createIssue(token, issueOption); core.setOutput('html_url', output.htmlUrl); core.setOutput('issue_number', output.issueNumber.toString()); } @@ -13315,19 +13323,18 @@ class Trivy { '--vuln-type', option.vulnType, '--format', - 'json', + option.format, '--quiet', '--no-progress', ]; - if (option.ignoreUnfixed) { + if (option.ignoreUnfixed) args.push('--ignore-unfixed'); - } args.push(image); const result = child_process_1.spawnSync(trivyPath, args, { encoding: 'utf-8', }); if (result.stdout && result.stdout.length > 0) { - const vulnerabilities = JSON.parse(result.stdout); + const vulnerabilities = option.format === 'json' ? JSON.parse(result.stdout) : result.stdout; if (vulnerabilities.length > 0) { return vulnerabilities; } @@ -13359,7 +13366,6 @@ class Trivy { } issueContent += `${vulnTable}\n\n`; } - console.debug(issueContent); return issueContent; } static validateOption(option) { From d334820388a535b0ba49e15f66a4fba899b22f5e Mon Sep 17 00:00:00 2001 From: homoluctus Date: Wed, 27 Nov 2019 15:38:23 +0900 Subject: [PATCH 3/3] [ci] Devide test into unit test and integration test --- .github/workflows/test.yml | 22 +++------------------- .github/workflows/unittest.yml | 26 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 19 deletions(-) create mode 100644 .github/workflows/unittest.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 475b988..b83a434 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,22 +12,6 @@ env: IMAGE_NAME: alpine:3.10.1 jobs: - jest: - name: Test with jest - runs-on: ubuntu-18.04 - steps: - - uses: actions/checkout@v1 - - - uses: actions/setup-node@v1 - with: - node-version: '12.x' - - - name: Install dependencies - run: npm install - - - name: Jest - run: npm run test - test1: name: Test for with parameter runs-on: ubuntu-18.04 @@ -58,7 +42,7 @@ jobs: if: always() with: type: ${{ job.status }} - job_name: ':ts: *test gitrivy*' + job_name: ':ts: *test gitrivy (test1)*' channel: '#develop' url: ${{ secrets.SLACK_WEBHOOK }} @@ -91,7 +75,7 @@ jobs: if: always() with: type: ${{ job.status }} - job_name: ':ts: *test gitrivy*' + job_name: ':ts: *test gitrivy (test2)*' channel: '#develop' url: ${{ secrets.SLACK_WEBHOOK }} @@ -121,6 +105,6 @@ jobs: if: always() with: type: ${{ job.status }} - job_name: ':ts: *test gitrivy*' + job_name: ':ts: *test gitrivy (test3)*' channel: '#develop' url: ${{ secrets.SLACK_WEBHOOK }} diff --git a/.github/workflows/unittest.yml b/.github/workflows/unittest.yml new file mode 100644 index 0000000..30158f8 --- /dev/null +++ b/.github/workflows/unittest.yml @@ -0,0 +1,26 @@ +name: Unit Test + +on: + pull_request: + paths: + - 'src/**' + - 'dist/**' + - tsconfig.json + - package* + +jobs: + jest: + name: Test with jest + runs-on: ubuntu-18.04 + steps: + - uses: actions/checkout@v1 + + - uses: actions/setup-node@v1 + with: + node-version: '12.x' + + - name: Install dependencies + run: npm install + + - name: Jest + run: npm run test