Skip to content

Commit 5408835

Browse files
authored
Merge pull request #3824 from mercedes-benz/feature-2078-gha-execute-define-false-positives-before-scan-starts
Add defineFalsePositives step to GH action before scan execution #2078
2 parents dbdedc3 + 980c310 commit 5408835

15 files changed

+334
-8
lines changed

github-actions/scan/README.adoc

+3
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ To be able to use this action you need a SecHub project. Check the https://merce
7979
# OPTIONAL: job (pipeline) will be marked as failed if SecHub finds something
8080
# DEFAULT: true
8181
fail-job-with-findings: true
82+
83+
# OPTIONAL: File that defines false positives. This step is executed before the scan. The SecHub cli defineFalsePositives overwrites the false positives on SecHub side with the ones defined in this file. Uses the SecHub cli getFalsePositives to get the current false positives and extend the returned false positive list.
84+
define-false-positives: 'sechub-false-positives.json'
8285
----
8386

8487
[IMPORTANT]

github-actions/scan/__test__/integrationtest.test.ts

+49
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { getFieldFromJson } from '../src/json-helper';
1010
import * as launcher from '../src/launcher';
1111
import { LaunchContext } from '../src/launcher';
1212
import { IntegrationTestContext } from './integrationtest/testframework';
13+
import { defineFalsePositives } from '../src/sechub-cli';
1314
jest.mock('@actions/core');
1415
jest.mock('@actions/artifact');
1516

@@ -321,6 +322,40 @@ describe('integrationtest non-generated config', () => {
321322

322323
});
323324

325+
describe('integrationtest define-false-positives generated config', () => {
326+
327+
test('codescan first red then result is green after define-false-positives is executed', async () => {
328+
329+
/* prepare */
330+
initInputMap();
331+
mockedInputMap.set(input.PARAM_INCLUDED_FOLDERS, '__test__/integrationtest/test-sources');
332+
mockedInputMap.set(input.PARAM_PROJECT_NAME, 'test-project-7');
333+
334+
/* execute */
335+
const result1 = await launcher.launch();
336+
337+
/* test */
338+
assertTrafficLight(result1, 'RED');
339+
assertLastClientExitCode(result1, 1);
340+
assertUploadDone();
341+
342+
/* prepare 2 */
343+
const defineFalsePositivesFile = createDefineFalsePositivesFile(result1);
344+
mockedInputMap.set(input.PARAM_DEFINE_FALSE_POSITIVES, defineFalsePositivesFile);
345+
346+
/* execute 2 */
347+
const result2 = await launcher.launch();
348+
349+
/* test 2 */
350+
assertLastClientExitCode(result2, 0);
351+
assertTrafficLight(result2, 'GREEN');
352+
assertUploadDone();
353+
354+
/* clean up */
355+
deleteFile(defineFalsePositivesFile);
356+
});
357+
358+
});
324359

325360
function assertActionIsMarkedAsFailed(){
326361
expect(setFailed).toHaveBeenCalledTimes(1);
@@ -376,3 +411,17 @@ function loadSpdxJsonReportAndAssertItContains(context: LaunchContext, textPart:
376411

377412
expect(spdxJson).toContain(textPart);
378413
}
414+
415+
function createDefineFalsePositivesFile(context: LaunchContext): string {
416+
const defineFalsePositivesJson = `{"apiVersion":"1.0","type":"falsePositiveDataList","jobData":[{"jobUUID":"${context.jobUUID}","findingId":1}]}`
417+
const fileName = "defineFalsePositivesFile.json";
418+
const filePath = `./${fileName}`;
419+
fs.writeFileSync(filePath, defineFalsePositivesJson);
420+
return filePath;
421+
}
422+
423+
function deleteFile(file: string) {
424+
if (fs.existsSync(file)) {
425+
fs.unlinkSync(file);
426+
}
427+
}

github-actions/scan/__test__/integrationtest/03-init_sechub_data.sh

+1
Original file line numberDiff line numberDiff line change
@@ -81,5 +81,6 @@ createData 3 codescan red
8181
createData 4 webscan red
8282
createData 5 secretscan yellow
8383
createData 6 licensescan green
84+
createData 7 codescan red-def-fp
8485

8586
./sechub-api.sh project_set_whitelist_uris test-project-4 https://vulnerable.demo.example.com
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"name": "executor-codescan-red-def-fp",
3+
"productIdentifier": "PDS_CODESCAN",
4+
"setup": {
5+
"baseURL": "https://localhost:8444",
6+
"credentials": {
7+
"user": "pds-inttest-techuser",
8+
"password": "pds-inttest-apitoken"
9+
},
10+
"jobParameters": [
11+
{
12+
"key": "sechub.productexecutor.pds.timetowait.nextcheck.milliseconds",
13+
"value": "500"
14+
},
15+
{
16+
"key": "sechub.productexecutor.pds.trustall.certificates",
17+
"value": "true"
18+
},
19+
{
20+
"key": "pds.mocking.disabled",
21+
"value": "true"
22+
},
23+
{
24+
"key": "pds.config.productidentifier",
25+
"value": "codescan_demo_red_def_fp"
26+
},
27+
{
28+
"key": "pds.config.use.sechub.storage",
29+
"value": "true"
30+
},
31+
{
32+
"key": "pds.config.supported.datatypes",
33+
"value": "SOURCE"
34+
}
35+
]
36+
},
37+
"executorVersion": 1,
38+
"enabled": true
39+
}

