Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prepare for v3: fix 0.16.0 sarif output #114 #115

Merged
merged 11 commits into from Aug 26, 2021
7 changes: 7 additions & 0 deletions .editorconfig
@@ -0,0 +1,7 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true
indent_size = 2
max_line_length = 80
ij_javascript_enforce_trailing_comma = keep
27 changes: 10 additions & 17 deletions README.md
Expand Up @@ -96,27 +96,22 @@ Optionally, change the `fail-build` field to `false` to avoid failing the build

### Action Inputs

The only required key is `image`; all the other keys are optional. These are all the available keys to configure this action, along with its defaults:
The only required key is `image` or `path`; all the other keys are optional. These are all the available keys to configure this action, along with its defaults:

| Input Name | Description | Default Value |
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
| `image` | The image to scan | N/A |
| `image` | The image to scan, this is mutually exclusive to `path` | N/A |
| `path` | The file path to scan, this is mutually exclusive to `image` | N/A |
kzantow marked this conversation as resolved.
Show resolved Hide resolved
| `debug` | Verbose logging output | `false` |
| `fail-build` | Fail the build if a vulnerability is found with a higher severity. That severity defaults to `"medium"` and can be set with `severity-cutoff`. | `false` |
| `grype-version` | An optional parameter to specify a specific version of `grype` to use for the scan. Default is the version locked to the scan-action release | `0.16.0` |
| `acs-report-enable` | Optionally, enable the feature that causes a result.sarif report to be generated after successful action execution. This report is compatible with GitHub Automated Code Scanning (ACS), as the artifact to upload for display as a Code Scanning Alert report. | `false` |
| `fail-build` | Fail the build if a vulnerability is found with a higher severity. That severity defaults to `"medium"` and can be set with `severity-cutoff`. | `true` |
| `acs-report-enable` | Generate a SARIF report and set the `sarif` output parameter after successful action execution. This report is compatible with GitHub Automated Code Scanning (ACS), as the artifact to upload for display as a Code Scanning Alert report. | `true` |
| `severity-cutoff` | With ACS reporting enabled, optionally specify the minimum vulnerability severity to trigger an "error" level ACS result. Valid choices are "negligible", "low", "medium", "high" and "critical". Any vulnerability with a severity less than this value will lead to a "warning" result. Default is "medium". | `"medium"` |

### Action Outputs

| Output Name | Description | Type |
| --------------- | ------------------------------------------------------------------- | ------ |
| vulnerabilities | Path to a JSON file with the list of vulnerabilities found in image | string |
| sarif | Path to a SARIF report file | string |

As a result of the action, you'll get a JSON file in the `anchore-reports` directory in the workspace:

- `vulnerabilities.json` - Vulnerabilities found in the image
| Output Name | Description | Type |
| ----------- | ----------------------------- | ------ |
| sarif | Path to the SARIF report file | string |

### Example Workflows

