diff --git a/.github/actions/trivy/action.yaml b/.github/actions/trivy/action.yaml new file mode 100644 index 0000000000..6b511c3bba --- /dev/null +++ b/.github/actions/trivy/action.yaml @@ -0,0 +1,99 @@ +name: Trivy +description: Scan this project using Trivy + +# The Trivy team maintains an action, but it has trouble caching its vulnerability data: +# https://github.com/aquasecurity/trivy-action/issues/389 +# +# The action below uses any recent cache matching `cache-prefix` and calculates a cache key +# derived from the data Trivy downloads. + +inputs: + cache: + default: restore,success,use + description: >- + What Trivy data to cache; one or more of restore, save, success, or use. + + setup: + default: v0.57.0,cache + description: >- + How to install Trivy; one or more of version, none, or cache. + + cache-directory: + default: ${{ github.workspace }}/.cache/trivy + + cache-prefix: + default: cache-trivy + + scan-target: + default: . + + scan-type: + default: filesystem + +runs: + using: composite + steps: + # Parse list inputs as separated by commas and spaces. + # Select the maximum version-looking string from `inputs.setup`. + - id: parsed + shell: bash + run: | + # Validate inputs + ( + <<< '${{ inputs.cache }}' jq -rRsS '"cache=\(split("[,\\s]+"; "") - [""])"' + <<< '${{ inputs.setup }}' jq -rRsS ' + "setup=\(split("[,\\s]+"; "") - [""])", + "version=\(split("[,\\s]+"; "") | max_by(split("[v.]"; "") | map(tonumber?)))" + ' + ) | tee --append $GITHUB_OUTPUT + + # Install Trivy as requested. + - if: ${{ ! contains(fromJSON(steps.parsed.outputs.setup), 'none') }} + uses: aquasecurity/setup-trivy@v0.2.2 + with: + cache: ${{ contains(fromJSON(steps.parsed.outputs.setup), 'cache') }} + version: ${{ steps.parsed.outputs.version }} + + # Restore a recent cache beginning with the prefix. + - id: restore + if: ${{ contains(fromJSON(steps.parsed.outputs.cache), 'restore') }} + uses: actions/cache/restore@v4 + with: + path: ${{ inputs.cache-directory }} + key: ${{ inputs.cache-prefix }}- + + - id: trivy + shell: bash + env: + TRIVY_CACHE_DIR: >- + ${{ contains(fromJSON(steps.parsed.outputs.cache), 'use') && inputs.cache-directory || '' }} + run: | + # Run Trivy + trivy '${{ inputs.scan-type }}' '${{ inputs.scan-target }}' || result=$? + + checksum=$([[ -z "${TRIVY_CACHE_DIR}" ]] || cat "${TRIVY_CACHE_DIR}/"*/metadata.json | sha256sum) + echo 'cache-key=${{ inputs.cache-prefix }}-'"${checksum%% *}" >> $GITHUB_OUTPUT + + exit "${result-0}" + + # Save updated data to the cache when requested. + - if: >- + ${{ + steps.restore.outcome == 'success' && + steps.restore.outputs.cache-matched-key == steps.trivy.outputs.cache-key + }} + shell: bash + run: | + # Cache hit on ${{ steps.restore.outputs.cache-matched-key }} + - if: >- + ${{ + steps.restore.outputs.cache-matched-key != steps.trivy.outputs.cache-key && + ( + (contains(fromJSON(steps.parsed.outputs.cache), 'save') && !cancelled()) || + (contains(fromJSON(steps.parsed.outputs.cache), 'success') && success()) + ) + }} + uses: actions/cache/save@v4 + with: + key: ${{ steps.trivy.outputs.cache-key }} + path: ${{ inputs.cache-directory }} diff --git a/.github/workflows/trivy.yaml b/.github/workflows/trivy.yaml index f6f4b2ca2d..a8447ee870 100644 --- a/.github/workflows/trivy.yaml +++ b/.github/workflows/trivy.yaml @@ -12,55 +12,21 @@ env: # https://github.com/actions/setup-go/issues/457 GOTOOLCHAIN: local - # Manage the Trivy data directory until upstream can do it reliably - # https://github.com/aquasecurity/trivy-action/issues/389 - # - # NOTE: This must match the default "cache-dir" upstream: - # https://github.com/aquasecurity/trivy-action/blob/-/action.yaml - TRIVY_CACHE_DIR: ${{ github.workspace }}/.cache/trivy - jobs: cache: + # Run only one of these jobs at a time across the entire project. + concurrency: { group: trivy-cache } + runs-on: ubuntu-latest steps: - - uses: aquasecurity/setup-trivy@v0.2.2 - with: - cache: true - version: v0.57.0 - - # The "aquasecurity/trivy-action" looks for data in the GitHub action - # cache under a key with today's date. - # - https://github.com/actions/cache/blob/-/restore#readme - # - https://github.com/aquasecurity/trivy-action/blob/-/action.yaml - - id: values - run: | - ( - date +'date=%Y-%m-%d' - echo "glob=${TRIVY_CACHE_DIR}/*/metadata.json" - ) | - tee --append $GITHUB_OUTPUT - - id: restore - uses: actions/cache/restore@v4 - with: - key: cache-trivy-${{ steps.values.outputs.date }} - path: ${{ env.TRIVY_CACHE_DIR }} - restore-keys: cache-trivy- - - # Validate or update the Trivy data cache. - - id: validate + - uses: actions/checkout@v4 + - name: Download Trivy + uses: ./.github/actions/trivy env: - METADATA_HASH: ${{ hashFiles(steps.values.outputs.glob) }} - run: | - <<< "before=${METADATA_HASH}" tee --append $GITHUB_OUTPUT - trivy filesystem --download-db-only --scanners license,secret,vuln --quiet - - # Save any successful changes back to the GitHub action cache. - # - https://github.com/actions/cache/blob/-/save#readme - - if: ${{ hashFiles(steps.values.outputs.glob) != steps.validate.outputs.before }} - uses: actions/cache/save@v4 - with: - key: ${{ steps.restore.outputs.cache-primary-key }} - path: ${{ env.TRIVY_CACHE_DIR }} + TRIVY_DEBUG: true + TRIVY_DOWNLOAD_DB_ONLY: true + TRIVY_NO_PROGRESS: true + TRIVY_SCANNERS: license,secret,vuln licenses: needs: [cache] @@ -75,13 +41,13 @@ jobs: # Report success only when detected licenses are listed in [/trivy.yaml]. - name: Scan licenses - uses: aquasecurity/trivy-action@0.28.0 + uses: ./.github/actions/trivy env: TRIVY_DEBUG: true + TRIVY_EXIT_CODE: 1 + TRIVY_SCANNERS: license with: - scan-type: filesystem - scanners: license - exit-code: 1 + cache: restore,use vulnerabilities: if: ${{ github.repository == 'CrunchyData/postgres-operator' }} @@ -97,21 +63,24 @@ jobs: # human consumption. This step fails only when Trivy is unable to scan. # A later step uploads results to GitHub as a pull request check. - name: Log detected vulnerabilities - uses: aquasecurity/trivy-action@0.28.0 + uses: ./.github/actions/trivy + env: + TRIVY_SCANNERS: secret,vuln with: - scan-type: filesystem - scanners: secret,vuln + cache: restore,use # Produce a SARIF report of actionable results. This step fails only when # Trivy is unable to scan. - name: Report actionable vulnerabilities - uses: aquasecurity/trivy-action@0.28.0 + uses: ./.github/actions/trivy + env: + TRIVY_IGNORE_UNFIXED: true + TRIVY_FORMAT: 'sarif' + TRIVY_OUTPUT: 'trivy-results.sarif' + TRIVY_SCANNERS: secret,vuln with: - scan-type: filesystem - ignore-unfixed: true - format: 'sarif' - output: 'trivy-results.sarif' - scanners: secret,vuln + cache: use + setup: none # Submit the SARIF report to GitHub code scanning. Pull requests checks # succeed or fail according to branch protection rules.