Skip to content

Commit

Permalink
ci(.github): deprecate reuse of sca scan action in docker image scan (#…
Browse files Browse the repository at this point in the history
…95)

ci(.github): deprecate image inputs for sca action
  • Loading branch information
saisatishkarra committed Mar 4, 2024
1 parent b0ef627 commit 60c9b13
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 71 deletions.
25 changes: 0 additions & 25 deletions security-actions/sca/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,6 @@ inputs:
description: 'Specify a file to be scanned. This is mutually exclusive to dir and image'
required: false
default: ''
image:
description: 'specify an image to be scanned. Specify registry credentials if the image is remote. Takes priority over dir and file'
required: false
default: ''
tag:
description: 'specify a docker image tag / release tag / ref to be scanned'
required: false
default: ''
registry_username:
description: 'docker username to login against private docker registry'
required: false
registry_password:
description: 'docker password to login against private docker registry'
required: false
config:
description: 'file path to syft custom configuration'
required: false
Expand All @@ -45,9 +31,6 @@ outputs:
global-enforce-build-failure:
description: 'Globally fail the build on failure. Overrides fail_build when set'
value: ${{ steps.meta.outputs.global_enforce_build_failure }}
analyzed-image:
description: 'sanitized docker image / tar only when image input is specified'
value: ${{ steps.meta.outputs.scan_image }}
cis-json-report:
description: 'docker-cis json report'
value: ${{ steps.meta.outputs.cis_json_file }}
Expand All @@ -72,8 +55,6 @@ runs:
shell: bash
id: meta
env:
IMAGE: ${{ inputs.image }}
TAG: ${{ inputs.tag }}
DIR: ${{ inputs.dir }}
FILE: ${{ inputs.file }}
ASSET_PREFIX: ${{ inputs.asset_prefix }}
Expand All @@ -85,9 +66,6 @@ runs:
id: sbom_spdx
with:
config: ${{ inputs.config }}
image: ${{ steps.meta.outputs.scan_image }}
registry-username: ${{ inputs.registry_username }}
registry-password: ${{ inputs.registry_password }}
path: ${{ steps.meta.outputs.scan_dir }}
file: ${{ steps.meta.outputs.scan_file }}
format: spdx-json
Expand All @@ -102,9 +80,6 @@ runs:
id: sbom_cyclonedx
with:
config: ${{ inputs.config }}
image: ${{ steps.meta.outputs.scan_image }}
registry-username: ${{ inputs.registry_username }}
registry-password: ${{ inputs.registry_password }}
path: ${{ steps.meta.outputs.scan_dir }}
file: ${{ steps.meta.outputs.scan_file }}
format: cyclonedx-json
Expand Down
17 changes: 4 additions & 13 deletions security-actions/sca/scripts/scan-metadata.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,16 @@ readonly cis_json_ext="cis-report.json"
global_severity_cutoff='critical'
global_enforce_build_failure='false'

if [[ -n ${IMAGE} && -n ${DIR} ]] || [[ -n ${IMAGE} && -n ${FILE} ]] || [[ -n ${DIR} && -n ${FILE} ]]; then
echo '::error ::Input fields "image", "dir" and "file" are mutually exlcusive'
if [[ -n ${DIR} && -n ${FILE} ]]; then
echo '::error ::Input fields "dir" and "file" are mutually exlcusive'
exit 1
fi

if [[ -z ${IMAGE} && -z ${DIR} && -z ${FILE} ]]; then
echo '::error ::Specify one of "image", "dir" and "file" inputs fields'
if [[ -z ${DIR} && -z ${FILE} ]]; then
echo '::error ::Specify one of "dir" and "file" inputs fields'
exit 1
fi

# OCI archive should be passed as image instead of file
if [[ -n ${IMAGE} ]]; then
if [[ -n ${TAG} ]]; then
echo "scan_image=${IMAGE}:${TAG}" >> $GITHUB_OUTPUT
else
echo "scan_image=${IMAGE}" >> $GITHUB_OUTPUT
fi
fi

