mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-25 14:30:43 +01:00
release: digest-bound provenance and SBOM attestations for container images (#12024)
* release: emit digest-bound provenance and SBOM attestations Per-platform builds now record SLSA provenance at mode=max and a BuildKit SBOM, so the builder identity and resolved dependencies are captured instead of the empty predicate the 4.48 build record carried. After the multi-arch index is assembled and signed, the merge job publishes GitHub artifact attestations bound to the merged digest and a syft SPDX SBOM of the image, giving digest-pinning consumers a verifiable record on the exact index they pull. * release: keep unfixed findings in the trivy SARIF upload ignore-unfixed dropped every finding without an available patch, so the uploaded report understated the image's exposure. The scan stays report-only; only the completeness of what is uploaded changes. * release: generate and attest SBOMs per platform digest An index-wide syft scan only describes its default platform, so the SBOM attested to the merged multi-arch digest misrepresented every other manifest. Scan and attest each platform digest instead, which also drops the sbom-action upload defaults that raced release-asset permissions and artifact names across the merge matrix. * release: resolve per-platform index to its image manifest before syft/cosign push-by-digest records point at OCI indexes (image + attestation manifests), so scanning or attesting the index digest picks the runner's platform and fails outright for the other arches under set -e. Resolve the non-attestation manifest inside each index and bind the SBOM attestation to the image digest a same-platform pull of the merged index actually returns. --------- Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
be081b49b7
commit
689ea6ea3e
1 file changed
+49
-3
@@ -271,8 +271,11 @@ jobs:
|
|||||||
file: ${{ matrix.dockerfile }}
|
file: ${{ matrix.dockerfile }}
|
||||||
platforms: ${{ matrix.platform }}
|
platforms: ${{ matrix.platform }}
|
||||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||||
# Flat single-platform manifest so imagetools create assembles cleanly.
|
# mode=max keeps resolvedDependencies complete and records the
|
||||||
provenance: false
|
# builder identity; attestations ride inside each pushed index and
|
||||||
|
# survive the imagetools merge into the multi-arch manifest.
|
||||||
|
provenance: mode=max
|
||||||
|
sbom: true
|
||||||
outputs: type=image,name=${{ env.IMAGE }},push-by-digest=true,name-canonical=true,push=true
|
outputs: type=image,name=${{ env.IMAGE }},push-by-digest=true,name-canonical=true,push=true
|
||||||
cache-from: type=gha,scope=${{ matrix.variant }}-${{ matrix.arch }}
|
cache-from: type=gha,scope=${{ matrix.variant }}-${{ matrix.arch }}
|
||||||
# max only for rocksdb: its RocksDB compile is sha-independent and worth
|
# max only for rocksdb: its RocksDB compile is sha-independent and worth
|
||||||
@@ -308,6 +311,8 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
id-token: write
|
id-token: write
|
||||||
|
attestations: write
|
||||||
|
packages: write
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -415,6 +420,45 @@ jobs:
|
|||||||
${{ env.IMAGE }}@${{ steps.manifest.outputs.digest }}
|
${{ env.IMAGE }}@${{ steps.manifest.outputs.digest }}
|
||||||
chrislusf/seaweedfs@${{ steps.manifest.outputs.digest }}
|
chrislusf/seaweedfs@${{ steps.manifest.outputs.digest }}
|
||||||
|
|
||||||
|
# Attest the merged multi-arch digest itself, so digest-pinning consumers
|
||||||
|
# can verify provenance against the exact index they pull. Both steps
|
||||||
|
# reuse the GHCR login above and push as OCI referrers.
|
||||||
|
- name: Attest build provenance for ${{ matrix.variant }}
|
||||||
|
if: github.event_name != 'workflow_dispatch' || github.event.inputs.variant == 'all' || github.event.inputs.variant == matrix.variant
|
||||||
|
uses: actions/attest-build-provenance@v4
|
||||||
|
with:
|
||||||
|
subject-name: ${{ env.IMAGE }}
|
||||||
|
subject-digest: ${{ steps.manifest.outputs.digest }}
|
||||||
|
push-to-registry: true
|
||||||
|
|
||||||
|
# SBOMs are generated and attested per platform manifest: an index-wide
|
||||||
|
# scan only describes syft's default platform, while the manifests it
|
||||||
|
# merges differ (some ship the real binary, others placeholders), so the
|
||||||
|
# SBOM has to be bound to the platform digest a consumer actually pulls.
|
||||||
|
- name: Install syft
|
||||||
|
if: github.event_name != 'workflow_dispatch' || github.event.inputs.variant == 'all' || github.event.inputs.variant == matrix.variant
|
||||||
|
uses: anchore/sbom-action/download-syft@v0
|
||||||
|
|
||||||
|
- name: Attest per-platform SBOMs for ${{ matrix.variant }}
|
||||||
|
if: github.event_name != 'workflow_dispatch' || github.event.inputs.variant == 'all' || github.event.inputs.variant == matrix.variant
|
||||||
|
working-directory: /tmp/digests
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
for f in *; do
|
||||||
|
# Each saved digest is an OCI index (image + attestation manifests);
|
||||||
|
# scan and attest the image manifest a same-platform pull resolves.
|
||||||
|
ref="${{ env.IMAGE }}@sha256:${f}"
|
||||||
|
image=$(crane manifest "$ref" | jq -r '
|
||||||
|
[.manifests[]? | select((.annotations["vnd.docker.reference.type"] // "") != "attestation-manifest"
|
||||||
|
and (.platform.os // "unknown") != "unknown") | .digest][0] // empty')
|
||||||
|
ref="${{ env.IMAGE }}@${image:-sha256:${f}}"
|
||||||
|
syft "registry:${ref}" -o "spdx-json=/tmp/sbom-${f}.spdx.json"
|
||||||
|
cosign attest --yes \
|
||||||
|
--predicate "/tmp/sbom-${f}.spdx.json" \
|
||||||
|
--type spdxjson \
|
||||||
|
"$ref"
|
||||||
|
done
|
||||||
|
|
||||||
# Report-only trivy scan: uploads fixable HIGH/CRITICAL findings to GitHub
|
# Report-only trivy scan: uploads fixable HIGH/CRITICAL findings to GitHub
|
||||||
# Security for visibility, but never blocks the release. Releases (including
|
# Security for visibility, but never blocks the release. Releases (including
|
||||||
# `latest`) ship regardless — vulnerabilities are tracked, not gated, since
|
# `latest`) ship regardless — vulnerabilities are tracked, not gated, since
|
||||||
@@ -453,7 +497,9 @@ jobs:
|
|||||||
scanners: vuln
|
scanners: vuln
|
||||||
vuln-type: os,library
|
vuln-type: os,library
|
||||||
severity: HIGH,CRITICAL
|
severity: HIGH,CRITICAL
|
||||||
ignore-unfixed: true
|
# Keep unfixed findings in the report so digest-pinning consumers see
|
||||||
|
# the complete picture, not just what a patch could resolve.
|
||||||
|
ignore-unfixed: false
|
||||||
limit-severities-for-sarif: true
|
limit-severities-for-sarif: true
|
||||||
format: sarif
|
format: sarif
|
||||||
output: trivy-results.sarif
|
output: trivy-results.sarif
|
||||||
|
|||||||
Reference in new issue
Block a user