diff --git a/.github/trivyignore b/.github/trivyignore new file mode 100644 index 00000000..209e3327 --- /dev/null +++ b/.github/trivyignore @@ -0,0 +1,3 @@ +# Advisories triaged out of main-binary-scan.yaml, one ID per line with the reason above it. Findings with +# no fixed version are already skipped by the workflow, so this is only for fixable findings in code the +# binary never calls. Remove an entry once the dependency moves past it. diff --git a/.github/workflows/main-binary-scan.yaml b/.github/workflows/main-binary-scan.yaml new file mode 100644 index 00000000..40f75324 --- /dev/null +++ b/.github/workflows/main-binary-scan.yaml @@ -0,0 +1,65 @@ +--- +# Scans the binaries for known vulnerabilities via the Go module and stdlib versions they embed. Two jobs so +# the red X on main says what to do: `release` scans the last published release (what users are running, so +# red means cut a release) and `main` scans a fresh build of this commit (what the next release would ship, +# so red means bump the dependency). govulncheck (pr-govulncheck.yaml) is the reachability check on PRs; +# this is the version-based artifact check, the same shape as hashicorp's release security scan. +name: binary vulnerability scan +on: + push: + branches: [ 'main' ] + schedule: + - cron: '23 5 * * 1' # weekly, so new advisories against unchanged binaries still turn main red + workflow_dispatch: # release.yaml reruns the failed jobs of the latest run on main once a release publishes + +permissions: + contents: read + +jobs: + release: + name: release binary + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Download the latest release + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + run: | + gh release download --pattern '*linux_amd64.tar.gz' --dir release + tar -xzf release/*.tar.gz -C release + rm release/*.tar.gz + # rootfs, not fs: fs mode does not read go binaries. unfixed findings are skipped because nothing can + # clear them; anything else triaged out goes in .github/trivyignore with a reason + - uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 + with: + scan-type: rootfs + scan-ref: release + scanners: vuln + severity: HIGH,CRITICAL + ignore-unfixed: true + trivyignores: .github/trivyignore + exit-code: 1 + + main: + name: build of main + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 + with: + go-version-file: ./.go-version + - run: go build -o out/terrafmt . + - uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 + with: + scan-type: rootfs + scan-ref: out + scanners: vuln + severity: HIGH,CRITICAL + ignore-unfixed: true + trivyignores: .github/trivyignore + exit-code: 1 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index f4826b14..fae542e0 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -63,3 +63,24 @@ jobs: TAG: ${{ github.ref_name }} HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }} run: ./scripts/update-homebrew-formula.sh + + # the binary scan (main-binary-scan.yaml) goes red on main when the published release has a known + # vulnerability, and only a release can clear it. rerun the failed jobs of the scan runs on main's head so + # the red check runs are replaced in place; a fresh run from a new event would sit beside them and the + # commit would stay red + rescan: + needs: goreleaser + runs-on: ubuntu-latest + permissions: + actions: write # rerun workflow jobs + steps: + - name: rerun the failed binary scan jobs on main + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + run: | + head=$(gh api "repos/$GH_REPO/commits/main" --jq .sha) + runs=$(gh run list -w main-binary-scan.yaml -c "$head" --json databaseId,conclusion \ + --jq '.[] | select(.conclusion == "failure") | .databaseId') + if [ -z "$runs" ]; then echo "no failed binary scan on main ($head), nothing to rerun"; exit 0; fi + for run in $runs; do echo "rerunning failed jobs of $run"; gh run rerun "$run" --failed; done