if [[ -n ${DIR} ]]; then
echo "scan_dir=${DIR}" >> $GITHUB_OUTPUT
fi
Expand Down
164 changes: 131 additions & 33 deletions security-actions/scan-docker-image/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ inputs:
registry_password:
description: 'docker password to login against private docker registry'
required: false
config:
description: 'file path to syft custom configuration'
required: false
fail_build:
description: 'fail the build if the vulnerability is above the severity cutoff'
required: false
Expand All @@ -32,84 +35,179 @@ inputs:
outputs:
cis-json-report:
description: 'docker-cis json report'
value: ${{ steps.sca.outputs.cis-json-report }}
value: ${{ steps.meta.outputs.cis_json_file }}
grype-json-report:
description: 'vulnerability json report'
value: ${{ steps.sca.outputs.grype_json_report }}
value: ${{ steps.meta.outputs.grype_json_report }}
grype-sarif-report:
description: 'vulnerability sarif report'
value: ${{ steps.sca.outputs.grype_sarif_report }}
value: ${{ steps.meta.outputs.grype_sarif_report }}
sbom-spdx-report:
description: 'SBOM spdx report'
value: ${{ steps.sca.outputs.sbom_spdx_file }}
value: ${{ steps.meta.outputs.sbom_spdx_file }}
sbom-cyclonedx-report:
description: 'SBOM cyclonedx report'
value: ${{ steps.sca.outputs.sbom_cyclonedx_file }}
value: ${{ steps.meta.outputs.sbom_cyclonedx_file }}

runs:
using: composite
steps:

# Due to https://github.com/orgs/community/discussions/41927
- name: Symlink current Actions repo
working-directory: ${{ github.action_path }}
- name: Set Scan Job Metadata
shell: bash
run: ln -fs $(realpath ../../) /home/runner/work/_actions/current
id: meta
env:
IMAGE: ${{ inputs.image }}
TAG: ${{ inputs.tag }}
ASSET_PREFIX: ${{ inputs.asset_prefix }}
run: $GITHUB_ACTION_PATH/scripts/scan-metadata.sh

# Must upload artifact for output file parameter to have effect
- name: Generate SPDX SBOM Using Syft
uses: anchore/sbom-action@v0.15.8
id: sbom_spdx
with:
config: ${{ inputs.config }}
image: ${{ steps.meta.outputs.scan_image }}
registry-username: ${{ inputs.registry_username }}
registry-password: ${{ inputs.registry_password }}
format: spdx-json
artifact-name: ${{ steps.meta.outputs.sbom_spdx_file }}
output-file: ${{ steps.meta.outputs.sbom_spdx_file }}
upload-artifact: true
upload-release-assets: false
dependency-snapshot: false

- name: Generate CycloneDX SBOM Using Syft
uses: anchore/sbom-action@v0.15.8
id: sbom_cyclonedx
with:
config: ${{ inputs.config }}
image: ${{ steps.meta.outputs.scan_image }}
registry-username: ${{ inputs.registry_username }}
registry-password: ${{ inputs.registry_password }}
format: cyclonedx-json
artifact-name: ${{ steps.meta.outputs.sbom_cyclonedx_file }}
output-file: ${{ steps.meta.outputs.sbom_cyclonedx_file }}
upload-artifact: true
upload-release-assets: false
dependency-snapshot: false

- name: Check SBOM files existence
uses: andstor/file-existence-action@v3
id: sbom_report
with:
files: "${{ steps.meta.outputs.sbom_spdx_file }}, ${{ steps.meta.outputs.sbom_cyclonedx_file }}"
fail: true

# Don't fail during report generation
- name: Vulnerability analysis of SBOM
uses: anchore/scan-action@v3.6.4
id: grype_analysis_sarif
if: ${{ steps.sbom_report.outputs.files_exists == 'true' }}
with:
sbom: ${{ steps.meta.outputs.sbom_spdx_file }}
output-format: sarif
fail-build: 'false'
add-cpes-if-none: true
severity-cutoff: ${{ steps.meta.outputs.global_severity_cutoff }}

# Don't fail during report generation
# JSON format will report any ignored rules
- name: Vulnerability analysis of SBOM
uses: anchore/scan-action@v3.6.4
id: grype_analysis_json
if: ${{ steps.sbom_report.outputs.files_exists == 'true' }}
with:
sbom: ${{ steps.meta.outputs.sbom_spdx_file }}
output-format: json
fail-build: 'false'
add-cpes-if-none: true
severity-cutoff: ${{ steps.meta.outputs.global_severity_cutoff }}