github-actions/scan/__test__/integrationtest/test-config/gha_integrationtest_pds-config.json

+6
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@
2020
"scanType": "codeScan",
2121
"description": "This is only a fake code scan - used by integration tests. The code scan will just return one high vulnerability"
2222
},
23+
{
24+
"id": "codescan_demo_red_def_fp",
25+
"path": "./__test__/integrationtest/test-scripts/pds-codescan-demo-red-define-false-positives.sh",
26+
"scanType": "codeScan",
27+
"description": "This is only a fake code scan - used by integration tests. The code scan will just return one high vulnerability, which is used for false positives handling."
28+
},
2329
{
2430
"id": "webscan_demo_red",
2531
"path": "./__test__/integrationtest/test-scripts/pds-webscan-demo-red.sh",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
{
2+
"version": "2.1.0",
3+
"$schema": "http://json.schemastore.org/sarif-2.1.0-rtm.4",
4+
"runs": [
5+
{
6+
"tool": {
7+
"driver": {
8+
"name": "ESLint",
9+
"informationUri": "https://eslint.org",
10+
"rules": [
11+
{
12+
"id": "no-unused-vars",
13+
"shortDescription": {
14+
"text": "disallow unused variables"
15+
},
16+
"helpUri": "https://eslint.org/docs/rules/no-unused-vars",
17+
"properties": {
18+
"category": "Variables"
19+
}
20+
}
21+
]
22+
}
23+
},
24+
"artifacts": [
25+
{
26+
"location": {
27+
"uri": "file:///C:/dev/sarif/sarif-tutorials/samples/Introduction/simple-example.js"
28+
}
29+
}
30+
],
31+
"results": [
32+
{
33+
"level": "error",
34+
"message": {
35+
"text": "'x' is assigned a value but never used."
36+
},
37+
"locations": [
38+
{
39+
"physicalLocation": {
40+
"artifactLocation": {
41+
"uri": "file:///C:/dev/sarif/sarif-tutorials/samples/Introduction/simple-example.js",
42+
"index": 0
43+
},
44+
"region": {
45+
"startLine": 1,
46+
"startColumn": 5
47+
}
48+
}
49+
}
50+
],
51+
"ruleId": "no-unused-vars",
52+
"ruleIndex": 0
53+
}
54+
]
55+
}
56+
]
57+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/bin/bash
2+
# SPDX-License-Identifier: MIT
3+
4+
cat "__test__/integrationtest/test-product-output/example-codescan-sarif-output-red.json" > "${PDS_JOB_RESULT_FILE}"

github-actions/scan/__test__/sechub-cli.test.ts

+44-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// SPDX-License-Identifier: MIT
22

3-
import {extractJobUUID, getReport, scan} from '../src/sechub-cli';
3+
import {defineFalsePositives, extractJobUUID, getReport, scan} from '../src/sechub-cli';
44
import {execFileSync} from 'child_process';
55
import {sanitize} from "../src/shell-arg-sanitizer";
66

@@ -220,4 +220,47 @@ describe('getReport', function () {
220220
expect(sanitize).toBeCalledWith('json');
221221
});
222222

223+
});
224+
225+
describe('defineFalsePositives', function () {
226+
227+
it('sanitizes shell arguments', () => {
228+
/* prepare */
229+
const context: any = {
230+
clientExecutablePath: '/path/to/sechub-cli',
231+
projectName: 'project-name',
232+
defineFalsePositivesFile: '/path/to/define-false-positive-file'
233+
};
234+
(sanitize as jest.Mock).mockImplementation((arg) => {
235+
return arg;
236+
});
237+
238+
/* execute */
239+
defineFalsePositives(context);
240+
241+
/* test */
242+
expect(sanitize).toBeCalledTimes(3);
243+
expect(sanitize).toBeCalledWith('/path/to/sechub-cli');
244+
expect(sanitize).toBeCalledWith('project-name');
245+
expect(sanitize).toBeCalledWith('/path/to/define-false-positive-file');
246+
});
247+
248+
249+
it.each([null, undefined, ''])('context.lastClientExitCode is 0 when defineFalsePositivesFile is %s', (file) => {
250+
/* prepare */
251+
const context: any = {
252+
defineFalsePositivesFile: file
253+
};
254+
(sanitize as jest.Mock).mockImplementation((arg) => {
255+
return arg;
256+
});
257+
258+
/* execute */
259+
defineFalsePositives(context);
260+
261+
/* test */
262+
expect(sanitize).toBeCalledTimes(0);
263+
expect(context.lastClientExitCode).toBe(0);
264+
expect(context.defineFalsePositivesFile).toBe(file);
265+
});
223266
});

