diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b1bae7b2..de5d3a47 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,14 +5,47 @@ on: [push, pull_request] jobs: test: runs-on: ubuntu-latest + services: + registry: + image: registry:2 + ports: + - 5000:5000 steps: - - uses: actions/checkout@v2 - - run: npm ci - - run: npm audit --production - - run: npm test -- --testPathIgnorePatterns action.test.js + - uses: actions/checkout@v2 + - run: echo $(uname -a) + - name: Check for npm (so make test works) + run: | + if ! [ -x "$(command -v npm)" ]; then + sudo apt update + sudo apt -y upgrade + sudo apt update + sudo apt -y install curl dirmngr apt-transport-https lsb-release ca-certificates + curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash - + sudo apt -y install nodejs + sudo apt -y install gcc g++ make + fi + - name: Build images + run: | + for distro in alpine centos debian; do + docker build -t localhost:5000/match-coverage/$distro ./tests/fixtures/image-$distro-match-coverage + docker push localhost:5000/match-coverage/$distro:latest + done + - name: Inspect + run: | + docker images -a + for distro in alpine centos debian; do + docker buildx imagetools inspect localhost:5000/match-coverage/$distro:latest + done + - run: npm ci + - run: npm audit --production + - run: npm test functional: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - run: make check + - uses: actions/checkout@v2 + - run: make check + - uses: actions/upload-artifact@v2 + with: + name: functional-test-output + path: tests/functional/output/* diff --git a/.gitignore b/.gitignore index a55947db..62970bee 100644 --- a/.gitignore +++ b/.gitignore @@ -108,8 +108,13 @@ typings/ # IDE files/dirs .vscode +.idea # Exclude python test artifacts /act /venv/* /tests/functional/__pycache__/* + +# Action temporary files +results.sarif +vulnerabilities.json diff --git a/Makefile b/Makefile index 191714bd..210e58db 100644 --- a/Makefile +++ b/Makefile @@ -47,6 +47,22 @@ bootstrap: ## Download and install all go dependencies (+ prep tooling in the ./ # prep temp dirs mkdir -p tests/functional/output +.PHONY: run-docker-registry +run-docker-registry: bootstrap + # start a local registry + docker run -d -p 5000:5000 --name registry registry:2 | echo + +.PHONY: test +test: run-docker-registry bootstrap + npm run build + ./act -v -P ubuntu-latest=ghcr.io/catthehacker/ubuntu:js-latest -j test + +.PHONY: wipe-docker +wipe-docker: + docker kill $(shell docker ps -a -q) | echo + docker image prune -af + docker container prune -f + docker volume prune -f %: ## do a local build scripts/local.sh "$*" diff --git a/README.md b/README.md index f58d6bb1..a014cb9b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ [![Test Status][test-img]][test] # GitHub Action for vulnerability Scanning + :zap: _Find threats in files or containers at lightning speed_ :zap: This is a GitHub Action for invoking the [grype](https://github.com/anchore/grype) scanner and returning the vulnerabilities found, @@ -10,10 +11,10 @@ Use this in your workflows to quickly verify files or containers' content after The action invokes the `grype` command-line tool, with these benefits: -* Runs locally, without sending data outbound - no credentials required! -* Speedy scan operations -* Scans both paths and container images -* Easy failure evaluation depending on vulnerability severity +- Runs locally, without sending data outbound - no credentials required! +- Speedy scan operations +- Scans both paths and container images +- Easy failure evaluation depending on vulnerability severity The example workflows have lots of usage examples for scanning both containers and directories. @@ -21,18 +22,18 @@ By default, a scan will produce very detailed output on system packages like an Supported Linux Distributions: -* Alpine -* BusyBox -* CentOS and RedHat -* Debian and Debian-based distros like Ubuntu +- Alpine +- BusyBox +- CentOS and RedHat +- Debian and Debian-based distros like Ubuntu Supported packages and libraries: -* Ruby Bundles -* Python Wheel, Egg, `requirements.txt` -* JavaScript NPM/Yarn -* Java JAR/EAR/WAR, Jenkins plugins JPI/HPI -* Go modules +- Ruby Bundles +- Python Wheel, Egg, `requirements.txt` +- JavaScript NPM/Yarn +- Java JAR/EAR/WAR, Jenkins plugins JPI/HPI +- Go modules ## Container scanning @@ -60,63 +61,62 @@ The simplest workflow for scanning a `localbuild/testimage` container: To scan a directory, add the following step: ```yaml - - name: Scan current project - uses: anchore/scan-action@v2 - with: - path: "." +- name: Scan current project + uses: anchore/scan-action@v2 + with: + path: "." ``` The `path` key allows any valid path for the current project. The root of the path (`"."` in this example) is the repository root. ## Failing a build on vulnerability severity + By default, if any vulnerability at `medium` or higher is seen, the build fails. To have the build step fail in cases where there are vulnerabilities with a severity level different than the default, set the `severity-cutoff` field to one of `low`, `high`, or `critical`: With a different severity level: ```yaml - - name: Scan image - uses: anchore/scan-action@v2 - with: - image: "localbuild/testimage:latest" - fail-build: true - severity-cutoff: critical +- name: Scan image + uses: anchore/scan-action@v2 + with: + image: "localbuild/testimage:latest" + fail-build: true + severity-cutoff: critical ``` Optionally, change the `fail-build` field to `false` to avoid failing the build regardless of severity: ```yaml - - name: Scan image - uses: anchore/scan-action@v2 - with: - image: "localbuild/testimage:latest" - fail-build: false +- name: Scan image + uses: anchore/scan-action@v2 + with: + image: "localbuild/testimage:latest" + fail-build: false ``` - ### 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: -| Input Name | Description | Default Value | -|-----------------|-------------|---------------| -| `image` | The image to scan | N/A | -| `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.1.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` | -| `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"` | +| Input Name | Description | Default Value | +| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | +| `image` | The image to scan | N/A | +| `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` | +| `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 | -|-----------------|-------------|----------| +| 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 | +| 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 - +- `vulnerabilities.json` - Vulnerabilities found in the image ### Example Workflows @@ -129,15 +129,15 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Build the container image - run: docker build . --file Dockerfile --tag localbuild/testimage:latest - - uses: anchore/scan-action@v2 - 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 + - uses: actions/checkout@v2 + - name: Build the container image + run: docker build . --file Dockerfile --tag localbuild/testimage:latest + - uses: anchore/scan-action@v2 + 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. @@ -151,25 +151,25 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Build the Container image - run: docker build . --file Dockerfile --tag localbuild/testimage:latest - - uses: anchore/scan-action@v2 - id: scan - with: - image: "localbuild/testimage:latest" - acs-report-enable: true - - name: upload Anchore scan SARIF report - uses: github/codeql-action/upload-sarif@v1 - with: - sarif_file: ${{ steps.scan.outputs.sarif }} + - uses: actions/checkout@v2 + - name: Build the Container image + run: docker build . --file Dockerfile --tag localbuild/testimage:latest + - uses: anchore/scan-action@v2 + id: scan + with: + image: "localbuild/testimage:latest" + acs-report-enable: true + - name: upload Anchore scan SARIF report + uses: github/codeql-action/upload-sarif@v1 + with: + sarif_file: ${{ steps.scan.outputs.sarif }} ``` Optionally, you can add a step to inspect the SARIF report produced: ```yaml - - name: Inspect action SARIF report - run: cat ${{ steps.scan.outputs.sarif }} +- name: Inspect action SARIF report + run: cat ${{ steps.scan.outputs.sarif }} ``` ## Contributing @@ -178,15 +178,14 @@ We love contributions, feedback, and bug reports. For issues with the invocation For contributing, see [Contributing](CONTRIBUTING.rst). - ## More Information + For documentation on Grype itself, including other output capabilities, see the [grype project](https://github.com/anchore/grype) 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 - +- toolbox-dev +- toolbox-help [test]: https://github.com/anchore/scan-action [test-img]: https://github.com/anchore/scan-action/workflows/Tests/badge.svg diff --git a/dist/index.js b/dist/index.js index 7770b3ed..9db0013a 100644 --- a/dist/index.js +++ b/dist/index.js @@ -11,7 +11,7 @@ const { exec } = __webpack_require__(514); const fs = __webpack_require__(747); const grypeBinary = "grype"; -const grypeVersion = "0.7.0"; +const grypeVersion = "0.16.0"; // sarif code function convert_severity_to_acs_level(input_severity, severity_cutoff_param) { @@ -404,125 +404,154 @@ async function run() { // Grype accepts several input options, initially this action is supporting both `image` and `path`, so // a check must happen to ensure one is selected at least, and then return it const source = sourceInput(); + const debug = core.getInput("debug"); + 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]); + }); + } catch (error) { + core.setFailed(error.message); + } +} - var debug = core.getInput("debug"); - var failBuild = core.getInput("fail-build"); - var acsReportEnable = core.getInput("acs-report-enable"); - var severityCutoff = core.getInput("severity-cutoff"); - var version = core.getInput("grype-version"); - 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`]; - } else { - debug = "false"; - cmdArgs = [`-o`, `json`]; - } - - if (failBuild.toLowerCase() === "true") { - failBuild = true; - } else { - failBuild = false; - } +async function runScan({ + source, + debug = "false", + failBuild = "true", + acsReportEnable = "false", + 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`]; + } else { + debug = "false"; + cmdArgs = [`-o`, `json`]; + } - if (acsReportEnable.toLowerCase() === "true") { - acsReportEnable = true; - } else { - acsReportEnable = false; - } + if (failBuild.toLowerCase() === "true") { + failBuild = true; + } else { + failBuild = false; + } - if ( - !SEVERITY_LIST.some( - (item) => - typeof severityCutoff.toLowerCase() === "string" && - item === severityCutoff.toLowerCase() - ) - ) { - throw new Error( - `Invalid severity-cutoff value is set to ${severityCutoff} - please ensure you are choosing either negligible, low, medium, high, or critical` - ); - } + if (acsReportEnable.toLowerCase() === "true") { + acsReportEnable = true; + } else { + acsReportEnable = false; + } - if (!version) { - version = `${grypeVersion}`; - } + if ( + !SEVERITY_LIST.some( + (item) => + typeof severityCutoff.toLowerCase() === "string" && + item === severityCutoff.toLowerCase() + ) + ) { + throw new Error( + `Invalid severity-cutoff value is set to ${severityCutoff} - please ensure you are choosing either negligible, low, medium, high, or critical` + ); + } - core.debug(`Installing grype version ${version}`); - await installGrype(version); + if (!version) { + version = `${grypeVersion}`; + } - core.debug("Image: " + source); - core.debug("Debug Output: " + debug); - core.debug("Fail Build: " + failBuild); - core.debug("Severity Cutoff: " + severityCutoff); - core.debug("ACS Enable: " + acsReportEnable); + core.debug(`Installing grype version ${version}`); + await installGrype(version); - core.debug("Creating options for GRYPE analyzer"); + core.debug("Image: " + source); + core.debug("Debug Output: " + debug); + core.debug("Fail Build: " + failBuild); + core.debug("Severity Cutoff: " + severityCutoff); + core.debug("ACS Enable: " + acsReportEnable); - // Run the grype analyzer - let cmdOutput = ""; - let cmd = `${grypeBinary}`; - if (severityCutoff != "") { - cmdArgs.push("--fail-on"); - cmdArgs.push(severityCutoff.toLowerCase()); - } - cmdArgs.push(source); - const cmdOpts = {}; - cmdOpts.listeners = { - stdout: (data = Buffer) => { - cmdOutput += data.toString(); - }, - }; + core.debug("Creating options for GRYPE analyzer"); - cmdOpts.ignoreReturnCode = true; + // Run the grype analyzer + let cmdOutput = ""; + let cmd = `${grypeBinary}`; + if (severityCutoff != "") { + cmdArgs.push("--fail-on"); + cmdArgs.push(severityCutoff.toLowerCase()); + } + cmdArgs.push(source); + const cmdOpts = {}; + cmdOpts.listeners = { + stdout: (data = Buffer) => { + cmdOutput += data.toString(); + }, + }; - core.info("\nAnalyzing: " + source); - core.debug(`Running cmd: ${cmd} ` + cmdArgs.join(" ")); - let exitCode = await exec(cmd, cmdArgs, cmdOpts); - let grypeVulnerabilities = JSON.parse(cmdOutput); + cmdOpts.ignoreReturnCode = true; - // handle output - fs.writeFileSync( - "./vulnerabilities.json", - JSON.stringify(grypeVulnerabilities) - ); + core.info("\nAnalyzing: " + source); + core.debug(`Running cmd: ${cmd} ` + cmdArgs.join(" ")); + let exitCode = await exec(cmd, cmdArgs, cmdOpts); + let grypeVulnerabilities = JSON.parse(cmdOutput); - if (acsReportEnable) { - try { - sarifGrypeGeneration(severityCutoff.toLowerCase(), version, source); - } catch (err) { - throw new Error(err); - } - } + // handle output + fs.writeFileSync( + "./vulnerabilities.json", + JSON.stringify(grypeVulnerabilities) + ); - if (failBuild === true && exitCode > 0) { - core.setFailed( - `Failed minimum severity level. Found vulnerabilities with level ${severityCutoff} or higher` + if (acsReportEnable) { + try { + const serifOut = sarifGrypeGeneration( + severityCutoff.toLowerCase(), + version, + source ); + Object.assign(out, serifOut); + } catch (err) { + throw new Error(err); } + } - core.setOutput("vulnerabilities", "./vulnerabilities.json"); + if (failBuild === true && exitCode > 0) { + core.setFailed( + `Failed minimum severity level. Found vulnerabilities with level ${severityCutoff} or higher` + ); + } - // 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 - // a grype problem - if (!severityCutoff) { - core.warning("grype had a non-zero exit status when running"); - } else { - // There is a non-zero exit status code with severity cut off, although there is still a chance this is grype - // that is broken, it will most probably be a failed severity. Using warning here will make it bubble up in the - // Actions UI - core.warning( - `Failed minimum severity level. Found vulnerabilities with level ${severityCutoff} or higher` - ); - } + 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 + // a grype problem + if (!severityCutoff) { + core.warning("grype had a non-zero exit status when running"); + } else { + // There is a non-zero exit status code with severity cut off, although there is still a chance this is grype + // that is broken, it will most probably be a failed severity. Using warning here will make it bubble up in the + // Actions UI + core.warning( + `Failed minimum severity level. Found vulnerabilities with level ${severityCutoff} or higher` + ); } - } catch (error) { - core.setFailed(error.message); } + + return out; } function sarifGrypeGeneration(severity_cutoff_param, version, source) { @@ -535,12 +564,16 @@ function sarifGrypeGeneration(severity_cutoff_param, version, source) { source ); fs.writeFileSync(SARIF_FILE, JSON.stringify(sarifOutput, null, 2)); - core.setOutput("sarif", SARIF_FILE); + return { + sarif: SARIF_FILE, + }; // end sarif generate section } module.exports = { run, + runScan, + installGrype, mergeResults, findContent, loadContent, diff --git a/index.js b/index.js index a065adab..7e0846f7 100644 --- a/index.js +++ b/index.js @@ -4,7 +4,7 @@ const { exec } = require("@actions/exec"); const fs = require("fs"); const grypeBinary = "grype"; -const grypeVersion = "0.7.0"; +const grypeVersion = "0.16.0"; // sarif code function convert_severity_to_acs_level(input_severity, severity_cutoff_param) { @@ -397,125 +397,154 @@ async function run() { // Grype accepts several input options, initially this action is supporting both `image` and `path`, so // a check must happen to ensure one is selected at least, and then return it const source = sourceInput(); + const debug = core.getInput("debug"); + 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]); + }); + } catch (error) { + core.setFailed(error.message); + } +} - var debug = core.getInput("debug"); - var failBuild = core.getInput("fail-build"); - var acsReportEnable = core.getInput("acs-report-enable"); - var severityCutoff = core.getInput("severity-cutoff"); - var version = core.getInput("grype-version"); - 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`]; - } else { - debug = "false"; - cmdArgs = [`-o`, `json`]; - } +async function runScan({ + source, + debug = "false", + failBuild = "true", + acsReportEnable = "false", + 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`]; + } else { + debug = "false"; + cmdArgs = [`-o`, `json`]; + } - if (failBuild.toLowerCase() === "true") { - failBuild = true; - } else { - failBuild = false; - } + if (failBuild.toLowerCase() === "true") { + failBuild = true; + } else { + failBuild = false; + } - if (acsReportEnable.toLowerCase() === "true") { - acsReportEnable = true; - } else { - acsReportEnable = false; - } + if (acsReportEnable.toLowerCase() === "true") { + acsReportEnable = true; + } else { + acsReportEnable = false; + } - if ( - !SEVERITY_LIST.some( - (item) => - typeof severityCutoff.toLowerCase() === "string" && - item === severityCutoff.toLowerCase() - ) - ) { - throw new Error( - `Invalid severity-cutoff value is set to ${severityCutoff} - please ensure you are choosing either negligible, low, medium, high, or critical` - ); - } + if ( + !SEVERITY_LIST.some( + (item) => + typeof severityCutoff.toLowerCase() === "string" && + item === severityCutoff.toLowerCase() + ) + ) { + throw new Error( + `Invalid severity-cutoff value is set to ${severityCutoff} - please ensure you are choosing either negligible, low, medium, high, or critical` + ); + } - if (!version) { - version = `${grypeVersion}`; - } + if (!version) { + version = `${grypeVersion}`; + } - core.debug(`Installing grype version ${version}`); - await installGrype(version); + core.debug(`Installing grype version ${version}`); + await installGrype(version); - core.debug("Image: " + source); - core.debug("Debug Output: " + debug); - core.debug("Fail Build: " + failBuild); - core.debug("Severity Cutoff: " + severityCutoff); - core.debug("ACS Enable: " + acsReportEnable); + core.debug("Image: " + source); + core.debug("Debug Output: " + debug); + core.debug("Fail Build: " + failBuild); + core.debug("Severity Cutoff: " + severityCutoff); + core.debug("ACS Enable: " + acsReportEnable); - core.debug("Creating options for GRYPE analyzer"); + core.debug("Creating options for GRYPE analyzer"); - // Run the grype analyzer - let cmdOutput = ""; - let cmd = `${grypeBinary}`; - if (severityCutoff != "") { - cmdArgs.push("--fail-on"); - cmdArgs.push(severityCutoff.toLowerCase()); - } - cmdArgs.push(source); - const cmdOpts = {}; - cmdOpts.listeners = { - stdout: (data = Buffer) => { - cmdOutput += data.toString(); - }, - }; + // Run the grype analyzer + let cmdOutput = ""; + let cmd = `${grypeBinary}`; + if (severityCutoff != "") { + cmdArgs.push("--fail-on"); + cmdArgs.push(severityCutoff.toLowerCase()); + } + cmdArgs.push(source); + const cmdOpts = {}; + cmdOpts.listeners = { + stdout: (data = Buffer) => { + cmdOutput += data.toString(); + }, + }; - cmdOpts.ignoreReturnCode = true; + 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); + 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) - ); + // handle output + fs.writeFileSync( + "./vulnerabilities.json", + JSON.stringify(grypeVulnerabilities) + ); - if (acsReportEnable) { - try { - sarifGrypeGeneration(severityCutoff.toLowerCase(), version, source); - } catch (err) { - throw new Error(err); - } + if (acsReportEnable) { + try { + const serifOut = sarifGrypeGeneration( + severityCutoff.toLowerCase(), + version, + source + ); + Object.assign(out, serifOut); + } catch (err) { + throw new Error(err); } + } - if (failBuild === true && exitCode > 0) { - core.setFailed( + if (failBuild === true && exitCode > 0) { + core.setFailed( + `Failed minimum severity level. Found vulnerabilities with level ${severityCutoff} or higher` + ); + } + + 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 + // a grype problem + if (!severityCutoff) { + core.warning("grype had a non-zero exit status when running"); + } else { + // There is a non-zero exit status code with severity cut off, although there is still a chance this is grype + // that is broken, it will most probably be a failed severity. Using warning here will make it bubble up in the + // Actions UI + core.warning( `Failed minimum severity level. Found vulnerabilities with level ${severityCutoff} or higher` ); } - - core.setOutput("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 - // a grype problem - if (!severityCutoff) { - core.warning("grype had a non-zero exit status when running"); - } else { - // There is a non-zero exit status code with severity cut off, although there is still a chance this is grype - // that is broken, it will most probably be a failed severity. Using warning here will make it bubble up in the - // Actions UI - core.warning( - `Failed minimum severity level. Found vulnerabilities with level ${severityCutoff} or higher` - ); - } - } - } catch (error) { - core.setFailed(error.message); } + + return out; } function sarifGrypeGeneration(severity_cutoff_param, version, source) { @@ -528,12 +557,16 @@ function sarifGrypeGeneration(severity_cutoff_param, version, source) { source ); fs.writeFileSync(SARIF_FILE, JSON.stringify(sarifOutput, null, 2)); - core.setOutput("sarif", SARIF_FILE); + return { + sarif: SARIF_FILE, + }; // end sarif generate section } module.exports = { run, + runScan, + installGrype, mergeResults, findContent, loadContent, diff --git a/jest.config.js b/jest.config.js index 4123aa6f..e4438ef3 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,4 +1,5 @@ module.exports = { - setupFiles: ["/.jest/setEnvVars.js"], - verbose: true - }; \ No newline at end of file + setupFiles: ["/.jest/setEnvVars.js"], + verbose: true, + testPathIgnorePatterns: ["action.test.js"], +}; diff --git a/package-lock.json b/package-lock.json index 4ac1ab3e..601a935b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -897,6 +897,86 @@ } } }, + "@microsoft/jest-sarif": { + "version": "1.0.0-beta.0", + "resolved": "https://registry.npmjs.org/@microsoft/jest-sarif/-/jest-sarif-1.0.0-beta.0.tgz", + "integrity": "sha512-A1fOhPd6Ic5iDk4F9YPw1qNB4XF+7+IviR7xl8r2F6QzQkx5cpY9LsGk2rExiFIEcBbjpVnSmwWGBukASkiQ9A==", + "dev": true, + "requires": { + "ajv": "^6.12.6", + "chalk": "^4.1.0", + "sync-fetch": "^0.3.0" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "@sinonjs/commons": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.7.2.tgz", @@ -1080,18 +1160,6 @@ "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==", "dev": true }, - "ajv": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", - "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, "ansi-escapes": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", @@ -1423,6 +1491,12 @@ } } }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true + }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -1483,6 +1557,16 @@ "node-int64": "^0.4.0" } }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", @@ -2021,6 +2105,26 @@ "table": "^5.2.3", "text-table": "^0.2.0", "v8-compile-cache": "^2.0.3" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + } } }, "eslint-scope": { @@ -2331,9 +2435,9 @@ "dev": true }, "fast-deep-equal": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", - "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, "fast-json-stable-stringify": { @@ -2563,6 +2667,26 @@ "requires": { "ajv": "^6.5.5", "har-schema": "^2.0.0" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + } } }, "has-flag": { @@ -2689,6 +2813,12 @@ "safer-buffer": ">= 2.1.2 < 3" } }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true + }, "ignore": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", @@ -4440,12 +4570,6 @@ "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", "dev": true }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -4719,6 +4843,12 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "dev": true + }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -6186,6 +6316,16 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, + "sync-fetch": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/sync-fetch/-/sync-fetch-0.3.0.tgz", + "integrity": "sha512-dJp4qg+x4JwSEW1HibAuMi0IIrBI3wuQr2GimmqB7OXR50wmwzfdusG+p39R9w3R6aFtZ2mzvxvWKQ3Bd/vx3g==", + "dev": true, + "requires": { + "buffer": "^5.7.0", + "node-fetch": "^2.6.1" + } + }, "table": { "version": "5.4.6", "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", @@ -6198,6 +6338,18 @@ "string-width": "^3.0.0" }, "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "emoji-regex": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", @@ -6210,6 +6362,12 @@ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, "string-width": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", @@ -6453,9 +6611,9 @@ } }, "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, "requires": { "punycode": "^2.1.0" diff --git a/package.json b/package.json index 997a7bef..b0f2ef4d 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "lodash": "^4.17.21" }, "devDependencies": { + "@microsoft/jest-sarif": "^1.0.0-beta.0", "@vercel/ncc": "^0.24.1", "eslint": "^6.8.0", "husky": "^3.1.0", diff --git a/tests/README.md b/tests/README.md index 8ac216a6..2201cdd8 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,6 +1,26 @@ # Developing tests -Current tests are written in Python 3 and will download [act](https://github.com/nektos/act) and create a Python virtual environment to run them in. To run these locally, from the root directory execute: +Tests are being implemented in javascript (and soon to be Typescript). +Some tests require a docker registry running locally on port 5000. This is handled +automatically in the Github action tests, +but if you want to run the tests yourself you will need to have docker installed +and run something like: + +``` +docker run -d -p 5000:5000 --name registry registry:2 +``` + +... or if you run `make bootstrap`, this is automatically handled for you. After +which time, you can just run: + +``` +npm test +``` + +Some of the existing tests are written in Python 3 and will +download [act](https://github.com/nektos/act) and create a Python virtual +environment to run them in. To run these locally, from the root directory execute: + ``` npm run build make check diff --git a/tests/action_args.test.js b/tests/action_args.test.js new file mode 100644 index 00000000..4acd29c3 --- /dev/null +++ b/tests/action_args.test.js @@ -0,0 +1,82 @@ +const { run } = require("../index"); +const core = require("@actions/core"); + +jest.setTimeout(30000); + +describe("Github action args", () => { + it("runs without sarif report", async () => { + const inputs = { + image: "", + path: "tests/fixtures/npm-project", + debug: "false", + "fail-build": "true", + "acs-report-enable": "false", + "severity-cutoff": "medium", + "grype-version": "", + }; + const spyInput = jest.spyOn(core, "getInput").mockImplementation((name) => { + try { + return inputs[name]; + } finally { + inputs[name] = true; + } + }); + + const outputs = {}; + const spyOutput = jest + .spyOn(core, "setOutput") + .mockImplementation((name, value) => { + outputs[name] = value; + }); + + await run(); + + Object.keys(inputs).map((name) => { + expect(inputs[name]).toBe(true); + }); + + expect(outputs["vulnerabilities"]).toBe("./vulnerabilities.json"); + expect(outputs["sarif"]).toBeFalsy(); + + spyInput.mockRestore(); + spyOutput.mockRestore(); + }); + + it("runs with sarif report", async () => { + const inputs = { + image: "", + path: "tests/fixtures/npm-project", + debug: "false", + "fail-build": "true", + "acs-report-enable": "true", + "severity-cutoff": "medium", + "grype-version": "", + }; + const spyInput = jest.spyOn(core, "getInput").mockImplementation((name) => { + try { + return inputs[name]; + } finally { + inputs[name] = true; + } + }); + + const outputs = {}; + const spyOutput = jest + .spyOn(core, "setOutput") + .mockImplementation((name, value) => { + outputs[name] = value; + }); + + await run(); + + Object.keys(inputs).map((name) => { + expect(inputs[name]).toBe(true); + }); + + expect(outputs["vulnerabilities"]).toBe("./vulnerabilities.json"); + expect(outputs["sarif"]).toBe("./results.sarif"); + + spyInput.mockRestore(); + spyOutput.mockRestore(); + }); +}); diff --git a/tests/fixtures/image-alpine-match-coverage/Dockerfile b/tests/fixtures/image-alpine-match-coverage/Dockerfile new file mode 100644 index 00000000..770e60b5 --- /dev/null +++ b/tests/fixtures/image-alpine-match-coverage/Dockerfile @@ -0,0 +1,2 @@ +FROM scratch +COPY . . \ No newline at end of file diff --git a/tests/fixtures/image-alpine-match-coverage/etc/os-release b/tests/fixtures/image-alpine-match-coverage/etc/os-release new file mode 100644 index 00000000..be51cc6e --- /dev/null +++ b/tests/fixtures/image-alpine-match-coverage/etc/os-release @@ -0,0 +1,6 @@ +NAME="Alpine Linux" +ID=alpine +VERSION_ID=3.12.0 +PRETTY_NAME="Alpine Linux v3.12" +HOME_URL="https://alpinelinux.org/" +BUG_REPORT_URL="https://bugs.alpinelinux.org/" diff --git a/tests/fixtures/image-alpine-match-coverage/lib/apk/db/installed b/tests/fixtures/image-alpine-match-coverage/lib/apk/db/installed new file mode 100644 index 00000000..0afacfe3 --- /dev/null +++ b/tests/fixtures/image-alpine-match-coverage/lib/apk/db/installed @@ -0,0 +1,29 @@ +C:Q1z0MwWQKfva+S+q7XmOBYFfQgW/k= +P:libvncserver +V:0.9.9 +A:x86_64 +S:166239 +I:389120 +T:Library to make writing a vnc server easy +U:http://libvncserver.sourceforge.net/ +L:GPL-2.0-or-later +o:libvncserver +m:A. Wilcox +t:1572818861 +c:bf1ec813f662f128fc6b70f37ef1c0474bb24488 +D:so:libc.musl-x86_64.so.1 so:libgcrypt.so.20 so:libgnutls.so.30 so:libjpeg.so.8 so:libpng16.so.16 so:libz.so.1 +p:so:libvncclient.so.1=1.0.0 so:libvncserver.so.1=1.0.0 +F:usr +F:usr/lib +R:libvncclient.so.1 +a:0:0:777 +Z:Q1quyp/JcSPFQhtQFjMUYdMwRvAWM= +R:libvncserver.so.1.0.0 +a:0:0:755 +Z:Q16Pd1AqyqQRMwiFfbUt9XkYnkapw= +R:libvncserver.so.1 +a:0:0:777 +Z:Q184HrHsxEBqnsH4QNxeU5w8alhKI= +R:libvncclient.so.1.0.0 +a:0:0:755 +Z:Q1IEjCrEwVlQt2GjIsb3o39vcgqMg= diff --git a/tests/fixtures/image-centos-match-coverage/Dockerfile b/tests/fixtures/image-centos-match-coverage/Dockerfile new file mode 100644 index 00000000..770e60b5 --- /dev/null +++ b/tests/fixtures/image-centos-match-coverage/Dockerfile @@ -0,0 +1,2 @@ +FROM scratch +COPY . . \ No newline at end of file diff --git a/tests/fixtures/image-centos-match-coverage/etc/os-release b/tests/fixtures/image-centos-match-coverage/etc/os-release new file mode 100644 index 00000000..f57b52da --- /dev/null +++ b/tests/fixtures/image-centos-match-coverage/etc/os-release @@ -0,0 +1,11 @@ +NAME="CentOS Linux" +VERSION="8 (Core)" +ID="centos" +ID_LIKE="rhel fedora" +VERSION_ID="8" +PLATFORM_ID="platform:el8" +PRETTY_NAME="CentOS Linux 8 (Core)" +ANSI_COLOR="0;31" +CPE_NAME="cpe:/o:centos:centos:8" +HOME_URL="https://www.centos.org/" +BUG_REPORT_URL="https://bugs.centos.org/" \ No newline at end of file diff --git a/tests/fixtures/image-centos-match-coverage/var/lib/rpm/Packages b/tests/fixtures/image-centos-match-coverage/var/lib/rpm/Packages new file mode 100644 index 00000000..ef499f2f Binary files /dev/null and b/tests/fixtures/image-centos-match-coverage/var/lib/rpm/Packages differ diff --git a/tests/fixtures/image-centos-match-coverage/var/lib/rpm/generate-fixture.sh b/tests/fixtures/image-centos-match-coverage/var/lib/rpm/generate-fixture.sh new file mode 100755 index 00000000..80bf0996 --- /dev/null +++ b/tests/fixtures/image-centos-match-coverage/var/lib/rpm/generate-fixture.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -eux + +docker create --name generate-rpmdb-fixture centos:latest sh -c 'tail -f /dev/null' + +function cleanup { + docker kill generate-rpmdb-fixture + docker rm generate-rpmdb-fixture +} +trap cleanup EXIT + +docker start generate-rpmdb-fixture +docker exec -i --tty=false generate-rpmdb-fixture bash <<-EOF + mkdir -p /scratch + cd /scratch + rpm --initdb --dbpath /scratch + curl -sSLO https://github.com/wagoodman/dive/releases/download/v0.9.2/dive_0.9.2_linux_amd64.rpm + rpm --dbpath /scratch -ivh dive_0.9.2_linux_amd64.rpm + rm dive_0.9.2_linux_amd64.rpm + rpm --dbpath /scratch -qa +EOF + +docker cp generate-rpmdb-fixture:/scratch/Packages . diff --git a/tests/fixtures/image-debian-match-coverage/Dockerfile b/tests/fixtures/image-debian-match-coverage/Dockerfile new file mode 100644 index 00000000..770e60b5 --- /dev/null +++ b/tests/fixtures/image-debian-match-coverage/Dockerfile @@ -0,0 +1,2 @@ +FROM scratch +COPY . . \ No newline at end of file diff --git a/tests/fixtures/image-debian-match-coverage/java/example-java-app-maven-0.1.0.jar b/tests/fixtures/image-debian-match-coverage/java/example-java-app-maven-0.1.0.jar new file mode 100644 index 00000000..55313783 Binary files /dev/null and b/tests/fixtures/image-debian-match-coverage/java/example-java-app-maven-0.1.0.jar differ diff --git a/tests/fixtures/image-debian-match-coverage/java/generate-fixtures.md b/tests/fixtures/image-debian-match-coverage/java/generate-fixtures.md new file mode 100644 index 00000000..f8b00d07 --- /dev/null +++ b/tests/fixtures/image-debian-match-coverage/java/generate-fixtures.md @@ -0,0 +1 @@ +See the syft/cataloger/java/test-fixtures/java-builds dir to generate test fixtures and copy to here manually. diff --git a/tests/fixtures/image-debian-match-coverage/javascript/pkg-json/package.json b/tests/fixtures/image-debian-match-coverage/javascript/pkg-json/package.json new file mode 100644 index 00000000..eaf6a418 --- /dev/null +++ b/tests/fixtures/image-debian-match-coverage/javascript/pkg-json/package.json @@ -0,0 +1,84 @@ +{ + "version": "6.14.6", + "name": "npm", + "description": "a package manager for JavaScript", + "keywords": [ + "install", + "modules", + "package manager", + "package.json" + ], + "preferGlobal": true, + "config": { + "publishtest": false + }, + "homepage": "https://docs.npmjs.com/", + "author": "Isaac Z. Schlueter (http://blog.izs.me)", + "repository": { + "type": "git", + "url": "https://github.com/npm/cli" + }, + "bugs": { + "url": "https://npm.community/c/bugs" + }, + "directories": { + "bin": "./bin", + "doc": "./doc", + "lib": "./lib", + "man": "./man" + }, + "main": "./lib/npm.js", + "bin": { + "npm": "./bin/npm-cli.js", + "npx": "./bin/npx-cli.js" + }, + "dependencies": { + "JSONStream": "^1.3.5", + "abbrev": "~1.1.1", + "ansicolors": "~0.3.2", + "write-file-atomic": "^2.4.3" + }, + "bundleDependencies": [ + "abbrev", + "ansicolors", + "ansistyles", + "write-file-atomic" + ], + "devDependencies": { + "deep-equal": "^1.0.1", + "get-stream": "^4.1.0", + "licensee": "^7.0.3", + "marked": "^0.6.3", + "marked-man": "^0.6.0", + "npm-registry-couchapp": "^2.7.4", + "npm-registry-mock": "^1.3.1", + "require-inject": "^1.4.4", + "sprintf-js": "^1.1.2", + "standard": "^11.0.1", + "tacks": "^1.3.0", + "tap": "^12.7.0", + "tar-stream": "^2.1.0" + }, + "scripts": { + "dumpconf": "env | grep npm | sort | uniq", + "prepare": "node bin/npm-cli.js rebuild && node bin/npm-cli.js --no-audit --no-timing prune --prefix=. --no-global && rimraf test/*/*/node_modules && make -j4 mandocs", + "preversion": "bash scripts/update-authors.sh && git add AUTHORS && git commit -m \"update AUTHORS\" || true", + "licenses": "licensee --production --errors-only", + "tap": "tap -J --timeout 300 --no-esm", + "tap-cover": "tap -J --nyc-arg=--cache --coverage --timeout 600 --no-esm", + "lint": "standard", + "pretest": "npm run lint", + "test": "npm run test-tap --", + "test:nocleanup": "NO_TEST_CLEANUP=1 npm run test --", + "sudotest": "sudo npm run tap -- \"test/tap/*.js\"", + "sudotest:nocleanup": "sudo NO_TEST_CLEANUP=1 npm run tap -- \"test/tap/*.js\"", + "posttest": "rimraf test/npm_cache*", + "test-coverage": "npm run tap-cover -- \"test/tap/*.js\" \"test/network/*.js\"", + "test-tap": "npm run tap -- \"test/tap/*.js\" \"test/network/*.js\"", + "test-node": "tap --timeout 240 \"test/tap/*.js\" \"test/network/*.js\"" + }, + "license": "Artistic-2.0", + "engines": { + "node": "6 >=6.2.0 || 8 || >=9.3.0" + } +} diff --git a/tests/fixtures/image-debian-match-coverage/python/dist-info/METADATA b/tests/fixtures/image-debian-match-coverage/python/dist-info/METADATA new file mode 100644 index 00000000..924780df --- /dev/null +++ b/tests/fixtures/image-debian-match-coverage/python/dist-info/METADATA @@ -0,0 +1,47 @@ +Metadata-Version: 2.1 +Name: Pygments +Version: 2.6.1 +Summary: Pygments is a syntax highlighting package written in Python. +Home-page: https://pygments.org/ +Author: Georg Brandl +Author-email: georg@python.org +License: BSD License +Keywords: syntax highlighting +Platform: any +Classifier: License :: OSI Approved :: BSD License +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: End Users/Desktop +Classifier: Intended Audience :: System Administrators +Classifier: Development Status :: 6 - Mature +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Operating System :: OS Independent +Classifier: Topic :: Text Processing :: Filters +Classifier: Topic :: Utilities +Requires-Python: >=3.5 + + +Pygments +~~~~~~~~ + +Pygments is a syntax highlighting package written in Python. + +It is a generic syntax highlighter suitable for use in code hosting, forums, +wikis or other applications that need to prettify source code. Highlights +are: + +* a wide range of over 500 languages and other text formats is supported +* special attention is paid to details, increasing quality by a fair amount +* support for new languages and formats are added easily +* a number of output formats, presently HTML, LaTeX, RTF, SVG, all image formats that PIL supports and ANSI sequences +* it is usable as a command-line tool and as a library + +:copyright: Copyright 2006-2019 by the Pygments team, see AUTHORS. +:license: BSD, see LICENSE for details. + diff --git a/tests/fixtures/image-debian-match-coverage/python/dist-info/top_level.txt b/tests/fixtures/image-debian-match-coverage/python/dist-info/top_level.txt new file mode 100644 index 00000000..5deeb53d --- /dev/null +++ b/tests/fixtures/image-debian-match-coverage/python/dist-info/top_level.txt @@ -0,0 +1 @@ +pygments \ No newline at end of file diff --git a/tests/fixtures/image-debian-match-coverage/ruby/Gemfile.lock b/tests/fixtures/image-debian-match-coverage/ruby/Gemfile.lock new file mode 100644 index 00000000..eb30fad6 --- /dev/null +++ b/tests/fixtures/image-debian-match-coverage/ruby/Gemfile.lock @@ -0,0 +1,11 @@ +GEM + remote: https://rubygems.org/ + specs: + rails (4.1.1) + activerecord (= 4.1.1) + +PLATFORMS + ruby + +DEPENDENCIES + rails (= 4.1.1) \ No newline at end of file diff --git a/tests/fixtures/image-debian-match-coverage/ruby/specifications/bundler.gemspec b/tests/fixtures/image-debian-match-coverage/ruby/specifications/bundler.gemspec new file mode 100644 index 00000000..a877840b --- /dev/null +++ b/tests/fixtures/image-debian-match-coverage/ruby/specifications/bundler.gemspec @@ -0,0 +1,26 @@ +# frozen_string_literal: true +# -*- encoding: utf-8 -*- +# stub: bundler 2.1.4 ruby lib + +Gem::Specification.new do |s| + s.name = "bundler".freeze + s.version = "2.1.4" + + s.required_rubygems_version = Gem::Requirement.new(">= 2.5.2".freeze) if s.respond_to? :required_rubygems_version= + s.metadata = { "bug_tracker_uri" => "https://github.com/bundler/bundler/issues", "changelog_uri" => "https://github.com/bundler/bundler/blob/master/CHANGELOG.md", "homepage_uri" => "https://bundler.io/", "source_code_uri" => "https://github.com/bundler/bundler/" } if s.respond_to? :metadata= + s.require_paths = ["lib".freeze] + s.authors = ["Andr\u00E9 Arko".freeze, "Samuel Giddins".freeze, "Colby Swandale".freeze, "Hiroshi Shibata".freeze, "David Rodr\u00EDguez".freeze, "Grey Baker".freeze, "Stephanie Morillo".freeze, "Chris Morris".freeze, "James Wen".freeze, "Tim Moore".freeze, "Andr\u00E9 Medeiros".freeze, "Jessica Lynn Suttles".freeze, "Terence Lee".freeze, "Carl Lerche".freeze, "Yehuda Katz".freeze] + s.bindir = "exe".freeze + s.date = "2020-01-05" + s.description = "Bundler manages an application's dependencies through its entire life, across many machines, systematically and repeatably".freeze + s.email = ["team@bundler.io".freeze] + s.executables = ["bundle".freeze, "bundler".freeze] + s.files = ["exe/bundle".freeze, "exe/bundler".freeze] + s.homepage = "https://bundler.io".freeze + s.licenses = ["MIT".freeze] + s.required_ruby_version = Gem::Requirement.new(">= 2.3.0".freeze) + s.rubygems_version = "3.1.2".freeze + s.summary = "The best way to manage your application's dependencies".freeze + + s.installed_by_version = "3.1.2" if s.respond_to? :installed_by_version +end \ No newline at end of file diff --git a/tests/fixtures/image-debian-match-coverage/usr/lib/os-release b/tests/fixtures/image-debian-match-coverage/usr/lib/os-release new file mode 100644 index 00000000..120c51b0 --- /dev/null +++ b/tests/fixtures/image-debian-match-coverage/usr/lib/os-release @@ -0,0 +1,8 @@ +PRETTY_NAME="Debian GNU/Linux 8 (jessie)" +NAME="Debian GNU/Linux" +VERSION_ID="8" +VERSION="8 (jessie)" +ID=debian +HOME_URL="http://www.debian.org/" +SUPPORT_URL="http://www.debian.org/support" +BUG_REPORT_URL="https://bugs.debian.org/" diff --git a/tests/fixtures/image-debian-match-coverage/var/lib/dpkg/status b/tests/fixtures/image-debian-match-coverage/var/lib/dpkg/status new file mode 100644 index 00000000..da9beca3 --- /dev/null +++ b/tests/fixtures/image-debian-match-coverage/var/lib/dpkg/status @@ -0,0 +1,35 @@ +Package: apt +Status: install ok installed +Priority: required +Section: admin +Installed-Size: 4064 +Maintainer: APT Development Team +Architecture: amd64 +Version: 1.8.2 +Source: apt-dev +Replaces: apt-transport-https (<< 1.5~alpha4~), apt-utils (<< 1.3~exp2~) +Provides: apt-transport-https (= 1.8.2) +Depends: adduser, gpgv | gpgv2 | gpgv1, debian-archive-keyring, libapt-pkg5.0 (>= 1.7.0~alpha3~), libc6 (>= 2.15), libgcc1 (>= 1:3.0), libgnutls30 (>= 3.6.6), libseccomp2 (>= 1.0.1), libstdc++6 (>= 5.2) +Recommends: ca-certificates +Suggests: apt-doc, aptitude | synaptic | wajig, dpkg-dev (>= 1.17.2), gnupg | gnupg2 | gnupg1, powermgmt-base +Breaks: apt-transport-https (<< 1.5~alpha4~), apt-utils (<< 1.3~exp2~), aptitude (<< 0.8.10) +Conffiles: + /etc/apt/apt.conf.d/01autoremove 76120d358bc9037bb6358e737b3050b5 + /etc/cron.daily/apt-compat 49e9b2cfa17849700d4db735d04244f3 + /etc/kernel/postinst.d/apt-auto-removal 4ad976a68f045517cf4696cec7b8aa3a + /etc/logrotate.d/apt 179f2ed4f85cbaca12fa3d69c2a4a1c3 +Description: commandline package manager + This package provides commandline tools for searching and + managing as well as querying information about packages + as a low-level access to all features of the libapt-pkg library. + . + These include: + * apt-get for retrieval of packages and information about them + from authenticated sources and for installation, upgrade and + removal of packages together with their dependencies + * apt-cache for querying available information about installed + as well as installable packages + * apt-cdrom to use removable media as a source for packages + * apt-config as an interface to the configuration settings + * apt-key as an interface to manage authentication keys + diff --git a/tests/fixtures/npm-project/package-lock.json b/tests/fixtures/npm-project/package-lock.json new file mode 100644 index 00000000..dd8cc370 --- /dev/null +++ b/tests/fixtures/npm-project/package-lock.json @@ -0,0 +1,104 @@ +{ + "name": "npm-project", + "version": "0.12.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" + }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "requires": { + "minipass": "^3.0.0" + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "minipass": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", + "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==", + "requires": { + "yallist": "^4.0.0" + } + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + }, + "react": { + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", + "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2" + } + }, + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "tar": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.0.tgz", + "integrity": "sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA==", + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } +} diff --git a/tests/fixtures/npm-project/package.json b/tests/fixtures/npm-project/package.json new file mode 100644 index 00000000..0dd2ada1 --- /dev/null +++ b/tests/fixtures/npm-project/package.json @@ -0,0 +1,12 @@ +{ + "name": "npm-project", + "version": "0.12.1", + "description": "Basic NPM-based project", + "main": "index.js", + "author": "test@test", + "license": "MIT", + "dependencies": { + "react": "^16.14.0", + "tar": "^6.1.0" + } +} diff --git a/tests/fixtures/yarn-project/package.json b/tests/fixtures/yarn-project/package.json new file mode 100644 index 00000000..17916b76 --- /dev/null +++ b/tests/fixtures/yarn-project/package.json @@ -0,0 +1,12 @@ +{ + "name": "yarn-project", + "version": "2.5.34", + "description": "Basic Yarn-based project", + "main": "index.js", + "author": "test@test", + "license": "MIT", + "dependencies": { + "react": "16", + "trim": "0.0.2" + } +} diff --git a/tests/fixtures/yarn-project/yarn.lock b/tests/fixtures/yarn-project/yarn.lock new file mode 100644 index 00000000..d12e4f80 --- /dev/null +++ b/tests/fixtures/yarn-project/yarn.lock @@ -0,0 +1,48 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"js-tokens@^3.0.0 || ^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +loose-envify@^1.1.0, loose-envify@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + +prop-types@^15.6.2: + version "15.7.2" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" + integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.8.1" + +react-is@^16.8.1: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +react@16: + version "16.14.0" + resolved "https://registry.yarnpkg.com/react/-/react-16.14.0.tgz#94d776ddd0aaa37da3eda8fc5b6b18a4c9a3114d" + integrity sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.2" + +trim@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.2.tgz#b41afc68d6b5fc1a1fceb47b2ac91da258a071d4" + integrity sha512-kTIK/cS0xM3jxJ7toUHlFTxHgix/kmmBgOiqc0gUAoW+NjIRsMB3vkjgAth5XEghYFCQxOdF0p/PHrv1BqTHgA== diff --git a/tests/grype_command.test.js b/tests/grype_command.test.js new file mode 100644 index 00000000..8430e1e4 --- /dev/null +++ b/tests/grype_command.test.js @@ -0,0 +1,47 @@ +const githubActionsExec = require("@actions/exec"); +const githubActionsToolCache = require("@actions/tool-cache"); + +jest.setTimeout(30000); + +jest.spyOn(githubActionsToolCache, "find").mockImplementation(() => { + return "grype"; +}); + +const spyExec = jest.spyOn(githubActionsExec, "exec").mockImplementation(() => { + return Promise.resolve("{}"); +}); + +const mockExec = async (args) => { + try { + const { runScan } = require("../index"); + await runScan(args); + } catch (e) { + // ignore: this happens trying to parse command output, which we don't care about + } + const [cmd, params] = spyExec.mock.calls[spyExec.mock.calls.length - 1]; + return `${cmd} ${params.join(" ")}`; +}; + +describe("Grype command", () => { + it("is invoked with defaults", async () => { + let cmd = await mockExec({ source: "python:3.8" }); + expect(cmd).toBe("grype -o json --fail-on medium python:3.8"); + }); + + it("is invoked with dir", async () => { + let cmd = await mockExec({ source: "dir:.", severityCutoff: "high" }); + expect(cmd).toBe("grype -o json --fail-on high dir:."); + }); + + it("is invoked with values", async () => { + let cmd = await mockExec({ + source: "asdf", + debug: "true", + failBuild: "false", + acsReportEnable: "false", + severityCutoff: "low", + version: "0.6.0", + }); + expect(cmd).toBe("grype -vv -o json --fail-on low asdf"); + }); +}); diff --git a/tests/sarif_output.test.js b/tests/sarif_output.test.js new file mode 100644 index 00000000..0da50d6e --- /dev/null +++ b/tests/sarif_output.test.js @@ -0,0 +1,64 @@ +require("@microsoft/jest-sarif"); // for sarif validation + +const fs = require("fs"); +const { runScan } = require("../index"); + +jest.setTimeout(30000); + +const testSource = async (source, vulnerabilities) => { + if (fs.existsSync("./vulnerabilities.json")) { + fs.rmSync("./vulnerabilities.json"); + } + if (fs.existsSync("./results.sarif")) { + fs.rmSync("./results.sarif"); + } + + const out = await runScan({ + source, + acsReportEnable: "true", + }); + + // expect to get sarif output + const sarifFile = fs.readFileSync(out.sarif, "utf8"); + expect(sarifFile).not.toBeNull(); + + // expect the sarif to be valid + const sarif = JSON.parse(sarifFile); + expect(sarif).toBeValidSarifLog(); + + // expect to find some known error-level vulnerability + if (vulnerabilities.length === 0) { + expect(sarif.runs[0].results.length).toBe(0); + } else { + vulnerabilities.forEach((vuln) => { + expect(sarif.runs[0].results.find((r) => r.ruleId === vuln)).toBeTruthy(); + }); + } +}; + +describe("SARIF", () => { + it("alpine", async () => { + await testSource("localhost:5000/match-coverage/alpine:latest", [ + "ANCHOREVULN_CVE-2014-6051_apk_libvncserver_0.9.9", + ]); + }); + it("centos", async () => { + await testSource("localhost:5000/match-coverage/centos:latest", []); + }); + it("debian", async () => { + await testSource("localhost:5000/match-coverage/debian:latest", [ + "ANCHOREVULN_CVE-2020-36327_gem_bundler_2.1.4", + "ANCHOREVULN_GHSA-9w8r-397f-prfh_python_Pygments_2.6.1", + ]); + }); + it("npm", async () => { + await testSource("dir:tests/fixtures/npm-project", [ + "ANCHOREVULN_GHSA-3jfq-g458-7qm9_npm_tar_6.1.0", + ]); + }); + it("yarn", async () => { + await testSource("dir:tests/fixtures/yarn-project", [ + "ANCHOREVULN_GHSA-w5p7-h5w8-2hfq_npm_trim_0.0.2", + ]); + }); +});