fix(ci): scan multi-arch images per-arch by digest, pin trivy-action (#476)
Resolves the intermittent "no child with platform linux/amd64 in
index" failure on the merge-backend job — and fixes the same latent
bug on merge-frontend before it surfaces.
Two compounding root causes per Luca's diagnosis:
1. aquasecurity/trivy-action@master was unpinned, so the action and
its bundled Trivy binary float on every CI run. A green build
could flip red overnight without a single repo change.
2. Trivy was asked to scan a multi-platform OCI index by tag (the
merge-* jobs ran AFTER manifest creation). Its remote resolver
cannot reliably pick the right per-arch child out of an index
reference — it needs a single-platform reference (digest, or a
--platform flag).
Fix:
- Move the Trivy + upload-sarif steps OUT of merge-backend /
merge-frontend and INTO the per-arch build-backend / build-frontend
matrix jobs. Each leg scans the image it just pushed by its
sha256 digest (`...@${{ steps.build.outputs.digest }}`), which is
always single-platform by construction.
- Pin aquasecurity/[email protected].0 (was @master).
- Distinct SARIF category per arch
(`backend-vulnerabilities-linux-amd64`, …-arm64) so an
amd64-only finding in a base layer doesn't get masked by the
arm64 scan in the Security tab.
- Move security-events: write down to the build-* jobs (where the
scan now runs) and remove it from the merge-* jobs (which only
publish the manifest now).
Out of scope: flipping `exit-code: '1'` to actually gate CI on
findings. Worth doing as a separate follow-up after an audit pass —
landing it here would surprise beta with a red build for any
pre-existing CRITICAL/HIGH in current images. Inline TODO in the
workflow notes the deferral.
This commit is contained in:
@@ -69,6 +69,11 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
|
# Trivy uploads its SARIF to the Security tab from this job — see
|
||||||
|
# the "Run Trivy" step below. Scanning per-arch by digest (#476)
|
||||||
|
# is reliable; scanning the multi-arch index by tag from the
|
||||||
|
# merge-* job was not.
|
||||||
|
security-events: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
@@ -153,13 +158,47 @@ jobs:
|
|||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
|
|
||||||
|
# Per-arch vulnerability scan (#476). Scanning the multi-arch
|
||||||
|
# manifest from the merge-* job by tag is unreliable — Trivy's
|
||||||
|
# remote resolver crashes intermittently with "no child with
|
||||||
|
# platform linux/amd64 in index". The fix is to scan each leg
|
||||||
|
# by its single-platform digest right here, where it just landed
|
||||||
|
# in GHCR. Tag pinned (was @master) so the action + bundled
|
||||||
|
# Trivy binary don't float between runs.
|
||||||
|
#
|
||||||
|
# exit-code is left unset (=0) for now: Trivy reports findings
|
||||||
|
# to the Security tab but doesn't fail the build. Flipping that
|
||||||
|
# to '1' to actually gate CI is a deliberate follow-up — needs an
|
||||||
|
# audit pass first so the next beta build doesn't surprise red.
|
||||||
|
- name: Run Trivy vulnerability scanner (per-arch, by digest)
|
||||||
|
if: steps.push-decision.outputs.push == 'true'
|
||||||
|
uses: aquasecurity/[email protected]
|
||||||
|
with:
|
||||||
|
image-ref: ${{ env.REGISTRY }}/${{ env.BACKEND_IMAGE_NAME }}@${{ steps.build.outputs.digest }}
|
||||||
|
format: 'sarif'
|
||||||
|
output: 'trivy-backend-${{ env.PLATFORM_PAIR }}.sarif'
|
||||||
|
severity: 'CRITICAL,HIGH'
|
||||||
|
timeout: '10m'
|
||||||
|
|
||||||
|
- name: Upload Trivy scan results to GitHub Security tab
|
||||||
|
if: steps.push-decision.outputs.push == 'true'
|
||||||
|
uses: github/codeql-action/upload-sarif@v4
|
||||||
|
with:
|
||||||
|
sarif_file: 'trivy-backend-${{ env.PLATFORM_PAIR }}.sarif'
|
||||||
|
# Distinct category per arch so the Security tab surfaces
|
||||||
|
# per-platform findings independently — an amd64-only CVE in
|
||||||
|
# a base layer doesn't get masked by the arm64 scan.
|
||||||
|
category: 'backend-vulnerabilities-${{ env.PLATFORM_PAIR }}'
|
||||||
|
|
||||||
merge-backend:
|
merge-backend:
|
||||||
needs: build-backend
|
needs: build-backend
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
# No security-events permission here — vulnerability scanning moved
|
||||||
|
# to per-arch build-backend jobs (#476). This job's only job is to
|
||||||
|
# combine the per-arch digests into a multi-arch manifest.
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
security-events: write
|
|
||||||
# Only run when at least one digest was pushed (i.e. not on PRs without push intent).
|
# Only run when at least one digest was pushed (i.e. not on PRs without push intent).
|
||||||
if: github.event_name != 'pull_request' || github.event.inputs.push == 'true'
|
if: github.event_name != 'pull_request' || github.event.inputs.push == 'true'
|
||||||
|
|
||||||
@@ -231,23 +270,6 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.BACKEND_IMAGE_NAME }}:${{ steps.meta-backend.outputs.version }}
|
docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.BACKEND_IMAGE_NAME }}:${{ steps.meta-backend.outputs.version }}
|
||||||
|
|
||||||
- name: Run Trivy vulnerability scanner
|
|
||||||
if: github.event_name != 'pull_request' && steps.login-ghcr.outcome == 'success'
|
|
||||||
uses: aquasecurity/trivy-action@master
|
|
||||||
with:
|
|
||||||
image-ref: ${{ env.REGISTRY }}/${{ env.BACKEND_IMAGE_NAME }}:${{ steps.meta-backend.outputs.version }}
|
|
||||||
format: 'sarif'
|
|
||||||
output: 'trivy-backend.sarif'
|
|
||||||
severity: 'CRITICAL,HIGH'
|
|
||||||
timeout: '10m'
|
|
||||||
|
|
||||||
- name: Upload Trivy scan results to GitHub Security tab
|
|
||||||
if: github.event_name != 'pull_request' && steps.login-ghcr.outcome == 'success'
|
|
||||||
uses: github/codeql-action/upload-sarif@v4
|
|
||||||
with:
|
|
||||||
sarif_file: 'trivy-backend.sarif'
|
|
||||||
category: 'backend-vulnerabilities'
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Frontend: per-arch build, then merge into a multi-arch manifest
|
# Frontend: per-arch build, then merge into a multi-arch manifest
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
@@ -264,6 +286,9 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
|
# See build-backend for the rationale (#476). Same pattern: per-arch
|
||||||
|
# vulnerability scan by digest, SARIF uploaded to the Security tab.
|
||||||
|
security-events: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
@@ -348,13 +373,34 @@ jobs:
|
|||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
|
|
||||||
|
# Per-arch vulnerability scan (#476). See build-backend for the
|
||||||
|
# full rationale; identical pattern here, only the image-ref +
|
||||||
|
# SARIF filename + category change.
|
||||||
|
- name: Run Trivy vulnerability scanner (per-arch, by digest)
|
||||||
|
if: steps.push-decision.outputs.push == 'true'
|
||||||
|
uses: aquasecurity/[email protected]
|
||||||
|
with:
|
||||||
|
image-ref: ${{ env.REGISTRY }}/${{ env.FRONTEND_IMAGE_NAME }}@${{ steps.build.outputs.digest }}
|
||||||
|
format: 'sarif'
|
||||||
|
output: 'trivy-frontend-${{ env.PLATFORM_PAIR }}.sarif'
|
||||||
|
severity: 'CRITICAL,HIGH'
|
||||||
|
timeout: '10m'
|
||||||
|
|
||||||
|
- name: Upload Trivy scan results to GitHub Security tab
|
||||||
|
if: steps.push-decision.outputs.push == 'true'
|
||||||
|
uses: github/codeql-action/upload-sarif@v4
|
||||||
|
with:
|
||||||
|
sarif_file: 'trivy-frontend-${{ env.PLATFORM_PAIR }}.sarif'
|
||||||
|
category: 'frontend-vulnerabilities-${{ env.PLATFORM_PAIR }}'
|
||||||
|
|
||||||
merge-frontend:
|
merge-frontend:
|
||||||
needs: build-frontend
|
needs: build-frontend
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
# See merge-backend — vulnerability scanning moved to the per-arch
|
||||||
|
# build-frontend matrix (#476). This job only publishes the manifest.
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
security-events: write
|
|
||||||
if: github.event_name != 'pull_request' || github.event.inputs.push == 'true'
|
if: github.event_name != 'pull_request' || github.event.inputs.push == 'true'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -425,23 +471,6 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.FRONTEND_IMAGE_NAME }}:${{ steps.meta-frontend.outputs.version }}
|
docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.FRONTEND_IMAGE_NAME }}:${{ steps.meta-frontend.outputs.version }}
|
||||||
|
|
||||||
- name: Run Trivy vulnerability scanner
|
|
||||||
if: github.event_name != 'pull_request' && steps.login-ghcr.outcome == 'success'
|
|
||||||
uses: aquasecurity/trivy-action@master
|
|
||||||
with:
|
|
||||||
image-ref: ${{ env.REGISTRY }}/${{ env.FRONTEND_IMAGE_NAME }}:${{ steps.meta-frontend.outputs.version }}
|
|
||||||
format: 'sarif'
|
|
||||||
output: 'trivy-frontend.sarif'
|
|
||||||
severity: 'CRITICAL,HIGH'
|
|
||||||
timeout: '10m'
|
|
||||||
|
|
||||||
- name: Upload Trivy scan results to GitHub Security tab
|
|
||||||
if: github.event_name != 'pull_request' && steps.login-ghcr.outcome == 'success'
|
|
||||||
uses: github/codeql-action/upload-sarif@v4
|
|
||||||
with:
|
|
||||||
sarif_file: 'trivy-frontend.sarif'
|
|
||||||
category: 'frontend-vulnerabilities'
|
|
||||||
|
|
||||||
summary:
|
summary:
|
||||||
needs: [build-backend, merge-backend, build-frontend, merge-frontend]
|
needs: [build-backend, merge-backend, build-frontend, merge-frontend]
|
||||||
if: always()
|
if: always()
|
||||||
|
|||||||
Reference in New Issue
Block a user