- run: |
ls -al /home/runner/work/_actions/current
- name: Check vulnerability analysis report existence
uses: andstor/file-existence-action@v3
id: grype_report
with:
files: "${{ steps.grype_analysis_sarif.outputs.sarif }}, ${{ steps.grype_analysis_json.outputs.json }}"
fail: true

# Grype CVE Action generates an ./results.sarif or ./results.report and no way to customize output file name
# Hack to increase readability of grype artifacts attached to workflows and releases
- name: Rename grype analysis report
shell: bash
run: |
mv ${{ steps.grype_analysis_sarif.outputs.sarif }} ${{ steps.meta.outputs.grype_sarif_file }}
mv ${{ steps.grype_analysis_json.outputs.json }} ${{ steps.meta.outputs.grype_json_file }}
- name: Upload grype analysis report
uses: actions/upload-artifact@v4
with:
name: ${{ steps.meta.outputs.grype_sarif_file }}
path: |
${{ steps.meta.outputs.grype_sarif_file }}
if-no-files-found: warn

# Upload grype cve reports
- name: Upload grype analysis report
uses: actions/upload-artifact@v4
with:
name: ${{ steps.meta.outputs.grype_json_file }}
path: |
${{ steps.meta.outputs.grype_json_file }}
if-no-files-found: warn

# Reuse SCA action for docker-image
- name: Perform Docker SCA
id: sca
uses: ./../../_actions/current/security-actions/sca
# Fail based on severity and input parameters
# Notify grype quick scan results in table format
# Table format will supress any specified ignore rules
- name: Inspect Vulnerability analysis of SBOM
uses: anchore/scan-action@v3.6.4
if: ${{ steps.sbom_report.outputs.files_exists == 'true' }}
with:
asset_prefix: ${{ inputs.asset_prefix }}
image: ${{ inputs.image }}
tag: ${{ inputs.tag }}
registry_username: ${{ inputs.registry_username }}
registry_password: ${{ inputs.registry_password }}
fail_build: ${{ inputs.fail_build }}
sbom: ${{ steps.meta.outputs.sbom_spdx_file }}
output-format: table
fail-build: ${{ steps.meta.outputs.global_enforce_build_failure == 'true' && steps.meta.outputs.global_enforce_build_failure || inputs.fail_build }}
add-cpes-if-none: true
severity-cutoff: ${{ steps.meta.outputs.global_severity_cutoff }}

- name: Check docker OCI tar existence
if: ${{ steps.sca.outputs.analyzed-image != '' }}
if: ${{ steps.meta.outputs.scan_image != '' }}
uses: andstor/file-existence-action@v3
id: docker_tar
with:
files: "${{ steps.sca.outputs.analyzed-image }}"
files: "${{ steps.meta.outputs.scan_image }}"

- name: Generate docker-cis JSON report
uses: docker://ghcr.io/aquasecurity/trivy:0.37.2
if: ${{ steps.sca.outputs.analyzed-image != '' }}
if: ${{ steps.meta.outputs.scan_image != '' }}
id: cis_json
with:
entrypoint: trivy
args: "image ${{ env.input }} ${{ steps.sca.outputs.analyzed-image }} --compliance ${{ env.compliance }} -f json --severity ${{ env.severity }} --ignore-unfixed -o ${{ steps.sca.outputs.cis-json-report }}"
args: "image ${{ env.input }} ${{ steps.meta.outputs.scan_image }} --compliance ${{ env.compliance }} -f json --severity ${{ env.severity }} --ignore-unfixed -o ${{ steps.meta.outputs.cis_json_file }}"
env:
compliance: docker-cis
severity: ${{ steps.sca.outputs.global_severity_cutoff }}
severity: ${{ steps.meta.outputs.global_enforce_build_failure }}
input: ${{ steps.docker_tar.outputs.files_exists == 'true' && '--input' || '' }}

- name: upload docker-cis JSON report
if: ${{ steps.sca.outputs.analyzed-image != '' }}
if: ${{ steps.meta.outputs.scan_image != '' }}
uses: actions/upload-artifact@v4
with:
name: ${{ steps.sca.outputs.cis-json-report }}
name: ${{ steps.meta.outputs.cis_json_file }}
path: |
${{ steps.sca.outputs.cis-json-report }}
${{ steps.meta.outputs.cis_json_file }}
if-no-files-found: warn