github-actions/scan/action.yml

+4
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ inputs:
5757
description: 'Job will be marked as failed if SecHub finds something'
5858
required: false
5959
default: true
60+
define-false-positives:
61+
description: 'The file that defines false positives. This step is executed before the scan. The SecHub cli defineFalsePositives overwrites the false positives on SecHub side with the ones defined in this file. Uses the SecHub cli getFalsePositives to get the current false positives and extend the returned false positive list.'
62+
required: false
6063

6164
outputs:
6265
scan-trafficlight:
@@ -115,6 +118,7 @@ runs:
115118
trust-all: ${{ inputs.trust-all }}
116119
debug: ${{ inputs.debug }}
117120
fail-job-with-findings: ${{ inputs.fail-job-with-findings }}
121+
define-false-positives: ${{ inputs.define-false-positives }}
118122
ACTIONS_RUNTIME_TOKEN: ${{ env.ACTIONS_RUNTIME_TOKEN }}
119123
ACTIONS_RUNTIME_URL: ${{ env.ACTIONS_RUNTIME_URL }}
120124
ACTIONS_CACHE_URL: ${{ env.ACTIONS_CACHE_URL }}

github-actions/scan/dist/index.js

+56-2
Original file line numberDiff line numberDiff line change
@@ -28120,6 +28120,7 @@ const PARAM_FAIL_JOB_ON_FINDING = 'fail-job-with-findings';
2812028120
const PARAM_TRUST_ALL = 'trust-all';
2812128121
const PARAM_SCAN_TYPES = 'scan-types';
2812228122
const PARAM_CONTENT_TYPE = 'content-type';
28123+
const PARAM_DEFINE_FALSE_POSITIVES = 'define-false-positives';
2812328124
const INPUT_DATA_DEFAULTS = {
2812428125
configPath: '',
2812528126
url: '',
@@ -28136,6 +28137,7 @@ const INPUT_DATA_DEFAULTS = {
2813628137
trustAll: '',
2813728138
scanTypes: '',
2813828139
contentType: '',
28140+
defineFalsePositives: '',
2813928141
};
2814028142
function resolveGitHubInputData() {
2814128143
return {
@@ -28154,6 +28156,7 @@ function resolveGitHubInputData() {
2815428156
trustAll: getParam(PARAM_TRUST_ALL),
2815528157
scanTypes: getParam(PARAM_SCAN_TYPES),
2815628158
contentType: getParam(PARAM_CONTENT_TYPE),
28159+
defineFalsePositives: getParam(PARAM_DEFINE_FALSE_POSITIVES),
2815728160
};
2815828161
}
2815928162
/**
@@ -28401,6 +28404,40 @@ function getReport(jobUUID, reportFormat, context) {
2840128404
context.lastClientExitCode = error.status;
2840228405
}
2840328406
}
28407+
/**
28408+
* Executes the defineFalsePositives method of the SecHub CLI. Sets the client exitcode inside context.
28409+
* @param context launch context
28410+
*/
28411+
function defineFalsePositives(context) {
28412+
if (!context.defineFalsePositivesFile) {
28413+
core.info("No define-false-positive file was specified. Skipping step defineFalsePositives...");
28414+
context.lastClientExitCode = 0;
28415+
return;
28416+
}
28417+
const clientExecutablePath = sanitize(context.clientExecutablePath);
28418+
const projectIdValue = sanitize(context.projectName);
28419+
const defineFalsePositivesFile = sanitize(context.defineFalsePositivesFile);
28420+
try {
28421+
const output = (0,external_child_process_.execFileSync)(clientExecutablePath, ['-project', projectIdValue, '-file', defineFalsePositivesFile, 'defineFalsePositives'], {
28422+
env: {
28423+
SECHUB_SERVER: process.env.SECHUB_SERVER,
28424+
SECHUB_USERID: process.env.SECHUB_USERID,
28425+
SECHUB_APITOKEN: process.env.SECHUB_APITOKEN,
28426+
SECHUB_PROJECT: process.env.SECHUB_PROJECT,
28427+
SECHUB_DEBUG: process.env.SECHUB_DEBUG,
28428+
SECHUB_TRUSTALL: process.env.SECHUB_TRUSTALL,
28429+
},
28430+
encoding: 'utf-8'
28431+
});
28432+
core.info('defineFalsePositives executed successfully');
28433+
context.lastClientExitCode = 0;
28434+
}
28435+
catch (error) {
28436+
core.error(`Error executing defineFalsePositives command: ${error.message}`);
28437+
core.error(`Standard error: ${error.stderr}`);
28438+
context.lastClientExitCode = error.status;
28439+
}
28440+
}
2840428441

2840528442
;// CONCATENATED MODULE: ./src/json-helper.ts
2840628443
// SPDX-License-Identifier: MIT
@@ -46125,13 +46162,20 @@ async function getRedirectUrl(url) {
4612546162

4612646163

4612746164

46165+
4612846166
/**
4612946167
* Starts the launch process
4613046168
* @returns launch context
4613146169
*/
4613246170
async function launch() {
4613346171
const context = await createContext();
4613446172
await init(context);
46173+
executeDefineFalsePositives(context);
46174+
if (context.lastClientExitCode > 0) {
46175+
// In case of an error during the defineFalsePositives step, we fail the action here!
46176+
failAction(context.lastClientExitCode);
46177+
return context;
46178+
}
4613546179
executeScan(context);
4613646180
await postScan(context);
4613746181
return context;
@@ -46151,7 +46195,8 @@ const LAUNCHER_CONTEXT_DEFAULTS = {
4615146195
workspaceFolder: '',
4615246196
secHubReportJsonObject: undefined,
4615346197
secHubReportJsonFileName: '',
46154-
trafficLight: 'OFF'
46198+
trafficLight: 'OFF',
46199+
defineFalsePositivesFile: '',
4615546200
};
4615646201
/**
4615746202
* Creates the initial launch context
@@ -46188,7 +46233,8 @@ async function createContext() {
4618846233
secHubJsonFilePath: generatedSecHubJsonFilePath,
4618946234
workspaceFolder: workspaceFolder,
4619046235
trafficLight: LAUNCHER_CONTEXT_DEFAULTS.trafficLight,
46191-
debug: gitHubInputData.debug == 'true'
46236+
debug: gitHubInputData.debug == 'true',
46237+
defineFalsePositivesFile: gitHubInputData.defineFalsePositives,
4619246238
};
4619346239
}
4619446240
function createSafeBuilderData(gitHubInputData) {
@@ -46212,6 +46258,14 @@ function executeScan(context) {
4621246258
scan(context);
4621346259
logExitCode(context.lastClientExitCode);
4621446260
}
46261+
/**
46262+
* Executes defineFalsePositive action of the SecHub GO client.
46263+
* @param context launch context
46264+
*/
46265+
function executeDefineFalsePositives(context) {
46266+
defineFalsePositives(context);
46267+
logExitCode(context.lastClientExitCode);
46268+
}
4621546269
async function postScan(context) {
4621646270
core.debug(`postScan(1): context.lastExitCode=${context.lastClientExitCode}`);
4621746271
if (context.lastClientExitCode > 1) {

0 commit comments

Comments
 (0)