Expand All @@ -136,8 +131,6 @@ jobs:
with:
image: "localbuild/testimage:latest"
fail-build: true
- name: grype scan JSON results
run: for j in `ls ./anchore-reports/*.json`; do echo "---- ${j} ----"; cat ${j}; echo; done
```

Same example as above, but with Automated Code Scanning (ACS) feature enabled - with this example, the action will generate a SARIF report, which can be uploaded and then displayed as a Code Scanning Report in the GitHub UI.
Expand Down Expand Up @@ -184,8 +177,8 @@ For documentation on Grype itself, including other output capabilities, see the

Connect with the community directly on [slack](https://anchore.com/slack). These channels from Anchore's toolbox project are ideal for engaging development of help-related discussions:

- toolbox-dev
- toolbox-help
- grype-dev
- grype-help
kzantow marked this conversation as resolved.
Show resolved Hide resolved

[test]: https://github.com/anchore/scan-action
[test-img]: https://github.com/anchore/scan-action/workflows/Tests/badge.svg
27 changes: 11 additions & 16 deletions action.yml
@@ -1,5 +1,5 @@
name: 'Anchore Container Scan'
description: 'Scan docker containers with Grype for vulnerabilities'
name: "Anchore Container Scan"
description: "Scan docker containers with Grype for vulnerabilities"
branding:
color: blue
icon: check-circle
Expand All @@ -11,29 +11,24 @@ inputs:
description: 'The path to scan. This option is mutually exclusive with "image".'
required: false
debug:
description: 'Set this to any value to enable verbose debug output'
description: "Set this to any value to enable verbose debug output"
required: false
default: 'false'
default: "false"
fail-build:
description: 'Set to false to avoid failing based on severity-cutoff. Default is to fail when severity-cutoff is reached (or surpassed)'
required: false
default: 'true'
grype-version:
description: 'Optionally, specify the Grype version (e.g. 0.1.0) to use instead of the default version'
description: "Set to false to avoid failing based on severity-cutoff. Default is to fail when severity-cutoff is reached (or surpassed)"
required: false
default: "true"
acs-report-enable:
description: 'Optionally, enable feature that causes a result.sarif report to be generated after successful action execution. This report is compatible with GitHub Automated Code Scanning (ACS), as the artifact to upload for display as a Code Scanning Alert report.'
description: "Generate a SARIF report and set the `sarif` output parameter after successful action execution. This report is compatible with GitHub Automated Code Scanning (ACS), as the artifact to upload for display as a Code Scanning Alert report."
required: false
default: 'false'
default: "true"
severity-cutoff:
description: 'Optionally specify the minimum vulnerability severity to trigger an "error" level ACS result. Valid choices are "negligible", "low", "medium", "high" and "critical". Any vulnerability with a severity less than this value will lead to a "warning" result. Default is "medium".'
required: false
default: "medium"
outputs:
vulnerabilities:
description: 'The found vulnerabilities for the image'
sarif:
description: 'Path to a SARIF report file for the image'
description: "Path to a SARIF report file for the image"
runs:
using: 'node12'
main: 'dist/index.js'
using: "node12"
main: "dist/index.js"
80 changes: 45 additions & 35 deletions dist/index.js
Expand Up @@ -90,14 +90,27 @@ function dottedQuadFileVersion(version) {
return version;
}

function get_fix_versions(v) {
if (
v.vulnerability.fix &&
kzantow marked this conversation as resolved.
Show resolved Hide resolved
v.vulnerability.fix.state === "fixed" &&
v.vulnerability.fix.versions &&
v.vulnerability.fix.versions.length > 0
) {
return v.vulnerability.fix.versions.join(",");
}
return "";
}

function make_subtitle(v) {
let subtitle = `${v.vulnerability.description}`;
if (subtitle != "undefined") {
return subtitle;
}

if (v.vulnerability.fixedInVersion) {
return `Version ${v.artifact.version} is affected with an available fix in version ${v.vulnerability.fixedInVersion}`;
const fixVersions = get_fix_versions(v);
if (fixVersions) {
return `Version ${v.artifact.version} is affected with an available fix in versions ${fixVersions}`;
}

return `Version ${v.artifact.version} is affected with no fixes reported yet.`;
Expand All @@ -120,8 +133,13 @@ function grype_render_rules(vulnerabilities, source) {
ruleIDs.push(ruleID);
// Entirely possible to not have any links whatsoever
let link = v.vulnerability.id;
if ("links" in v.vulnerability) {
link = `[${v.vulnerability.id}](${v.vulnerability.links[0]})`;
if ("dataSource" in v.vulnerability) {
link = `[${v.vulnerability.id}](${v.vulnerability.dataSource})`;
} else if (
"urls" in v.vulnerability &&
v.vulnerability.urls.length > 0
) {
link = `[${v.vulnerability.id}](${v.vulnerability.urls[0]})`;
}

result.push({
Expand Down Expand Up @@ -149,7 +167,7 @@ function grype_render_rules(vulnerabilities, source) {
v.artifact.version +
"\n" +
"Fix Version: " +
"unknown" +
(get_fix_versions(v) || "none") +
"\n" +
"Type: " +
v.artifact.type +
Expand All @@ -175,7 +193,7 @@ function grype_render_rules(vulnerabilities, source) {
"|" +
v.artifact.version +
"|" +
"unknown" +
(get_fix_versions(v) || "none") +
"|" +
v.artifact.type +
"|" +
Expand Down Expand Up @@ -258,14 +276,12 @@ function grype_render_results(vulnerabilities, severity_cutoff_param, source) {
}

function vulnerabilities_to_sarif(
input_vulnerabilities,
grypeVulnerabilities,
severity_cutoff_param,
version,
source
) {
let rawdata = fs.readFileSync(input_vulnerabilities);
let parsed = JSON.parse(rawdata);
let vulnerabilities = parsed.matches;
let vulnerabilities = grypeVulnerabilities.matches;

const sarifOutput = {
$schema:
Expand Down Expand Up @@ -408,14 +424,12 @@ async function run() {
const failBuild = core.getInput("fail-build");
const acsReportEnable = core.getInput("acs-report-enable");
const severityCutoff = core.getInput("severity-cutoff");
const version = core.getInput("grype-version");
const out = await runScan({
source,
debug,
failBuild,
acsReportEnable,
severityCutoff,
version,
});
Object.keys(out).map((key) => {
core.setOutput(key, out[key]);
Expand All @@ -429,16 +443,14 @@ async function runScan({
source,
debug = "false",
failBuild = "true",
acsReportEnable = "false",
acsReportEnable = "true",
severityCutoff = "medium",
version = "",
}) {
const out = {};

const billOfMaterialsPath = "./anchore-reports/content.json";
const SEVERITY_LIST = ["negligible", "low", "medium", "high", "critical"];
let cmdArgs = [];
console.log(billOfMaterialsPath);

if (debug.toLowerCase() === "true") {
debug = "true";
cmdArgs = [`-vv`, `-o`, `json`];
Expand Down Expand Up @@ -471,12 +483,8 @@ async function runScan({
);
}

if (!version) {
version = `${grypeVersion}`;
}

core.debug(`Installing grype version ${version}`);
await installGrype(version);
core.debug(`Installing grype version ${grypeVersion}`);
await installGrype(grypeVersion);

core.debug("Image: " + source);
core.debug("Debug Output: " + debug);
Expand Down Expand Up @@ -504,21 +512,20 @@ async function runScan({
cmdOpts.ignoreReturnCode = true;

core.info("\nAnalyzing: " + source);
core.debug(`Running cmd: ${cmd} ` + cmdArgs.join(" "));
let exitCode = await exec(cmd, cmdArgs, cmdOpts);
let grypeVulnerabilities = JSON.parse(cmdOutput);

// handle output
fs.writeFileSync(
"./vulnerabilities.json",
JSON.stringify(grypeVulnerabilities)
);
const exitCode = await core.group("Grype Output", () => {
core.info(`Executing: ${cmd} ` + cmdArgs.join(" "));
return exec(cmd, cmdArgs, cmdOpts);
});

let grypeVulnerabilities = JSON.parse(cmdOutput);

if (acsReportEnable) {
try {
const serifOut = sarifGrypeGeneration(
grypeVulnerabilities,
severityCutoff.toLowerCase(),
version,
grypeVersion,
source
);
Object.assign(out, serifOut);
Expand All @@ -533,8 +540,6 @@ async function runScan({
);
}

out.vulnerabilities = "./vulnerabilities.json";

// If there is a non-zero exit status code there are a couple of potential reporting paths
if (failBuild === false && exitCode > 0) {
// There was a non-zero exit status but it wasn't because of failing severity, this must be
Expand All @@ -554,11 +559,16 @@ async function runScan({
return out;
}

function sarifGrypeGeneration(severity_cutoff_param, version, source) {
function sarifGrypeGeneration(
grypeVulnerabilities,
severity_cutoff_param,
version,
source
) {
// sarif generate section
const SARIF_FILE = "./results.sarif";
let sarifOutput = vulnerabilities_to_sarif(
"./vulnerabilities.json",
grypeVulnerabilities,
severity_cutoff_param,
version,
source
Expand Down