diff --git a/README.md b/README.md index 6c0b562f..081b6bf3 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,7 @@ The inputs `image`, `path`, and `sbom` are mutually exclusive to specify the sou | `image` | The image to scan | N/A | | `path` | The file path to scan | N/A | | `sbom` | The SBOM to scan | N/A | +| `config` | The path to the Grype configuration file | | | `registry-username` | The registry username to use when authenticating to an external registry | | | `registry-password` | The registry password to use when authenticating to an external registry | | | `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` | diff --git a/action.yml b/action.yml index 7093e3e7..04167a02 100644 --- a/action.yml +++ b/action.yml @@ -13,6 +13,9 @@ inputs: sbom: description: 'The SBOM file to scan. This option is mutually exclusive with "path" and "image".' required: false + config: + description: 'The path to the Grype configuration file.' + required: 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 diff --git a/dist/index.js b/dist/index.js index 77d0d19d..861e11a8 100644 --- a/dist/index.js +++ b/dist/index.js @@ -100,6 +100,7 @@ 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 config = core.getInput("config"); const failBuild = core.getInput("fail-build") || "true"; const outputFormat = core.getInput("output-format") || "sarif"; const severityCutoff = core.getInput("severity-cutoff") || "medium"; @@ -109,6 +110,7 @@ async function run() { const vex = core.getInput("vex") || ""; const out = await runScan({ source, + config, failBuild, severityCutoff, onlyFixed, @@ -127,6 +129,7 @@ async function run() { async function runScan({ source, + config, failBuild, severityCutoff, onlyFixed, @@ -170,6 +173,10 @@ async function runScan({ cmdArgs.push("-o", outputFormat); + if (config) { + cmdArgs.push("--config", config); + } + if ( !SEVERITY_LIST.some( (item) => @@ -197,6 +204,7 @@ async function runScan({ await installGrype(grypeVersion); core.debug("Source: " + source); + core.debug("Config: " + config); core.debug("Fail Build: " + failBuild); core.debug("Severity Cutoff: " + severityCutoff); core.debug("Only Fixed: " + onlyFixed); diff --git a/index.js b/index.js index 2428a4b6..81676462 100644 --- a/index.js +++ b/index.js @@ -86,6 +86,7 @@ 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 config = core.getInput("config"); const failBuild = core.getInput("fail-build") || "true"; const outputFormat = core.getInput("output-format") || "sarif"; const severityCutoff = core.getInput("severity-cutoff") || "medium"; @@ -95,6 +96,7 @@ async function run() { const vex = core.getInput("vex") || ""; const out = await runScan({ source, + config, failBuild, severityCutoff, onlyFixed, @@ -113,6 +115,7 @@ async function run() { async function runScan({ source, + config, failBuild, severityCutoff, onlyFixed, @@ -156,6 +159,10 @@ async function runScan({ cmdArgs.push("-o", outputFormat); + if (config) { + cmdArgs.push("--config", config); + } + if ( !SEVERITY_LIST.some( (item) => @@ -183,6 +190,7 @@ async function runScan({ await installGrype(grypeVersion); core.debug("Source: " + source); + core.debug("Config: " + config); core.debug("Fail Build: " + failBuild); core.debug("Severity Cutoff: " + severityCutoff); core.debug("Only Fixed: " + onlyFixed); diff --git a/tests/action_args.test.js b/tests/action_args.test.js index 5cabc507..442a67b7 100644 --- a/tests/action_args.test.js +++ b/tests/action_args.test.js @@ -13,7 +13,7 @@ describe("Github action args", () => { "output-format": "json", "severity-cutoff": "medium", "add-cpes-if-none": "true", - "vex": "test.vex", + vex: "test.vex", }; const spyInput = jest.spyOn(core, "getInput").mockImplementation((name) => { try { @@ -162,4 +162,34 @@ describe("Github action args", () => { spyInput.mockRestore(); }); + + it("runs with config file", async () => { + const inputs = { + path: "tests/fixtures/npm-project", + config: "tests/fixtures/config-file/custom-config.yaml", + }; + 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); + }); + + spyInput.mockRestore(); + spyOutput.mockRestore(); + }); }); diff --git a/tests/fixtures/config-file/custom-config.yaml b/tests/fixtures/config-file/custom-config.yaml new file mode 100644 index 00000000..e69de29b