From 562b8308b70ee7cfd6b4a5e29bd423a57da902b6 Mon Sep 17 00:00:00 2001 From: KyuubiYoru Date: Sat, 22 Aug 2026 21:06:21 +0200 Subject: [PATCH] fix(release): run trivy from a pinned verified binary (#1) trivy-action checks its own repository out of github.com using the runner token; on this self-hosted Gitea that token is a Gitea token, GitHub answers "Bad credentials", and both scan steps die before trivy is installed. Download the v0.69.3 release archive directly, verify it against a sha256 digest pinned inline, and unpack only the binary into .release-work/bin, which is gitignored and excluded from the Docker build context. The gate keeps its exact semantics: --exit-code 1, --severity HIGH,CRITICAL, table output, unfixed vulnerabilities still in scope. The SPDX step writes the same filename release_artifacts.py normalize-container-sbom consumes, and TRIVY_CACHE_DIR keeps the vulnerability DB inside the work directory. --- .gitea/workflows/release.yml | 57 ++++++++++++++----- .../Release/ReleaseCompatibilityTests.cs | 21 ++++++- 2 files changed, 62 insertions(+), 16 deletions(-) diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 5cc4708..b7d1053 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -196,23 +196,52 @@ jobs: exit 1 } + - name: Install pinned vulnerability scanner + shell: bash + run: | + set -euo pipefail + # trivy-action checks its own repository out of github.com with the + # runner token, which this Gitea instance cannot mint, so the release + # binary is fetched directly instead. The archive URL and its digest + # are pinned inline: fetching the published checksums file at run time + # would only prove the asset matches whatever the release currently + # serves, which is exactly what pinning has to rule out. + trivy_bin_dir="$RENDEZVOUS_WORK_DIR/bin" + trivy_archive="$RENDEZVOUS_WORK_DIR/trivy_0.69.3_Linux-64bit.tar.gz" + # .release-work is gitignored and excluded from the Docker build + # context, so nothing unpacked here can reach an image layer. + mkdir -p "$trivy_bin_dir" "$RENDEZVOUS_WORK_DIR/trivy-cache" + curl --fail --location --silent --show-error --output "$trivy_archive" \ + https://github.com/aquasecurity/trivy/releases/download/v0.69.3/trivy_0.69.3_Linux-64bit.tar.gz + echo "1816b632dfe529869c740c0913e36bd1629cb7688bd5634f4a858c1d57c88b75 $trivy_archive" \ + | sha256sum --check --strict - + tar --extract --file "$trivy_archive" --directory "$trivy_bin_dir" trivy + rm -f "$trivy_archive" + chmod +x "$trivy_bin_dir/trivy" + echo "RENDEZVOUS_TRIVY=$trivy_bin_dir/trivy" >>"$GITHUB_ENV" + echo "TRIVY_CACHE_DIR=$RENDEZVOUS_WORK_DIR/trivy-cache" >>"$GITHUB_ENV" + "$trivy_bin_dir/trivy" --version + - name: Scan candidate for high and critical vulnerabilities - uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.35.0, post-incident safe SHA - with: - image-ref: git.finalfactory.de/heikyu/rendezvous:${{ env.RENDEZVOUS_VERSION }} - version: v0.69.3 - format: table - exit-code: "1" - ignore-unfixed: false - severity: HIGH,CRITICAL + shell: bash + run: | + set -euo pipefail + # Unfixed vulnerabilities stay in scope: the flag that would drop them + # is never passed, so the gate keeps trivy's fail-closed default. + "$RENDEZVOUS_TRIVY" image \ + --exit-code 1 \ + --severity HIGH,CRITICAL \ + --format table \ + "git.finalfactory.de/heikyu/rendezvous:${RENDEZVOUS_VERSION}" - name: Generate container SPDX inventory - uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.35.0, post-incident safe SHA - with: - image-ref: git.finalfactory.de/heikyu/rendezvous:${{ env.RENDEZVOUS_VERSION }} - version: v0.69.3 - format: spdx-json - output: ${{ env.RENDEZVOUS_RELEASE_DIR }}/FinalFactory.Rendezvous.Container.${{ env.RENDEZVOUS_VERSION }}.spdx.json + shell: bash + run: | + set -euo pipefail + "$RENDEZVOUS_TRIVY" image \ + --format spdx-json \ + --output "$RENDEZVOUS_RELEASE_DIR/FinalFactory.Rendezvous.Container.$RENDEZVOUS_VERSION.spdx.json" \ + "git.finalfactory.de/heikyu/rendezvous:${RENDEZVOUS_VERSION}" - name: Finalize checksums over the publish-ready candidate shell: bash diff --git a/tests/FinalFactory.Rendezvous.Tests/Release/ReleaseCompatibilityTests.cs b/tests/FinalFactory.Rendezvous.Tests/Release/ReleaseCompatibilityTests.cs index ae2ff79..1ef3656 100644 --- a/tests/FinalFactory.Rendezvous.Tests/Release/ReleaseCompatibilityTests.cs +++ b/tests/FinalFactory.Rendezvous.Tests/Release/ReleaseCompatibilityTests.cs @@ -86,8 +86,25 @@ public sealed class ReleaseCompatibilityTests Assert.DoesNotContain("skip-duplicate", workflow, StringComparison.OrdinalIgnoreCase); Assert.Contains("check-compatibility.sh", workflow, StringComparison.Ordinal); Assert.Contains("--platform linux/amd64", workflow, StringComparison.Ordinal); - Assert.Contains("ignore-unfixed: false", workflow, StringComparison.Ordinal); - Assert.Contains("format: spdx-json", workflow, StringComparison.Ordinal); + Assert.DoesNotContain("aquasecurity/trivy-action", workflow, StringComparison.Ordinal); + Assert.Contains( + "https://github.com/aquasecurity/trivy/releases/download/v0.69.3/trivy_0.69.3_Linux-64bit.tar.gz", + workflow, + StringComparison.Ordinal); + Assert.Contains( + "1816b632dfe529869c740c0913e36bd1629cb7688bd5634f4a858c1d57c88b75", + workflow, + StringComparison.Ordinal); + Assert.Contains("sha256sum --check --strict", workflow, StringComparison.Ordinal); + Assert.Contains("TRIVY_CACHE_DIR=$RENDEZVOUS_WORK_DIR/trivy-cache", workflow, StringComparison.Ordinal); + Assert.Contains("--exit-code 1", workflow, StringComparison.Ordinal); + Assert.Contains("--severity HIGH,CRITICAL", workflow, StringComparison.Ordinal); + Assert.DoesNotContain("--ignore-unfixed", workflow, StringComparison.Ordinal); + Assert.Contains("--format spdx-json", workflow, StringComparison.Ordinal); + Assert.Contains( + "--output \"$RENDEZVOUS_RELEASE_DIR/FinalFactory.Rendezvous.Container.$RENDEZVOUS_VERSION.spdx.json\"", + workflow, + StringComparison.Ordinal); Assert.Contains("normalize-container-sbom", workflow, StringComparison.Ordinal); Assert.Contains("finalize-release-candidate.sh", workflow, StringComparison.Ordinal); Assert.Contains("verify-real-consumers.sh", workflow, StringComparison.Ordinal);