diff --git a/action.yaml b/action.yaml index 916d43f..c119ea9 100644 --- a/action.yaml +++ b/action.yaml @@ -172,9 +172,62 @@ inputs: required: false runs: - using: node20 - main: index.js - -branding: - icon: shield - color: gray-dark + using: composite + steps: + - uses: ./actions/install + id: install-scout + name: Install Docker Scout + with: + digest: 91fd9f3 + - uses: ./actions/run + id: run-scout + name: Run Docker Scout + with: + command: ${{ inputs.command }} + debug: ${{ inputs.debug }} + verbose-debug: ${{ inputs.verbose-debug }} + summary: ${{ inputs.summary }} + organization: ${{ inputs.organization }} + image: ${{ inputs.image }} + platform: ${{ inputs.platform }} + ref: ${{ inputs.ref }} + to: ${{ inputs.to }} + to-ref: ${{ inputs.to-ref }} + to-stream: ${{ inputs.to-stream }} + to-env: ${{ inputs.to-env }} + to-latest: ${{ inputs.to-latest }} + stream: ${{ inputs.stream }} + environment: ${{ inputs.environment }} + hide-policies: ${{ inputs.hide-policies }} + ignore-base: ${{ inputs.ignore-base }} + ignore-unchanged: ${{ inputs.ignore-unchanged }} + only-vex-affected: ${{ inputs.only-vex-affected }} + vex-author: ${{ inputs.vex-author }} + vex-location: ${{ inputs.vex-location }} + only-fixed: ${{ inputs.only-fixed }} + only-unfixed: ${{ inputs.only-unfixed }} + only-severities: ${{ inputs.only-severities }} + only-package-types: ${{ inputs.only-package-types }} + only-cisa-kev: ${{ inputs.only-cisa-kev }} + exit-code: ${{ inputs.exit-code }} + exit-on: ${{ inputs.exit-on }} + sarif-file: ${{ inputs.sarif-file }} + format: ${{ inputs.format }} + output: ${{ inputs.output }} + secrets: ${{ inputs.secrets }} + tags: ${{ inputs.tags }} + file: ${{ inputs.file }} + predicate-type: ${{ inputs.predicate-type }} + referrer: ${{ inputs.referrer }} + referrer-repository: ${{ inputs.referrer-repository }} + registry-write-user: ${{ inputs.registry-write-user }} + registry-write-password: ${{ inputs.registry-write-password }} + dockerhub-user: ${{ inputs.dockerhub-user }} + dockerhub-password: ${{ inputs.dockerhub-password }} + registry-user: ${{ inputs.registry-user }} + registry-password: ${{ inputs.registry-password }} + github-token: ${{ inputs.github-token }} + write-comment: ${{ inputs.write-comment }} + keep-previous-comments: ${{ inputs.keep-previous-comments }} + + \ No newline at end of file diff --git a/actions/install/action.yaml b/actions/install/action.yaml new file mode 100644 index 0000000..f68c319 --- /dev/null +++ b/actions/install/action.yaml @@ -0,0 +1,62 @@ +# https://help.github.com/en/articles/metadata-syntax-for-github-actions +name: 'Install Docker Scout' +description: 'Install docker scout CLI' + +inputs: + digest: + description: digest + required: true + +runs: + using: composite + steps: + - + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 + id: run + with: + script: | + const fs = require('fs'); + const os = require('os'); + const path = require('path'); + + await core.group(`Pull docker/scout-cli image`, async () => { + await exec.exec(`docker pull docker.io/docker/scout-cli@${{ inputs.digest }}`); + }); + + await core.group(`Copy binary`, async () => { + const res = await exec.getExecOutput('docker', ['create', 'docker.io/docker/scout-cli@${{ inputs.digest }}'], { + ignoreReturnCode: true + }); + if (res.stderr.length > 0 && res.exitCode != 0) { + throw new Error(res.stderr); + } + const ctnid = res.stdout.trim(); + const dockerCfgPath = process.env.DOCKER_CONFIG || path.join(os.homedir(), '.docker'); + const pluginsPath = path.join(dockerCfgPath, 'cli-plugins'); + fs.mkdirSync(pluginsPath, {recursive: true}); + await exec.exec(`docker cp ${ctnid}:/docker-scout ${pluginsPath}`); + await exec.exec(`docker rm -v ${ctnid}`); + }); + + await core.group(`Docker info`, async () => { + await exec.exec(`docker info`); + }); + + let version; + await core.group(`Docker scout version`, async () => { + const res = await exec.getExecOutput('docker', ['scout', 'version'], { + ignoreReturnCode: true, + silent: true + }); + if (res.stderr.length > 0 && res.exitCode != 0) { + throw new Error(res.stderr); + } + const matchVersion = res.stdout.trim().match(/version:\s(.*?)\s/); + version = matchVersion ? matchVersion[1] : null; + if (!version) { + throw new Error('Failed to get Docker scout version'); + } + core.info(version); + }); + + // TODO: cache binary \ No newline at end of file diff --git a/actions/run/action.yaml b/actions/run/action.yaml new file mode 100644 index 0000000..4e43a53 --- /dev/null +++ b/actions/run/action.yaml @@ -0,0 +1,179 @@ +# https://help.github.com/en/articles/metadata-syntax-for-github-actions +name: Run Docker Scout +description: List vulnerabilities in images; find better base images and upload an image SBOM to Docker Scout + +inputs: + command: + required: true + description: | + Command(s) to run. + Use a comma separated list to run several commands on the same set of parameters, for instance quickview,compare + debug: + required: false + description: Debug + verbose-debug: + required: false + description: Print more verbose debug messages + summary: + required: false + description: Publish the output as GitHub Action summary + default: true + + organization: + required: false + description: Namespace of the Docker organization + image: + required: false + description: Image to analyze + platform: + required: false + description: Platform of the image to analyze + ref: + required: false + description: Ref if needed + + # compare flags + to: + required: false + description: Image to compare to + to-ref: + required: false + description: Ref of image to compare + to-stream: + required: false + description: Compare to image in stream + deprecationMessage: Use to-env instead + to-env: + required: false + description: Compare to image in environment + to-latest: + required: false + description: Compare to latest pushed image + + # stream/environment flags + stream: + required: false + description: Name of the stream to record the image + deprecationMessage: Use environment instead + environment: + required: false + description: Name of the environment to record the image + + # policy flags + hide-policies: + required: false + description: Hide policies from the output altogether + + # filter flags + ignore-base: + required: false + description: Ignore vulnerabilities from base image + ignore-unchanged: + required: false + description: Filter out unchanged packages + only-vex-affected: + required: false + description: Filter out CVEs that are marked not affected by a VEX statement + vex-author: + required: false + description: List of VEX statement authors to accept + vex-location: + required: false + description: File location of directory or file containing VEX statement + only-fixed: + required: false + description: Filter to fixable CVEs + only-unfixed: + required: false + description: Filter to unfixed CVEs + only-severities: + required: false + description: Comma separated list of severities (critical, high, medium, low, unspecified) to filter CVEs by + only-package-types: + required: false + description: Comma separated list of package types (like apk, deb, rpm, npm, pypi, golang, etc) + only-cisa-kev: + required: false + description: Filter to CVEs listed in the CISA Known Exploited Vulnerabilities catalog + exit-code: + required: false + description: Fail the action step if vulnerability changes are detected + exit-on: + required: false + description: "(compare only) Comma separated list of conditions to fail the action step if worsened, options are: vulnerability, policy" + + sarif-file: + required: false + description: Write output to a SARIF file for further processing or upload into GitHub code scanning + + # sbom flags + format: + required: false + description: Format of the SBOM to generate (json, list, spdx, cyclonedx) + default: json + output: + required: false + description: Output file for the SBOM + secrets: + required: false + description: Enable secret scanning as part of SBOM indexing + + # attestation add flags + tags: + required: false + description: List of tags to add to the attestation + file: + required: false + description: File path to the attestation file + predicate-type: + required: false + description: Predicate type of the attestation + referrer: + required: false + description: Use OCI referrer API for pushing attestation + referrer-repository: + required: false + description: Repository to push referrer to + # credentials needed to push images + registry-write-user: + description: Registry user to push attestations + required: false + registry-write-password: + description: Registry password to push attestations + required: false + + dockerhub-user: + required: false + description: Docker Hub User + dockerhub-password: + required: false + description: Docker Hub PAT + + # credentials needed to pull private images + registry-user: + description: Registry user to pull images + required: false + registry-password: + description: Registry password to pull images + required: false + + # comments + github-token: + description: GitHub Token to write comments + default: ${{ github.token }} + required: false + write-comment: + description: Write the output as a Pull Request comment + required: false + default: true + keep-previous-comments: + description: If set, keep but hide previous comment. If not set, keep and update one single comment per job + required: false + +runs: + using: node20 + main: index.js + +branding: + icon: shield + color: gray-dark diff --git a/actions/run/index.js b/actions/run/index.js new file mode 100644 index 0000000..2f20511 --- /dev/null +++ b/actions/run/index.js @@ -0,0 +1,18 @@ +const childProcess = require('child_process') +const os = require('os') +const path = require('path') +const process = require('process') + +function main() { + const binary = chooseBinary() + const spawnSyncReturns = childProcess.spawnSync(BinaryName, { stdio: 'inherit' }) + const status = spawnSyncReturns.status + if (typeof status === 'number') { + process.exit(status) + } + process.exit(1) +} + +if (require.main === module) { + main() +} \ No newline at end of file diff --git a/dist/docker-scout-action_darwin_amd64 b/dist/docker-scout-action_darwin_amd64 deleted file mode 100755 index 48b405b..0000000 Binary files a/dist/docker-scout-action_darwin_amd64 and /dev/null differ diff --git a/dist/docker-scout-action_darwin_arm64 b/dist/docker-scout-action_darwin_arm64 deleted file mode 100755 index 7441072..0000000 Binary files a/dist/docker-scout-action_darwin_arm64 and /dev/null differ diff --git a/dist/docker-scout-action_linux_amd64 b/dist/docker-scout-action_linux_amd64 deleted file mode 100755 index cbb0b6a..0000000 Binary files a/dist/docker-scout-action_linux_amd64 and /dev/null differ diff --git a/dist/docker-scout-action_linux_arm64 b/dist/docker-scout-action_linux_arm64 deleted file mode 100755 index 9d1df97..0000000 Binary files a/dist/docker-scout-action_linux_arm64 and /dev/null differ diff --git a/dist/docker-scout-action_windows_amd64.exe b/dist/docker-scout-action_windows_amd64.exe deleted file mode 100755 index cf656fc..0000000 Binary files a/dist/docker-scout-action_windows_amd64.exe and /dev/null differ diff --git a/dist/docker-scout-action_windows_arm64.exe b/dist/docker-scout-action_windows_arm64.exe deleted file mode 100755 index dde27a6..0000000 Binary files a/dist/docker-scout-action_windows_arm64.exe and /dev/null differ diff --git a/index.js b/index.js deleted file mode 100644 index ec53a0b..0000000 --- a/index.js +++ /dev/null @@ -1,50 +0,0 @@ -const childProcess = require('child_process') -const os = require('os') -const path = require('path') -const process = require('process') - -const BinaryName = "docker-scout-action" - -function chooseBinary() { - const platform = os.platform() - const arch = os.arch() - - if (platform === 'darwin' && arch === 'x64') { - return `${BinaryName}_darwin_amd64` - } - if (platform === 'darwin' && arch === 'arm64') { - return `${BinaryName}_darwin_arm64` - } - if (platform === 'linux' && arch === 'x64') { - return `${BinaryName}_linux_amd64` - } - if (platform === 'linux' && arch === 'arm64') { - return `${BinaryName}_linux_arm64` - } - if (platform === 'win32' && arch === 'x64') { - return `${BinaryName}_windows_amd64.exe` - } - if (platform === 'win32' && arch === 'arm64') { - return `${BinaryName}_windows_arm64.exe` - } - - console.error(`Unsupported platform (${platform}) and architecture (${arch})`) - process.exit(1) - - return `${BinaryName}_${platform}_${arch}` -} - -function main() { - const binary = chooseBinary() - const mainScript = path.join(__dirname, "dist", binary); - const spawnSyncReturns = childProcess.spawnSync(mainScript, { stdio: 'inherit' }) - const status = spawnSyncReturns.status - if (typeof status === 'number') { - process.exit(status) - } - process.exit(1) -} - -if (require.main === module) { - main() -} \ No newline at end of file