diff --git a/.github/workflows/dir-scan.yml b/.github/workflows/dir-scan.yml index 6855cc3d..f3745bf6 100644 --- a/.github/workflows/dir-scan.yml +++ b/.github/workflows/dir-scan.yml @@ -35,3 +35,5 @@ jobs: asset_prefix: test.insomnia dir: ${{env.TEST_REPOSITORY}} upload-sbom-release-assets: true + force_grype_db_update: true ## Explicitly skip cache + fail_build: false diff --git a/.github/workflows/docker-image-scan.yml b/.github/workflows/docker-image-scan.yml index d6f57130..44529a50 100644 --- a/.github/workflows/docker-image-scan.yml +++ b/.github/workflows/docker-image-scan.yml @@ -72,6 +72,7 @@ jobs: asset_prefix: test.kong-gateway-dev-linux-arm64 image: ${{env.IMAGE}}@${{ steps.image_manifest_metadata.outputs.arm64_sha }} upload-sbom-release-assets: true + force_grype_db_update: true ## Explicitly skip db cache when available test-download-sbom: if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }} diff --git a/security-actions/sca/action.yml b/security-actions/sca/action.yml index 0f5f58f8..4fa1d316 100644 --- a/security-actions/sca/action.yml +++ b/security-actions/sca/action.yml @@ -37,6 +37,14 @@ inputs: options: - 'true' - 'false' + force_grype_db_update: + required: false + default: false + description: 'Force to download DB when cache is available and up-to-date' + type: choice + options: + - 'true' + - 'false' # Outputs to be consumed by others using this SCA action outputs: @@ -110,11 +118,82 @@ runs: files: "${{ steps.meta.outputs.sbom_spdx_file }}, ${{ steps.meta.outputs.sbom_cyclonedx_file }}" fail: true + - name: Download Grype + uses: anchore/scan-action/download-grype@v4.1.1 + + # Check for any existing cache to reuse / update + - name: Cache Grype DB + id: cache_grype_db + if: ${{ inputs.force_grype_db_update != 'true' }} + uses: actions/cache@v4 + env: + cache-name: cache_grype_db + with: + # Grype cache files are stored in `~/.cache/grype/db` on Linux/macOS + path: ~/.cache/grype/db + key: ${{ env.cache-name }} + + # Make a network call to anchore grype CDN. + # This could fail when CDN is flaky for long periods of time. + # Setting timeout for available avoids long stuck grype processes on workflow jobs + + ## Edgecase: Grype DB will never update if stale cache is found + - name: Grype DB Check Updates + #if: ${{ steps.cache_grype_db.outputs.cache-hit != 'true' }} + id: grype_db_check_updates + shell: bash + run: | + db_check_status=0 + db_update_status=0 + echo "::group::Grype DB Status Check" + grype db check -vv || db_check_status=$? + if [[ "${db_check_status}" -eq 0 ]]; then + echo "::notice :: Grype DB is already up-to-date" + fi + echo "::endgroup::" + + echo "::group:: Update Grype DB" + if [[ "${db_check_status}" -ne 0 ]] || [[ ${FORCE_GRYPE_DB_UPDATE} == "true" ]]; then + grype db update -vv || db_update_status=$? + fi + + if [[ "${db_update_status}" -ne 0 ]]; then + GRYPE_DB_UPDATE_MSG="Grype DB updates was not successful. SCA / CVE Grype results might be skipped / unavailable due to DB issues" + if [[ ${FAIL_BUILD} -eq 1 ]]; then + echo "::error ::${GRYPE_DB_UPDATE_MSG}" + exit ${FAIL_BUILD} + elif [[ $FAIL_BUILD -eq 0 ]]; then + echo "::warning ::${GRYPE_DB_UPDATE_MSG}" + echo "GRYPE_DB_UPDATE_STATUS=${db_update_status}" >> $GITHUB_OUTPUT + fi + else + echo "::notice :: Grype DB is updated succesfully" + fi + echo "::endgroup::" + + echo "GRYPE_DB_UPDATE_STATUS=${db_update_status}" >> $GITHUB_OUTPUT + env: + FAIL_BUILD: ${{ (steps.meta.outputs.global_enforce_build_failure == 'true' || inputs.fail_build == 'true') && '1' || '0' }} + GRYPE_DB_UPDATE_AVAILABLE_TIMEOUT: 30s # timeout to fetch listing.json to check if db download is needed + GRYPE_DB_UPDATE_DOWNLOAD_TIMEOUT: 600s # timeout for actual db download if needed + FORCE_GRYPE_DB_UPDATE: ${{ inputs.force_grype_db_update }} + + - name: Cache Grype DB updates + if: ${{ steps.grype_db_check_updates.outputs.GRYPE_DB_UPDATE_STATUS == 0 }} + id: cache_grype_db_updates + uses: actions/cache@v4 + env: + cache-name: cache_grype_db # Use generic cache key instead of unique keys for different refs since CVE DB doesn't change frequently + with: + # Grype cache files are stored in `~/.cache/grype/db` on Linux/macOS + path: ~/.cache/grype/db + key: ${{ env.cache-name }} + # Don't fail during report generation - name: Vulnerability analysis of SBOM uses: anchore/scan-action@v4.1.1 id: grype_analysis_sarif - if: ${{ steps.sbom_report.outputs.files_exists == 'true' }} + if: ${{ steps.sbom_report.outputs.files_exists == 'true' && steps.grype_db_check_updates.outputs.GRYPE_DB_UPDATE_STATUS == 0 }} # Run only if DB is available on the runner }} with: sbom: ${{ steps.meta.outputs.sbom_spdx_file }} output-format: sarif @@ -127,7 +206,7 @@ runs: - name: Vulnerability analysis of SBOM uses: anchore/scan-action@v4.1.1 id: grype_analysis_json - if: ${{ steps.sbom_report.outputs.files_exists == 'true' }} + if: ${{ steps.sbom_report.outputs.files_exists == 'true' && steps.grype_db_check_updates.outputs.GRYPE_DB_UPDATE_STATUS == 0 }} # Run only if DB is available on the runner}} with: sbom: ${{ steps.meta.outputs.sbom_spdx_file }} output-format: json @@ -138,6 +217,7 @@ runs: GRYPE_DB_AUTO_UPDATE: false # Use grype db cache from grype step above - name: Check vulnerability analysis report existence + if: ${{ steps.grype_db_check_updates.outputs.GRYPE_DB_UPDATE_STATUS == 0 }} uses: andstor/file-existence-action@v3 id: grype_report with: @@ -148,11 +228,13 @@ runs: # Hack to increase readability of grype artifacts attached to workflows and releases - name: Rename grype analysis report shell: bash + if: ${{ steps.grype_db_check_updates.outputs.GRYPE_DB_UPDATE_STATUS == 0 }} # Run only if DB is available on the runner 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 + if: ${{ steps.grype_db_check_updates.outputs.GRYPE_DB_UPDATE_STATUS == 0 }} # Run only if DB is available on the runner uses: actions/upload-artifact@v4 with: name: ${{ steps.meta.outputs.grype_sarif_file }} @@ -162,6 +244,7 @@ runs: # Upload grype cve reports - name: Upload grype analysis report + if: ${{ steps.grype_db_check_updates.outputs.GRYPE_DB_UPDATE_STATUS == 0 }} # Run only if DB is available on the runner uses: actions/upload-artifact@v4 with: name: ${{ steps.meta.outputs.grype_json_file }} @@ -174,7 +257,7 @@ runs: # Table format will supress any specified ignore rules - name: Inspect Vulnerability analysis of SBOM uses: anchore/scan-action@v4.1.1 - if: ${{ steps.sbom_report.outputs.files_exists == 'true' }} + if: ${{ steps.sbom_report.outputs.files_exists == 'true' && steps.grype_db_check_updates.outputs.GRYPE_DB_UPDATE_STATUS == 0 }} with: sbom: ${{ steps.meta.outputs.sbom_spdx_file }} output-format: table diff --git a/security-actions/scan-docker-image/action.yml b/security-actions/scan-docker-image/action.yml index c81a8d6d..6de718ad 100644 --- a/security-actions/scan-docker-image/action.yml +++ b/security-actions/scan-docker-image/action.yml @@ -43,6 +43,14 @@ inputs: options: - 'true' - 'false' + force_grype_db_update: + required: false + default: false + description: 'Force to download DB when cache is available and up-to-date' + type: choice + options: + - 'true' + - 'false' outputs: cis-json-report: @@ -113,25 +121,99 @@ runs: with: files: "${{ steps.meta.outputs.sbom_spdx_file }}, ${{ steps.meta.outputs.sbom_cyclonedx_file }}" fail: true + + - name: Download Grype + uses: anchore/scan-action/download-grype@v4.1.1 + + # Check for any existing cache to reuse / update + - name: Cache Grype DB + if: ${{ inputs.force_grype_db_update != 'true' }} + id: cache_grype_db + uses: actions/cache@v4 + env: + cache-name: cache_grype_db + with: + # Grype cache files are stored in `~/.cache/grype/db` on Linux/macOS + path: ~/.cache/grype/db + key: ${{ env.cache-name }} + + # Make a network call to anchore grype CDN. + # This could fail when CDN is flaky for long periods of time. + # Setting timeout for available avoids long stuck grype processes on workflow jobs + ## Edgecase: Grype DB will never update if stale cache is found + - name: Grype DB Check Updates + #if: ${{ steps.cache_grype_db.outputs.cache-hit != 'true' }} + id: grype_db_check_updates + shell: bash + run: | + db_check_status=0 + db_update_status=0 + echo "::group::Grype DB Status Check" + grype db check -vv || db_check_status=$? + if [[ "${db_check_status}" -eq 0 ]]; then + echo "::notice :: Grype DB is already up-to-date" + fi + echo "::endgroup::" + + echo "::group:: Update Grype DB" + if [[ "${db_check_status}" -ne 0 ]] || [[ ${FORCE_GRYPE_DB_UPDATE} == "true" ]]; then + grype db update -vv || db_update_status=$? + fi + + if [[ "${db_update_status}" -ne 0 ]]; then + GRYPE_DB_UPDATE_MSG="Grype DB updates was not successful. SCA / CVE Grype results might be skipped / unavailable due to DB issues" + if [[ ${FAIL_BUILD} -eq 1 ]]; then + echo "::error ::${GRYPE_DB_UPDATE_MSG}" + exit ${FAIL_BUILD} + elif [[ $FAIL_BUILD -eq 0 ]]; then + echo "::warning ::${GRYPE_DB_UPDATE_MSG}" + echo "GRYPE_DB_UPDATE_STATUS=${db_update_status}" >> $GITHUB_OUTPUT + fi + else + echo "::notice :: Grype DB is updated succesfully" + fi + echo "::endgroup::" + + echo "GRYPE_DB_UPDATE_STATUS=${db_update_status}" >> $GITHUB_OUTPUT + env: + FAIL_BUILD: ${{ (steps.meta.outputs.global_enforce_build_failure == 'true' || inputs.fail_build == 'true') && '1' || '0' }} + GRYPE_DB_UPDATE_AVAILABLE_TIMEOUT: 30s # timeout to fetch listing.json to check if db download is needed + GRYPE_DB_UPDATE_DOWNLOAD_TIMEOUT: 600s # timeout for actual db download if needed + FORCE_GRYPE_DB_UPDATE: ${{ inputs.force_grype_db_update }} + + - name: Cache Grype DB updates + if: ${{ steps.grype_db_check_updates.outputs.GRYPE_DB_UPDATE_STATUS == 0 }} + id: cache_grype_db_updates + uses: actions/cache@v4 + env: + cache-name: cache_grype_db # Use generic cache key instead of unique keys for different refs since CVE DB doesn't change frequently + with: + # Grype cache files are stored in `~/.cache/grype/db` on Linux/macOS + path: ~/.cache/grype/db + key: ${{ env.cache-name }} + + # Grype is invoked first time ever # Don't fail during report generation - name: Vulnerability analysis of SBOM uses: anchore/scan-action@v4.1.1 id: grype_analysis_sarif - if: ${{ steps.sbom_report.outputs.files_exists == 'true' }} + if: ${{ steps.sbom_report.outputs.files_exists == 'true' && steps.grype_db_check_updates.outputs.GRYPE_DB_UPDATE_STATUS == 0 }} 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 }} + env: + GRYPE_DB_AUTO_UPDATE: false # Don't fail during report generation # JSON format will report any ignored rules - name: Vulnerability analysis of SBOM uses: anchore/scan-action@v4.1.1 id: grype_analysis_json - if: ${{ steps.sbom_report.outputs.files_exists == 'true' }} + if: ${{ steps.sbom_report.outputs.files_exists == 'true' && steps.grype_db_check_updates.outputs.GRYPE_DB_UPDATE_STATUS == 0 }} with: sbom: ${{ steps.meta.outputs.sbom_spdx_file }} output-format: json @@ -142,6 +224,7 @@ runs: GRYPE_DB_AUTO_UPDATE: false # Use grype db cache from grype step above - name: Check vulnerability analysis report existence + if: ${{ steps.grype_db_check_updates.outputs.GRYPE_DB_UPDATE_STATUS == 0 }} # Run only if DB is available on the runner uses: andstor/file-existence-action@v3 id: grype_report with: @@ -151,12 +234,14 @@ runs: # 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 + if: ${{ steps.grype_db_check_updates.outputs.GRYPE_DB_UPDATE_STATUS == 0 }} # Run only if DB is available on the runner 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 + if: ${{ steps.grype_db_check_updates.outputs.GRYPE_DB_UPDATE_STATUS == 0 }} # Run only if DB is available on the runner uses: actions/upload-artifact@v4 with: name: ${{ steps.meta.outputs.grype_sarif_file }} @@ -166,6 +251,7 @@ runs: # Upload grype cve reports - name: Upload grype analysis report + if: ${{ steps.grype_db_check_updates.outputs.GRYPE_DB_UPDATE_STATUS == 0 }} # Run only if DB is available on the runner uses: actions/upload-artifact@v4 with: name: ${{ steps.meta.outputs.grype_json_file }} @@ -178,7 +264,7 @@ runs: # Table format will supress any specified ignore rules - name: Inspect Vulnerability analysis of SBOM uses: anchore/scan-action@v4.1.1 - if: ${{ steps.sbom_report.outputs.files_exists == 'true' }} + if: ${{ steps.sbom_report.outputs.files_exists == 'true' && steps.grype_db_check_updates.outputs.GRYPE_DB_UPDATE_STATUS == 0 }} with: sbom: ${{ steps.meta.outputs.sbom_spdx_file }} output-format: table