|
| 1 | +# Copyright 2024 - 2025 Crunchy Data Solutions, Inc. |
| 2 | +# |
| 3 | +# SPDX-License-Identifier: Apache-2.0 |
| 4 | +# |
| 5 | +# schema documentation: https://docs.github.com/actions/sharing-automations/creating-actions/metadata-syntax-for-github-actions |
| 6 | +# yaml-language-server: $schema=https://json.schemastore.org/github-action.json |
| 7 | + |
| 8 | +name: Trivy |
| 9 | +description: Scan this project using Trivy |
| 10 | + |
| 11 | +# The Trivy team maintains an action, but it has trouble caching its vulnerability data: |
| 12 | +# https://github.com/aquasecurity/trivy-action/issues/389 |
| 13 | +# |
| 14 | +# 1. It caches vulnerability data once per calendar day, despite Trivy wanting |
| 15 | +# to download more frequently than that. |
| 16 | +# 2. When it fails to download the data, it fails the workflow *and* caches |
| 17 | +# the incomplete data. |
| 18 | +# 3. When (1) and (2) coincide, every following run that day *must* update the data, |
| 19 | +# producing more opportunities for (2) and more failed workflows. |
| 20 | +# |
| 21 | +# The action below uses any recent cache matching `cache-prefix` and calculates a cache key |
| 22 | +# derived from the data Trivy downloads. An older database is better than no scans at all. |
| 23 | +# When a run successfully updates the data, that data is cached and available to other runs. |
| 24 | + |
| 25 | +inputs: |
| 26 | + cache: |
| 27 | + default: restore,success,use |
| 28 | + description: >- |
| 29 | + What Trivy data to cache; one or more of restore, save, success, or use. |
| 30 | + The value "use" instructs Trivy to read and write to its cache. |
| 31 | + The value "restore" loads the Trivy cache from GitHub. |
| 32 | + The value "success" saves the Trivy cache to GitHub when Trivy succeeds. |
| 33 | + The value "save" saves the Trivy cache to GitHub regardless of Trivy. |
| 34 | +
|
| 35 | + database: |
| 36 | + default: update |
| 37 | + description: >- |
| 38 | + How Trivy should handle its data; one of update or skip. |
| 39 | + The value "skip" fetches no Trivy data at all. |
| 40 | +
|
| 41 | + setup: |
| 42 | + default: v0.65.0,cache |
| 43 | + description: >- |
| 44 | + How to install Trivy; one or more of version, none, or cache. |
| 45 | + The value "none" does not install Trivy at all. |
| 46 | +
|
| 47 | + cache-directory: |
| 48 | + default: ${{ github.workspace }}/.cache/trivy |
| 49 | + description: >- |
| 50 | + Directory where Trivy should store its data |
| 51 | +
|
| 52 | + cache-prefix: |
| 53 | + default: cache-trivy |
| 54 | + description: >- |
| 55 | + Name (key) where Trivy data should be stored in the GitHub cache |
| 56 | +
|
| 57 | + scan-target: |
| 58 | + default: . |
| 59 | + description: >- |
| 60 | + What Trivy should scan |
| 61 | +
|
| 62 | + scan-type: |
| 63 | + default: repository |
| 64 | + description: >- |
| 65 | + How Trivy should interpret scan-target; one of filesystem, image, repository, or sbom. |
| 66 | +
|
| 67 | +runs: |
| 68 | + using: composite |
| 69 | + steps: |
| 70 | + # Parse list inputs as separated by commas and spaces. |
| 71 | + # Select the maximum version-looking string from `inputs.setup`. |
| 72 | + - id: parsed |
| 73 | + shell: bash |
| 74 | + run: | |
| 75 | + # Validate inputs |
| 76 | + ( |
| 77 | + <<< '${{ inputs.cache }}' jq -rRsS '"cache=\(split("[,\\s]+"; "") - [""])"' |
| 78 | + <<< '${{ inputs.setup }}' jq -rRsS ' |
| 79 | + "setup=\(split("[,\\s]+"; "") - [""])", |
| 80 | + "version=\(split("[,\\s]+"; "") | max_by(split("[v.]"; "") | map(tonumber?)))" |
| 81 | + ' |
| 82 | + ) | tee --append "${GITHUB_OUTPUT}" |
| 83 | +
|
| 84 | + # Install Trivy as requested. |
| 85 | + # NOTE: `setup-trivy` can download a "latest" version but cannot cache it. |
| 86 | + - if: ${{ ! contains(fromJSON(steps.parsed.outputs.setup), 'none') }} |
| 87 | + uses: aquasecurity/setup-trivy@v0.2.4 |
| 88 | + with: |
| 89 | + cache: ${{ contains(fromJSON(steps.parsed.outputs.setup), 'cache') }} |
| 90 | + version: ${{ steps.parsed.outputs.version }} |
| 91 | + |
| 92 | + # Restore a recent cache beginning with the prefix. |
| 93 | + - id: restore |
| 94 | + if: ${{ contains(fromJSON(steps.parsed.outputs.cache), 'restore') }} |
| 95 | + uses: actions/cache/restore@v4 |
| 96 | + with: |
| 97 | + path: ${{ inputs.cache-directory }} |
| 98 | + key: ${{ inputs.cache-prefix }}- |
| 99 | + |
| 100 | + - id: trivy |
| 101 | + shell: bash |
| 102 | + env: |
| 103 | + TRIVY_CACHE_DIR: >- |
| 104 | + ${{ contains(fromJSON(steps.parsed.outputs.cache), 'use') && inputs.cache-directory || '' }} |
| 105 | + TRIVY_SKIP_CHECK_UPDATE: ${{ inputs.database == 'skip' }} |
| 106 | + TRIVY_SKIP_DB_UPDATE: ${{ inputs.database == 'skip' }} |
| 107 | + TRIVY_SKIP_JAVA_DB_UPDATE: ${{ inputs.database == 'skip' }} |
| 108 | + TRIVY_SKIP_VEX_REPO_UPDATE: ${{ inputs.database == 'skip' }} |
| 109 | + run: | |
| 110 | + # Run Trivy |
| 111 | + trivy '${{ inputs.scan-type }}' '${{ inputs.scan-target }}' || result=$? |
| 112 | +
|
| 113 | + checksum=$([[ -z "${TRIVY_CACHE_DIR}" ]] || cat "${TRIVY_CACHE_DIR}/"*/metadata.json | sha256sum) |
| 114 | + echo 'cache-key=${{ inputs.cache-prefix }}-'"${checksum%% *}" >> "${GITHUB_OUTPUT}" |
| 115 | +
|
| 116 | + exit "${result-0}" |
| 117 | +
|
| 118 | + # Save updated data to the cache when requested. |
| 119 | + - if: >- |
| 120 | + ${{ |
| 121 | + steps.restore.outcome == 'success' && |
| 122 | + steps.restore.outputs.cache-matched-key == steps.trivy.outputs.cache-key |
| 123 | + }} |
| 124 | + shell: bash |
| 125 | + run: | |
| 126 | + # Cache hit on ${{ steps.restore.outputs.cache-matched-key }} |
| 127 | + - if: >- |
| 128 | + ${{ |
| 129 | + steps.restore.outputs.cache-matched-key != steps.trivy.outputs.cache-key && |
| 130 | + ( |
| 131 | + (contains(fromJSON(steps.parsed.outputs.cache), 'save') && !cancelled()) || |
| 132 | + (contains(fromJSON(steps.parsed.outputs.cache), 'success') && success()) |
| 133 | + ) |
| 134 | + }} |
| 135 | + uses: actions/cache/save@v4 |
| 136 | + with: |
| 137 | + key: ${{ steps.trivy.outputs.cache-key }} |
| 138 | + path: ${{ inputs.cache-directory }} |
0 commit comments