- name: Inspect docker-cis report
if: ${{ steps.sca.outputs.analyzed-image != '' }}
if: ${{ steps.meta.outputs.scan_image != '' }}
uses: docker://ghcr.io/aquasecurity/trivy:0.37.2
with:
entrypoint: trivy
args: "image ${{ env.input }} ${{ steps.sca.outputs.analyzed-image }} --compliance ${{ env.compliance }} -f table --severity ${{ env.severity }} --ignore-unfixed --exit-code ${{ env.exit-code }}"
args: "image ${{ env.input }} ${{ steps.meta.outputs.scan_image }} --compliance ${{ env.compliance }} -f table --severity ${{ env.severity }} --ignore-unfixed --exit-code ${{ env.exit-code }}"
env:
exit-code: ${{ (steps.sca.outputs.global_enforce_build_failure == 'true' || inputs.fail_build == 'true') && '1' || '0' }}
exit-code: ${{ (steps.meta.outputs.global_enforce_build_failure == 'true' || inputs.fail_build == 'true') && '1' || '0' }}
compliance: docker-cis
severity: ${{ steps.sca.outputs.global_severity_cutoff }}
severity: ${{ steps.meta.outputs.global_enforce_build_failure }}
input: ${{ steps.docker_tar.outputs.files_exists == 'true' && '--input' || '' }}


54 changes: 54 additions & 0 deletions security-actions/scan-docker-image/scripts/scan-metadata.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#!/usr/bin/env bash

set -euo pipefail

readonly spdx_ext="sbom.spdx.json"
readonly cyclonedx_ext="sbom.cyclonedx.json"
readonly cve_json_ext="cve-report.json"
readonly cve_sarif_ext="cve-report.sarif"
readonly cis_json_ext="cis-report.json"

global_severity_cutoff='critical'
global_enforce_build_failure='false'

if [[ -z ${IMAGE} ]]; then
echo '::error ::Specify "image" inputs fields'
exit 1
fi

# OCI archive should be passed as image instead of file
if [[ -n ${IMAGE} ]]; then
if [[ -n ${TAG} ]]; then
echo "scan_image=${IMAGE}:${TAG}" >> $GITHUB_OUTPUT
else
echo "scan_image=${IMAGE}" >> $GITHUB_OUTPUT
fi
fi

if [[ -n ${ASSET_PREFIX} ]]; then
echo "sbom_spdx_file=${ASSET_PREFIX##*/}-${spdx_ext}" >> $GITHUB_OUTPUT
echo "sbom_cyclonedx_file=${ASSET_PREFIX##*/}-${cyclonedx_ext}" >> $GITHUB_OUTPUT
echo "grype_json_file=${ASSET_PREFIX##*/}-${cve_json_ext}" >> $GITHUB_OUTPUT
echo "grype_sarif_file=${ASSET_PREFIX##*/}-${cve_sarif_ext}" >> $GITHUB_OUTPUT
echo "cis_json_file=${ASSET_PREFIX##*/}-${cis_json_ext}" >> $GITHUB_OUTPUT
else
echo "sbom_spdx_file=${spdx_ext}" >> $GITHUB_OUTPUT
echo "sbom_cyclonedx_file=${cyclonedx_ext}" >> $GITHUB_OUTPUT
echo "grype_json_file=${cve_json_ext}" >> $GITHUB_OUTPUT
echo "grype_sarif_file=${cve_sarif_ext}" >> $GITHUB_OUTPUT
echo "cis_json_file=${cis_json_ext}" >> $GITHUB_OUTPUT
fi

if [[ -n ${global_severity_cutoff} ]]; then
echo "global_severity_cutoff=${global_severity_cutoff}" >> $GITHUB_OUTPUT
else
echo '::error ::set global_severity_cutoff in $0'
exit 1
fi

if [[ -n ${global_enforce_build_failure} ]]; then
echo "global_enforce_build_failure=${global_enforce_build_failure}" >> $GITHUB_OUTPUT
else
echo '::error ::set global_enforce_build_failure in $0'
exit 1
fi

0 comments on commit 60c9b13

Please sign in to comment.