make KICS Github Action use KICS Docker Image (#32)

Signed-off-by: João Reigota <joao.reigota@checkmarx.com>
This commit is contained in:
João Reigota
2021-11-24 18:39:26 +00:00
committed by GitHub
parent adcbb642ff
commit d78ffb0a1b
6 changed files with 168 additions and 18735 deletions

View File

@ -8,7 +8,7 @@ inputs:
default: ${{github.token}} default: ${{github.token}}
enable_comments: enable_comments:
required: false required: false
default: false default: "false"
description: "Enable pull request report comments" description: "Enable pull request report comments"
path: path:
description: "paths to a file or directories to scan, accepts a comma separated list" description: "paths to a file or directories to scan, accepts a comma separated list"
@ -79,12 +79,59 @@ inputs:
bom: bom:
description: "include bill of materials (BoM) in results output" description: "include bill of materials (BoM) in results output"
required: false required: false
outputs:
results:
description: "the result of KICS scan"
branding: branding:
icon: "shield" icon: "shield"
color: "green" color: "green"
runs: runs:
using: node12 using: "composite"
main: dist/index.js steps:
- uses: actions/checkout@v2
- run: chmod +x ./entrypoint.sh
shell: bash
- name: Run KICS Scan
id: kics_scan
uses: docker://checkmarx/kics:v1.4.8-alpine
env:
INPUT_PATH: ${{ inputs.path }}
INPUT_FAIL_ON: ${{ inputs.fail_on }}
INPUT_TIMEOUT: ${{ inputs.timeout }}
INPUT_PROFILING: ${{ inputs.profiling }}
INPUT_CONFIG_PATH: ${{ inputs.config }}
INPUT_PLATFORM_TYPE: ${{ inputs.platform_type }}
INPUT_EXCLUDE_PATHS: ${{ inputs.exclude_paths }}
INPUT_EXCLUDE_QUERIES: ${{ inputs.exclude_queries }}
INPUT_INCLUDE_QUERIES: ${{ inputs.include_queries }}
INPUT_EXCLUDE_CATEGORIES: ${{ inputs.exclude_categories }}
INPUT_EXCLUDE_RESULTS: ${{ inputs.exclude_results }}
INPUT_OUTPUT_FORMATS: ${{ inputs.output_formats }}
INPUT_OUTPUT_PATH: ${{ inputs.output_path }}
INPUT_PAYLOAD_PATH: ${{ inputs.payload_path }}
INPUT_QUERIES: ${{ inputs.queries }}
INPUT_VERBOSE: ${{ inputs.verbose }}
INPUT_BOM: ${{ inputs.bom }}
INPUT_IGNORE_ON_EXIT: ${{ inputs.ignore_on_exit }}
INPUT_DISABLE_SECRETS: ${{ inputs.disable_secrets }}
INPUT_DISABLE_FULL_DESCRIPTIONS: ${{ inputs.disable_full_descriptions }}
INPUT_LIBRARIES_PATH: ${{ inputs.libraries_path }}
INPUT_SECRETS_REGEXES_PATH: ${{ inputs.secrets_regexes_path}}
with:
entrypoint: ./entrypoint.sh
- name: Run KICS PR Comentator
uses: actions/setup-node@v2
with:
node-version: 12.x
- name: Install dependencies
shell: bash
run: npm ci
- run: |
sudo chown -R ${USER} ${{ inputs.output_path }}
npm run build --if-present
shell: bash
- run: node dist/index.js
shell: bash
env:
INPUT_TOKEN: ${{ inputs.token }}
INPUT_OUTPUT_PATH: ${{ inputs.output_path }}
INPUT_ENABLE_COMMENTS: ${{ inputs.enable_comments }}
INPUT_OUTPUT_FORMATS: ${{ inputs.output_formats }}
KICS_EXIT_CODE: ${{ steps.kics_scan.outputs.exit_code }}

18472
dist/index.js vendored

File diff suppressed because one or more lines are too long

76
entrypoint.sh Normal file
View File

@ -0,0 +1,76 @@
#!/bin/ash
DATETIME="$(date '+%H:%M')"
####################################
# Check if Scan Path is Present #
####################################
if [ -z "$INPUT_PATH" ]; then
echo "${DATETIME} - ERR input path can't be empty"
exit 1
else
INPUT_PARAM="-p $INPUT_PATH"
fi
###########################
# Set KICS Flags Values #
###########################
[[ ! -z "$INPUT_PAYLOAD_PATH" ]] && PAYLOAD_PATH_PARAM="-d $INPUT_PAYLOAD_PATH"
[[ ! -z "$INPUT_CONFIG_PATH" ]] && CONFIG_PATH_PARAM="--config $INPUT_CONFIG_PATH"
[[ ! -z "$INPUT_EXCLUDE_PATHS" ]] && EXCLUDE_PATHS_PARAM="-e $INPUT_EXCLUDE_PATHS"
[[ ! -z "$INPUT_EXCLUDE_RESULTS" ]] && EXCLUDE_RESULTS_PARAM="-x $INPUT_EXCLUDE_RESULTS"
[[ ! -z "$INPUT_EXCLUDE_QUERIES" ]] && EXCLUDE_QUERIES_PARAM="--exclude-queries $INPUT_EXCLUDE_QUERIES"
[[ ! -z "$INPUT_EXCLUDE_CATEGORIES" ]] && EXCLUDE_CATEGORIES_PARAM="--exclude-categories $INPUT_EXCLUDE_CATEGORIES"
[[ ! -z "$INPUT_PLATFORM_TYPE" ]] && PLATFORM_TYPE_PARAM="--type $INPUT_PLATFORM_TYPE"
[[ ! -z "$INPUT_FAIL_ON" ]] && FAIL_ON_PARAM="--fail-on $INPUT_FAIL_ON"
[[ ! -z "$INPUT_TIMEOUT" ]] && TIMEOUT_PARAM="--timeout $INPUT_TIMEOUT"
[[ ! -z "$INPUT_PROFILING" ]] && PROFILING_PARAM="--profiling $INPUT_PROFILING"
[[ ! -z "$INPUT_BOM" ]] && BOM_PARAM="-m $INPUT_PROFILING"
[[ ! -z "$INPUT_INCLUDE_QUERIES" ]] && INCLUDE_QUERIES_PARAM="-i $INPUT_PROFILING"
[[ ! -z "$INPUT_DISABLE_SECRETS" ]] && DISABLE_SECRETS_PARAM="--disable-secrets"
[[ ! -z "$INPUT_DISABLE_FULL_DESCRIPTIONS" ]] && DISABLE_FULL_DESCRIPTIONS_PARAM="--disable-full-descriptions"
[[ ! -z "$INPUT_LIBRARIES_PATH" ]] && LIBRARIES_PATH_PARAM="-b $INPUT_LIBRARIES_PATH"
[[ ! -z "$INPUT_SECRETS_REGEXES_PATH" ]] && SECRETS_REGEXES_PATH_PARAM="-r $INPUT_SECRETS_REGEXES_PATH"
[[ ! -z "$INPUT_IGNORE_ON_EXIT" ]] && IGNORE_ON_EXIT_PARAM="--ignore-on-exit $INPUT_IGNORE_ON_EXIT"
[[ ! -z "$INPUT_VERBOSE" ]] && VERBOSE_PARAM="-v"
#######################
# Set Queries Path #
#######################
if [ ! -z "$INPUT_QUERIES" ]; then
QUERIES_PARAM="-q $INPUT_QUERIES"
else
QUERIES_PARAM="-q /app/bin/assets/queries"
fi
###############################################
# Add JSON as Report Format if not present #
###############################################
if [ ! -z "$INPUT_OUTPUT_FORMATS" ]; then
if [[ "json" == *"$INPUT_OUTPUT_FORMATS"* ]]; then
OUTPUT_FORMATS_PARAM="--report-formats $INPUT_OUTPUT_FORMATS"
else
OUTPUT_FORMATS_PARAM="--report-formats $INPUT_OUTPUT_FORMATS,json"
fi
else
OUTPUT_FORMATS_PARAM="--report-formats json"
fi
############################
# Check for Output Path #
############################
if [ ! -z "$INPUT_OUTPUT_PATH" ]; then
OUTPUT_PATH_PARAM="-o $INPUT_OUTPUT_PATH"
else
OUTPUT_PATH_PARAM="-o ./"
fi
####################
# Run KICS Scan #
####################
cd $GITHUB_WORKSPACE
echo "${DATETIME} - INF : about to scan directory $INPUT_PATH"
echo "${DATETIME} - INF : kics command kics $INPUT_PARAM $OUTPUT_PATH_PARAM $OUTPUT_FORMATS_PARAM $PLATFORM_TYPE_PARAM $PAYLOAD_PATH_PARAM $CONFIG_PATH_PARAM $EXCLUDE_PATHS_PARAM $EXCLUDE_CATEGORIES_PARAM $EXCLUDE_RESULTS_PARAM $EXCLUDE_QUERIES_PARAM $QUERIES_PARAM $VERBOSE_PARAM $IGNORE_ON_EXIT_PARAM $FAIL_ON_PARAM $TIMEOUT_PARAM $PROFILING_PARAM $BOM_PARAM $INCLUDE_QUERIES_PARAM $DISABLE_SECRETS_PARAM $DISABLE_FULL_DESCRIPTIONS_PARAM $LIBRARIES_PATH_PARAM $SECRETS_REGEXES_PATH_PARAM"
/app/bin/kics scan --no-progress $INPUT_PARAM $OUTPUT_PATH_PARAM $OUTPUT_FORMATS_PARAM $PLATFORM_TYPE_PARAM $PAYLOAD_PATH_PARAM $CONFIG_PATH_PARAM $EXCLUDE_PATHS_PARAM $EXCLUDE_CATEGORIES_PARAM $EXCLUDE_RESULTS_PARAM $EXCLUDE_QUERIES_PARAM $QUERIES_PARAM $VERBOSE_PARAM $IGNORE_ON_EXIT_PARAM $FAIL_ON_PARAM $TIMEOUT_PARAM $PROFILING_PARAM $BOM_PARAM $INCLUDE_QUERIES_PARAM $DISABLE_SECRETS_PARAM $DISABLE_FULL_DESCRIPTIONS_PARAM $LIBRARIES_PATH_PARAM $SECRETS_REGEXES_PATH_PARAM
echo "::set-output name=exit_code::$?"
exit 0

View File

@ -1,93 +0,0 @@
const https = require('https')
const filepath = require('path');
const tc = require('@actions/tool-cache');
const core = require("@actions/core");
const os = require('os');
function getVersion(version) {
let path = ''
if (version == "latest") {
path = '/repos/checkmarx/kics/releases/latest'
} else {
path = '/repos/checkmarx/kics/releases/tags/' + version
}
const options = {
hostname: 'api.github.com',
port: 443,
path: path,
headers: {
'User-Agent': 'node.js'
},
method: 'GET'
}
return new Promise((resolve, reject) => {
const req = https.get(options, (resp) => {
console.log(`${options.method} https://${options.hostname}${options.path} ${resp.statusCode}`)
let rawData = '';
resp.on('data', (d) => {
rawData += d;
});
resp.on('end', () => {
try {
const parsedData = JSON.parse(rawData);
resolve(parsedData);
} catch (e) {
reject(e);
}
});
})
req.on('error', (error) => {
reject(error);
})
})
}
function getReleaseInfo(release) {
const assets = release.assets || [];
const os = process.platform;
const arch = process.arch;
let targetAsset;
switch (os) {
case 'darwin':
targetAsset = assets.filter((asset) => asset.name.indexOf('darwin') !== -1 && asset.name.indexOf(arch) !== -1)[0];
break;
case 'linux':
targetAsset = assets.filter((asset) => asset.name.indexOf('linux') !== -1 && asset.name.indexOf(arch) !== -1)[0];
break;
case 'win32':
targetAsset = assets.filter((asset) => asset.name.indexOf('windows') !== -1 && asset.name.indexOf(arch) !== -1)[0];
break;
default:
targetAsset = { size: 0, browser_download_url: '' };
}
return {
binary: 'kics',
size: targetAsset.size,
browser_download_url: targetAsset.browser_download_url,
version: release.tag_name,
arch: arch
};
}
async function installKICS(kicsVersion) {
let release = {};
if (!kicsVersion || kicsVersion == "latest") {
release = await getVersion("latest");
} else {
release = await getVersion(kicsVersion);
}
const releaseInfo = getReleaseInfo(release)
let kicsPath = tc.find(releaseInfo.binary, releaseInfo.version, releaseInfo.arch);
if (!kicsPath) {
core.info(`Downloading ${releaseInfo.binary} ${releaseInfo.version} ${releaseInfo.arch}`);
const kicsDownloadPath = await tc.downloadTool(releaseInfo.browser_download_url);
const kicsExtractedFolder = await tc.extractTar(kicsDownloadPath, filepath.join(os.homedir(), 'kics', releaseInfo.version));
kicsPath = await tc.cacheDir(kicsExtractedFolder, 'kics', releaseInfo.version, releaseInfo.arch);
}
core.addPath(kicsPath);
}
module.exports = {
installKICS
}

View File

@ -1,80 +1,58 @@
const install = require("./install");
const commenter = require("./commenter"); const commenter = require("./commenter");
const scanner = require("./scanner");
const annotator = require("./annotator"); const annotator = require("./annotator");
const core = require("@actions/core"); const core = require("@actions/core");
const github = require("@actions/github"); const github = require("@actions/github");
const io = require("@actions/io"); const io = require("@actions/io");
const filepath = require('path');
const fs = require("fs"); const fs = require("fs");
const exitStatus = {
results: {
codes: {
HIGH: 50,
MEDIUM: 40,
LOW: 30,
INFO: 20,
},
isResultExitStatus: function (exitCode) {
for (const key in this.codes) {
if (this.codes[key] === exitCode) {
return true;
}
}
return false;
}
}
}
function setWorkflowStatus(statusCode) {
console.log(`KICS scan status code: ${statusCode}`);
if (statusCode === 0) {
return;
}
const ignoreOnExit = core.getInput('ignore_on_exit');
if (ignoreOnExit.toLowerCase() === 'all') {
console.log(`ignore_on_exit=all :: Ignoring exit code ${statusCode}`);
return;
}
if (ignoreOnExit.toLowerCase() === 'results') {
if (exitStatus.results.isResultExitStatus(statusCode)) {
console.log(`ignore_on_exit=results :: Ignoring exit code ${statusCode}`);
return;
}
}
if (ignoreOnExit.toLowerCase() === 'errors') {
if (!exitStatus.results.isResultExitStatus(statusCode)) {
console.log(`ignore_on_exit=errors :: Ignoring exit code ${statusCode}`);
return;
}
}
core.setFailed(`KICS scan failed with exit code ${statusCode}`);
}
function readJSON(filename) { function readJSON(filename) {
const rawdata = fs.readFileSync(filename); const rawdata = fs.readFileSync(filename);
const parsedJSON = JSON.parse(rawdata.toString()); const parsedJSON = JSON.parse(rawdata.toString());
return parsedJSON; return parsedJSON;
} }
function cleanupOutput(resultsJSONFile) { function cleanupOutput(resultsJSONFile, outputFormats) {
const outputFormats = core.getInput('output_formats'); if (!outputFormats.toLowerCase().includes('json') || outputFormats === '') {
if (!outputFormats.toLowerCase().includes('json') || core.getInput('output_path') === '') {
io.rmRF(resultsJSONFile); io.rmRF(resultsJSONFile);
} }
} }
function processOutputPath(output) {
if (output === '') {
return {
path: "./",
resultsJSONFile: "./results.json"
}
}
return {
path: output,
resultsJSONFile: filepath.join(output, "/results.json")
}
}
function setWorkflowStatus(statusCode) {
console.log(`KICS scan status code: ${statusCode}`);
if (statusCode === "0") {
return;
}
core.setFailed(`KICS scan failed with exit code ${statusCode}`);
}
async function main() { async function main() {
console.log("Running KICS action..."); console.log("Running KICS action...");
// Get ENV variables
const githubToken = process.env.INPUT_TOKEN;
const enableComments = process.env.INPUT_ENABLE_COMMENTS;
const outputPath = processOutputPath(process.env.INPUT_OUTPUT_PATH);
const outputFormats = process.env.INPUT_OUTPUT_FORMATS;
const exitCode = process.env.KICS_EXIT_CODE
try { try {
const githubToken = core.getInput("token");
const octokit = github.getOctokit(githubToken); const octokit = github.getOctokit(githubToken);
let context = {}; let context = {};
let repo = ''; let repo = '';
@ -90,17 +68,15 @@ async function main() {
} }
} }
await install.installKICS(); const parsedResults = readJSON(outputPath.resultsJSONFile);
const scanResults = await scanner.scanWithKICS(); if (enableComments.toLocaleLowerCase() === "true") {
const parsedResults = readJSON(scanResults.resultsJSONFile);
if (core.getInput('enable_comments').toLocaleLowerCase() === "true") {
await commenter.postPRComment(parsedResults, repo, prNumber, octokit); await commenter.postPRComment(parsedResults, repo, prNumber, octokit);
} }
annotator.annotateChangesWithResults(parsedResults); annotator.annotateChangesWithResults(parsedResults);
cleanupOutput(scanResults.resultsJSONFile); setWorkflowStatus(exitCode);
setWorkflowStatus(scanResults.statusCode); cleanupOutput(outputPath.resultsJSONFile, outputFormats);
} catch (e) { } catch (e) {
console.error(e); console.error(e);
core.setFailed(e.message); core.setFailed(e.message);

View File

@ -1,101 +0,0 @@
const exec = require('@actions/exec');
const core = require("@actions/core");
const filepath = require('path');
const kicsBinary = 'kics';
const kicsInput = {
path: { value_type: "list", flag: '--path', value: core.getInput('path') },
ignore_on_exit: { value_type: "list", flag: '--ignore-on-exit', value: core.getInput('ignore_on_exit') },
fail_on: { value_type: "list", flag: '--fail-on', value: core.getInput('fail_on') },
timeout: { value_type: "int", flag: '--timeout', value: core.getInput('timeout') },
profiling: { value_type: "list", flag: '--profiling', value: core.getInput('profiling') },
config_path: { value_type: "string", flag: '--config', value: core.getInput('config_path') },
payload_path: { value_type: "string", flag: '--payload-path', value: core.getInput('payload_path') },
exclude_paths: { value_type: "list", flag: '--exclude-paths', value: core.getInput('exclude_paths') },
exclude_queries: { value_type: "list", flag: '--exclude-queries', value: core.getInput('exclude_queries') },
exclude_categories: { value_type: "list", flag: '--exclude-categories', value: core.getInput('exclude_categories') },
exclude_results: { value_type: "list", flag: '--exclude-results', value: core.getInput('exclude_results') },
output_formats: { value_type: "list", flag: '--report-formats', value: core.getInput('output_formats') },
output_path: { value_type: "string", flag: '--output-path', value: core.getInput('output_path') },
queries: { value_type: "string", flag: '--queries-path', value: core.getInput('queries') },
verbose: { value_type: "bool", flag: '--verbose', value: core.getInput('verbose') },
secrets_regexes_path: { value_type: "string", flag: '--secrets-regexes-path', value: core.getInput('secrets_regexes_path') },
libraries_path: { value_type: "string", flag: '--libraries-path', value: core.getInput('libraries-path') },
disable_secrets: { value_type: "bool", flag: '--disable-secrets', value: core.getInput('disable_secrets') },
disable_full_descriptions: { value_type: "bool", flag: '--disable-full-descriptions', value: core.getInput('disable_full_descriptions') },
types: { value_type: "list", flag: '--types', value: core.getInput('types') },
bom: { value_type: "bool", flag: '--bom', value: core.getInput('bom') },
};
function addJSONReportFormat(cmdArgs) {
const outputFormats = core.getInput('output_formats');
if (outputFormats.toLowerCase().indexOf('json') == -1) {
cmdArgs.push('--report-formats');
cmdArgs.push('json');
}
}
function addKICSCmdArgs(cmdArgs) {
for (let input in kicsInput) {
if (kicsInput[input].value_type === 'string') {
if (kicsInput[input].value) {
cmdArgs.push(kicsInput[input].flag);
cmdArgs.push(kicsInput[input].value);
}
} else if (kicsInput[input].value_type === 'list') {
if (kicsInput[input].value) {
if (kicsInput[input].value.indexOf(',') > -1) {
kicsInput[input].value.split(',').forEach(value => {
cmdArgs.push(kicsInput[input].flag);
cmdArgs.push(value);
});
} else {
cmdArgs.push(kicsInput[input].flag);
cmdArgs.push(kicsInput[input].value);
}
}
} else if (kicsInput[input].value_type === 'bool') {
if (kicsInput[input].value) {
cmdArgs.push(kicsInput[input].flag);
}
} else if (kicsInput[input].value_type === 'int') {
if (kicsInput[input].value) {
cmdArgs.push(kicsInput[input].flag);
cmdArgs.push(kicsInput[input].value);
}
}
}
}
async function scanWithKICS() {
let resultsJSONFile;
if (!kicsInput.path.value) {
core.error('Path to scan is not set');
core.setFailed('Path to scan is not set');
}
let cmdArgs = [];
addKICSCmdArgs(cmdArgs);
// making sure results.json is always created
if (!cmdArgs.find(arg => arg == '--output-path')) {
cmdArgs.push('--output-path');
cmdArgs.push('./');
resultsJSONFile = './results.json';
} else {
let resultsDir = core.getInput('output_path');
resultsJSONFile = filepath.join(resultsDir, '/results.json');
}
addJSONReportFormat(cmdArgs);
exitCode = await exec.exec(`${kicsBinary} scan --no-progress ${cmdArgs.join(" ")}`, [], { ignoreReturnCode: true });
return {
statusCode: exitCode,
resultsJSONFile: resultsJSONFile
};
}
module.exports = {
scanWithKICS
};