Skip to content

Commit

Permalink
GitHub Code Scanning Integration (#32)
Browse files Browse the repository at this point in the history
* Initial GitHub Automatic Code Scanning (ACS) integration, including new parameters to instruct the action to generate SARIF results that are compatible with ACS report rendering

Signed-off-by: Daniel Nurmi <nurmi@anchore.com>

* disable actual SARIF upload in the sarifdemo workflow, instead cat the results to stdout

Signed-off-by: Daniel Nurmi <nurmi@anchore.com>

* update the README and option descriptions for ACS parameters

Signed-off-by: Daniel Nurmi <nurmi@anchore.com>

* update the README

Signed-off-by: Daniel Nurmi <nurmi@anchore.com>

* use the input dockerfile_path parameter as the file location for generated sarif results

Signed-off-by: Daniel Nurmi <nurmi@anchore.com>

* update README and examplee workflow with the stable upload-sarif action locations

Signed-off-by: Daniel Nurmi <nurmi@anchore.com>
  • Loading branch information
nurmi committed Jun 9, 2020
1 parent 642e59b commit 7e648bc
Show file tree
Hide file tree
Showing 6 changed files with 453 additions and 21 deletions.
25 changes: 25 additions & 0 deletions .github/workflows/sarifdemo.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: "Run Anchore Scan Action (ACS SARIF Demo)"

on: [push]

jobs:
demo:
runs-on: ubuntu-latest
steps:
- name: Checkout the code
uses: actions/checkout@v1

- name: Run the local anchore scan action itself with sarif generation enabled
uses: ./
with:
image-reference: "debian:8"
debug: true
acs-report-enable: true
#acs-report-severity-cutoff: "Medium"
- name: Inspect Generated SARIF
run: cat results.sarif
# Uncomment this section to enable report upload
# - name: Upload Anchore Scan SARIF
# uses: github/codeql-action/upload-sarif@v1
# with:
# sarif_file: results.sarif
34 changes: 32 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,9 @@ For an overview of policy format and the checks it can perform, see the [Anchore
| fail-build | Fail the build if policy evaluation returns a fail | | false |
| include-app-packages | Include application packages for vulnerability matches. Requires more vuln data and thus scan will be slower but better results | | false |
| custom-policy-path | A path to a policy json file for specifying a policy other than the default, which fails on >high vulnerabilities with fixes | | null |
| anchore-version | An optional parameter to specify a specific version of anchore to use for the scan. Default is the version locked to the scan-action release | false | v0.6.0 |

| anchore-version | An optional parameter to specify a specific version of anchore to use for the scan. Default is the version locked to the scan-action release | false | v0.7.1 |
| acs-report-enable | 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. | | false |
| acs-report-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

Expand Down Expand Up @@ -131,6 +132,35 @@ jobs:
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.

```yaml
name: Docker Image CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Build the Docker image
run: docker build . --file Dockerfile --tag localbuild/testimage:latest
- uses: anchore/scan-action@master
with:
image-reference: "localbuild/testimage:latest"
dockerfile-path: "./Dockerfile"
fail-build: true
acs-report-enable: true
#acs-report-severity-cutoff: "Medium"
- name: anchore inline scan JSON results
run: for j in `ls ./anchore-reports/*.json`; do echo "---- ${j} ----"; cat ${j}; echo; done
- name: anchore action SARIF report
run: cat results.sarif
- name: upload Anchore scan SARIF report
uses: github/codeql-action/upload-sarif@v1
with:
sarif_file: results.sarif
```
## Contributing
We love contributions, feedback, and bug reports. For issues with the invocation of this action, file [issues](https://github.com/anchore/anchore-scan-action/issues) in this repository.
Expand Down
6 changes: 6 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ inputs:
anchore-version:
description: 'Optionally, specify anchore-engine version (eg 0.5.2) to use instead of the default version'
required: false
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.'
required: false
acs-report-severity-cutoff:
description: '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".'
required: "Medium"
outputs:
billofmaterials:
description: 'The json output report specifying the content of the image'
Expand Down
202 changes: 193 additions & 9 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1085,6 +1085,154 @@ const fs = __webpack_require__(747);
const scanScript = 'inline_scan';
const defaultAnchoreVersion = '0.7.2';

// sarif code
function convert_severity_to_acs_level(input_severity, severity_cutoff_param) {
var ret = "error"
const severityLevels = {
'Unknown': 0,
'Negligible': 1,
'Low': 2,
'Medium': 3,
'High': 4,
'Critical': 5
}

if (severityLevels[input_severity] < severityLevels[severity_cutoff_param]) {
ret = "warning"
}

return(ret)
}

function render_rules(vulnerabilities) {
var ret = {}
if (vulnerabilities) {
ret = vulnerabilities.map(v =>
{
return {
"id": "ANCHOREVULN_"+v.vuln+"_"+v.package_type+"_"+v.package,
"shortDescription": {
"text": v.vuln + " Severity=" + v.severity + " Package=" + v.package
},
"fullDescription": {
"text": v.vuln + " Severity=" + v.severity + " Package=" + v.package
},
"help": {
"text": "Vulnerability "+v.vuln+"\n"+
"Severity: "+v.severity+"\n"+
"Package: "+v.package_name+"\n"+
"Version: "+v.package_version+"\n"+
"Fix Version: "+v.fix+"\n"+
"Type: "+v.package_type+"\n"+
"Location: "+v.package_path+"\n"+
"Data Namespace: "+v.feed + ", "+v.feed_group+"\n"+
"Link: ["+v.vuln+"]("+v.url+")",
"markdown": "**Vulnerability "+v.vuln+"**\n"+
"| Severity | Package | Version | Fix Version | Type | Location | Data Namespace | Link |\n"+
"| --- | --- | --- | --- | --- | --- | --- | --- |\n"+
"|"+v.severity+"|"+v.package_name+"|"+v.package_version+"|"+v.fix+"|"+v.package_type+"|"+v.package_path+"|"+v.feed_group+"|["+v.vuln+"]("+v.url+")|\n"
}

}
}
);
}
return(ret);
}

function render_results(vulnerabilities, severity_cutoff_param, dockerfile_path_param) {
var ret = {}
var dockerfile_location = dockerfile_path_param
if (!dockerfile_location) {
dockerfile_location = "Dockerfile"
}
if (vulnerabilities) {
ret = vulnerabilities.map(v =>
{
return {
"ruleId": "ANCHOREVULN_"+v.vuln+"_"+v.package_type+"_"+v.package,
"ruleIndex": 0,
"level": convert_severity_to_acs_level(v.severity, severity_cutoff_param),
"message": {
"text": "This dockerfile results in a container image that has installed software with a vulnerability: ("+v.package+" type="+v.package_type+")",
"id": "default"
},
"analysisTarget": {
"uri": dockerfile_location,
"index": 0
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": dockerfile_location
},
"region": {
"startLine": 1,
"startColumn": 1,
"endLine": 1,
"endColumn": 1,
"byteOffset": 1,
"byteLength": 1
}
},
"logicalLocations": [
{
"fullyQualifiedName": "dockerfile"
}
]
}
],
"suppressions": [
{
"kind": "external"
}
],
"baselineState": "unchanged"
}
}
)
}
return(ret);
}

function vulnerabilities_to_sarif(input_vulnerabilities, severity_cutoff_param, anchore_version, dockerfile_path_param) {
let rawdata = fs.readFileSync(input_vulnerabilities);
let vulnerabilities_raw = JSON.parse(rawdata);
let vulnerabilities = vulnerabilities_raw.vulnerabilities;

const sarifOutput = {
"$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.4.json",
"version": "2.1.0",
"runs": [
{
"tool": {
"driver": {
"name": "Anchore Container Vulnerability Report",
"fullName": "Anchore Container Vulnerability Report",
"version": anchore_version,
"semanticVersion": anchore_version,
"dottedQuadFileVersion": anchore_version + ".0",
"rules": render_rules(vulnerabilities)
}
},
"logicalLocations": [
{
"name": "dockerfile",
"fullyQualifiedName": "dockerfile",
"kind": "namespace"
}
],
"results": render_results(vulnerabilities, severity_cutoff_param, dockerfile_path_param),
"columnKind": "utf16CodeUnits"
}
]
}

return(sarifOutput)
}


// Find all 'content-*.json' files in the directory. dirname should include the full path
function findContent(searchDir) {
let contentFiles = [];
Expand Down Expand Up @@ -1145,32 +1293,54 @@ async function run() {

const requiredOption = {required: true};
const imageReference = core.getInput('image-reference', requiredOption);
// const imageReference = "alpine:latest"
//const imageReference = "alpine:latest"
const customPolicyPath = core.getInput('custom-policy-path');
const dockerfilePath = core.getInput('dockerfile-path');
var debug = core.getInput('debug');
//var debug = "debug"
var failBuild = core.getInput('fail-build');
var acsReportEnable = core.getInput('acs-report-enable');
var acsSevCutoff = core.getInput('acs-report-severity-cutoff');
var includePackages = core.getInput('include-app-packages');
var version = core.getInput('anchore-version');

const billOfMaterialsPath = "./anchore-reports/content.json";
const runScan = __webpack_require__.ab + "run_scan.sh";
var policyBundlePath = __webpack_require__.ab + "critical_security_policy.json";
var policyBundleName = "critical_security_policy";
var inlineScanImage;
const SEVERITY_LIST = ['Unknown', 'Negligible', 'Low', 'Medium', 'High', 'Critical'];

if (!debug) {
debug = "false";
} else {
debug = "true";
}

if (failBuild.toLowerCase() == "true") {
failBuild = "true";
if (failBuild.toLowerCase() === "true") {
failBuild = true;
} else {
failBuild = "false";
failBuild = false;
}

if (acsReportEnable.toLowerCase() === "true") {
acsReportEnable = true;
} else {
acsReportEnable = false;
}

if (!acsSevCutoff) {
acsSevCutoff = "Medium"
}
else if (
!SEVERITY_LIST.some(
item =>
typeof acsSevCutoff === 'string' &&
item === acsSevCutoff,
)
) {
throw new Error ('Invalid acs-report-severity-cutoff value is set - please ensure you are choosing either Unknown, Negligible, Low, Medium, High, or Critical');
}

if (!version) {
version = `${defaultAnchoreVersion}`;
}
Expand Down Expand Up @@ -1220,6 +1390,8 @@ async function run() {
core.debug('Fail Build: ' + failBuild);
core.debug('Include App Packages: ' + includePackages);
core.debug('Custom Policy Path: ' + customPolicyPath);
core.debug('ACS Enable: ' + acsReportEnable);
core.debug('ACS Severity Cutoff: ' + acsSevCutoff);

core.debug('Policy path for evaluation: ' + policyBundlePath);
core.debug('Policy name for evaluation: ' + policyBundleName);
Expand Down Expand Up @@ -1247,11 +1419,16 @@ async function run() {
throw error;
}

if (acsReportEnable) {
try {sarifGeneration(version, acsSevCutoff, dockerfilePath);}
catch (err) {throw new Error(err)}
}

core.setOutput('billofmaterials', billOfMaterialsPath);
core.setOutput('vulnerabilities', './anchore-reports/vulnerabilities.json');
core.setOutput('policycheck', policyStatus);

if (failBuild === "true" && policyStatus === "fail") {
if (failBuild === true && policyStatus === "fail") {
core.setFailed("Image failed Anchore policy evaluation");
}

Expand All @@ -1260,10 +1437,17 @@ async function run() {
}
}

module.exports = {run, mergeResults, findContent, loadContent};
function sarifGeneration(anchore_version, severity_cutoff_param, dockerfile_path_param){
// sarif generate section
let sarifOutput = vulnerabilities_to_sarif("./anchore-reports/vulnerabilities.json", severity_cutoff_param, anchore_version, dockerfile_path_param);
fs.writeFileSync("./results.sarif", JSON.stringify(sarifOutput, null, 2));
// end sarif generate section
}

module.exports = {run, mergeResults, findContent, loadContent, vulnerabilities_to_sarif, convert_severity_to_acs_level};

if (require.main === require.cache[eval('__filename')]) {
run();
run().catch((err)=>{throw new Error(err)});
}


Expand Down
Loading

0 comments on commit 7e648bc

Please sign in to comment.