diff --git a/.github/actions/attach-scan-report/action.yml b/.github/actions/attach-scan-report/action.yml index c1c1b1f..1f8f396 100644 --- a/.github/actions/attach-scan-report/action.yml +++ b/.github/actions/attach-scan-report/action.yml @@ -1,18 +1,24 @@ # Composite action: attach-scan-report # -# Attach an empty OCI scan-report referrer to a promoted image with `oras -# attach`. The referrer records how and when the image was cleared: its scan -# method, source, tag, threshold, excepted CVEs, and scanner name/version. It -# carries annotations only (no layer blobs); its subject is the promoted image. +# Attach an OCI vulnerability-attestation referrer to a promoted image with +# `oras attach`. The referrer carries the scan result as an in-toto +# vulnerability attestation (a Cosign vuln scan record, predicate type +# https://cosign.sigstore.dev/attestation/vuln/v1) in its payload layer, and +# records how and when the image was cleared as manifest annotations: its scan +# method, source, tag, threshold, excepted CVEs, and scanner name/version. Its +# subject is the promoted image. # # Terminology: see docs/reference/workflow-actions.md. name: attach-scan-report -description: Attach an empty OCI scan-report referrer to a promoted image. +description: Attach an OCI vulnerability-attestation referrer to a promoted image. inputs: image-ref: description: "Promoted image to attach the referrer to (repo:tag)." required: true + attestation-file: + description: "Path to the in-toto vulnerability attestation to attach as the referrer payload." + required: true source-repo: description: "Repository the image was promoted from (recorded in the referrer)." required: true @@ -61,6 +67,7 @@ runs: shell: bash env: IMAGE_REF: "${{ inputs.image-ref }}" + ATTESTATION_FILE: "${{ inputs.attestation-file }}" SOURCE_REPO: "${{ inputs.source-repo }}" TAG: "${{ inputs.tag }}" THRESHOLD: "${{ inputs.threshold }}" @@ -79,8 +86,14 @@ runs: created="$(date -u +%Y-%m-%dT%H:%M:%SZ)" echo "Attaching scan report to ${IMAGE_REF}" + if [ ! -s "${ATTESTATION_FILE}" ]; then + echo "::error::attestation-file '${ATTESTATION_FILE}' is missing or empty" >&2 + exit 1 + fi + args=( - --artifact-type application/vnd.cssc.scan-report.v1+json + --artifact-type application/vnd.in-toto+json + --annotation "in-toto.io/predicate-type=https://cosign.sigstore.dev/attestation/vuln/v1" --annotation "org.opencontainers.image.created=${created}" --annotation "com.cssc.scan.source=${SOURCE_REPO}" --annotation "com.cssc.scan.tag=${TAG}" @@ -111,4 +124,5 @@ runs: ) fi - oras attach "${args[@]}" "${IMAGE_REF}" + oras attach "${args[@]}" --disable-path-validation \ + "${IMAGE_REF}" "${ATTESTATION_FILE}:application/vnd.in-toto+json" diff --git a/.github/actions/scan-image/action.yml b/.github/actions/scan-image/action.yml index 92c8f72..e999e62 100644 --- a/.github/actions/scan-image/action.yml +++ b/.github/actions/scan-image/action.yml @@ -1,9 +1,11 @@ # Composite action: scan-image # # Scan a single container image's filesystem with `trivy image` and write the -# JSON report plus the de-duplicated list of vulnerability IDs found at or above -# the requested severity set. Suited to images that carry package-manager -# metadata; for distroless / hardened images use scan-sbom instead. +# JSON report, the de-duplicated list of vulnerability IDs found at or above the +# requested severity set, and an in-toto vulnerability attestation (a Cosign +# vuln scan record wrapped in an in-toto Statement) for the scanned image. +# Suited to images that carry package-manager metadata; for distroless / +# hardened images use scan-sbom instead. # # Terminology: see docs/reference/workflow-actions.md. name: scan-image @@ -27,6 +29,9 @@ outputs: blocking-ids-path: description: "Path to the newline-separated, de-duplicated list of vulnerability IDs found." value: ${{ steps.scan.outputs.blocking-ids-path }} + attestation-path: + description: "Path to the in-toto vulnerability attestation (Cosign vuln predicate) for the scanned image." + value: ${{ steps.scan.outputs.attestation-path }} runs: using: composite @@ -45,6 +50,7 @@ runs: mkdir -p "${OUTPUT_DIR}" report="${OUTPUT_DIR}/report.json" ids="${OUTPUT_DIR}/blocking-ids.txt" + attestation="${OUTPUT_DIR}/vuln-attestation.json" echo "::group::Scanning ${IMAGE_REF}" trivy image --quiet --scanners vuln --severity "${SEVERITIES}" \ @@ -54,5 +60,27 @@ runs: jq -r '[.Results[]?.Vulnerabilities[]?.VulnerabilityID] | unique | .[]' \ "${report}" | sort -u > "${ids}" + # Build a vulnerability attestation from the report we already have: a + # Cosign vulnerability scan record (`trivy convert`, no re-scan) wrapped + # in an in-toto Statement whose subject is the scanned image digest. + vuln="${OUTPUT_DIR}/cosign-vuln.json" + trivy convert --quiet --format cosign-vuln --output "${vuln}" "${report}" + + ref_digest="$(crane digest "${IMAGE_REF}")" + algo="${ref_digest%%:*}" + hexv="${ref_digest#*:}" + jq -n \ + --arg name "${IMAGE_REF%:*}" \ + --arg algo "${algo}" \ + --arg hexv "${hexv}" \ + --slurpfile pred "${vuln}" \ + '{ + _type: "https://in-toto.io/Statement/v1", + subject: [ { name: $name, digest: { ($algo): $hexv } } ], + predicateType: "https://cosign.sigstore.dev/attestation/vuln/v1", + predicate: $pred[0] + }' > "${attestation}" + echo "report-path=${report}" >> "${GITHUB_OUTPUT}" echo "blocking-ids-path=${ids}" >> "${GITHUB_OUTPUT}" + echo "attestation-path=${attestation}" >> "${GITHUB_OUTPUT}" diff --git a/.github/actions/scan-sbom/action.yml b/.github/actions/scan-sbom/action.yml index 5bb235b..61ac6fb 100644 --- a/.github/actions/scan-sbom/action.yml +++ b/.github/actions/scan-sbom/action.yml @@ -9,7 +9,9 @@ # # Emits the union of vulnerability IDs across all platforms, the number of # platforms whose SBOM could not be found (a blocking condition), the platform -# count, and a Markdown fragment with the per-platform breakdown. +# count, a Markdown fragment with the per-platform breakdown, and an image-level +# in-toto vulnerability attestation (a Cosign vuln scan record built from the +# merged per-platform findings). # # Terminology: see docs/reference/workflow-actions.md. name: scan-sbom @@ -48,6 +50,9 @@ outputs: platform-detail-path: description: "Path to a Markdown fragment with the per-platform findings table rows." value: ${{ steps.scan.outputs.platform-detail-path }} + attestation-path: + description: "Path to the image-level in-toto vulnerability attestation (Cosign vuln predicate) built from the merged per-platform findings." + value: ${{ steps.scan.outputs.attestation-path }} runs: using: composite @@ -120,6 +125,8 @@ runs: : > "${tag_blocking}" plat_detail="${OUTPUT_DIR}/platdetail.md" : > "${plat_detail}" + reports_list="${OUTPUT_DIR}/reports.txt" # per-platform report paths + : > "${reports_list}" missing_sbom=0 platform_count=0 @@ -139,6 +146,7 @@ runs: report="${OUTPUT_DIR}/report-$(printf '%s' "${plat_name}" | tr '/' '_').json" trivy sbom --quiet --scanners vuln --severity "${SEVERITIES}" \ --format json --output "${report}" "${sbom_file}" + printf '%s\n' "${report}" >> "${reports_list}" plat_ids="${OUTPUT_DIR}/ids.txt" jq -r '[.Results[]?.Vulnerabilities[]?.VulnerabilityID] | unique | .[]' \ @@ -163,7 +171,44 @@ runs: # platforms is reported once (matches the documented output semantics). sort -u "${tag_blocking}" -o "${tag_blocking}" + # Build one image-level vulnerability attestation: merge the per-platform + # Trivy JSON reports into a single report, convert it to a Cosign vuln + # scan record (no re-scan), and wrap it in an in-toto Statement whose + # subject is the image (index) digest. + merged="${OUTPUT_DIR}/merged-report.json" + attestation="${OUTPUT_DIR}/vuln-attestation.json" + vuln="${OUTPUT_DIR}/cosign-vuln.json" + if [ -s "${reports_list}" ]; then + # shellcheck disable=SC2046 + jq -s ' + { SchemaVersion: (.[0].SchemaVersion // 2), + ArtifactName: (.[0].ArtifactName // ""), + ArtifactType: (.[0].ArtifactType // ""), + Metadata: (.[0].Metadata // {}), + Results: ( map(.Results // []) | add | unique ) } + ' $(tr '\n' ' ' < "${reports_list}") > "${merged}" + else + jq -n '{SchemaVersion: 2, Results: []}' > "${merged}" + fi + trivy convert --quiet --format cosign-vuln --output "${vuln}" "${merged}" + + ref_digest="$(crane digest "${src}")" + algo="${ref_digest%%:*}" + hexv="${ref_digest#*:}" + jq -n \ + --arg name "${src%:*}" \ + --arg algo "${algo}" \ + --arg hexv "${hexv}" \ + --slurpfile pred "${vuln}" \ + '{ + _type: "https://in-toto.io/Statement/v1", + subject: [ { name: $name, digest: { ($algo): $hexv } } ], + predicateType: "https://cosign.sigstore.dev/attestation/vuln/v1", + predicate: $pred[0] + }' > "${attestation}" + echo "missing-sbom-count=${missing_sbom}" >> "${GITHUB_OUTPUT}" echo "platform-count=${platform_count}" >> "${GITHUB_OUTPUT}" echo "blocking-ids-path=${tag_blocking}" >> "${GITHUB_OUTPUT}" echo "platform-detail-path=${plat_detail}" >> "${GITHUB_OUTPUT}" + echo "attestation-path=${attestation}" >> "${GITHUB_OUTPUT}" diff --git a/.github/workflows/_promote-from-quarantine-sbom.yml b/.github/workflows/_promote-from-quarantine-sbom.yml index c459c81..b060e83 100644 --- a/.github/workflows/_promote-from-quarantine-sbom.yml +++ b/.github/workflows/_promote-from-quarantine-sbom.yml @@ -246,6 +246,7 @@ jobs: uses: ./.github/actions/attach-scan-report with: image-ref: ${{ inputs.dest_repo }}:${{ matrix.tag }} + attestation-file: ${{ steps.scan.outputs.attestation-path }} source-repo: ${{ inputs.source_repo }} tag: ${{ matrix.tag }} threshold: ${{ inputs.severity_threshold }} diff --git a/.github/workflows/_promote-from-quarantine.yml b/.github/workflows/_promote-from-quarantine.yml index f77243d..f7f1034 100644 --- a/.github/workflows/_promote-from-quarantine.yml +++ b/.github/workflows/_promote-from-quarantine.yml @@ -241,6 +241,7 @@ jobs: uses: ./.github/actions/attach-scan-report with: image-ref: ${{ inputs.dest_repo }}:${{ matrix.tag }} + attestation-file: ${{ steps.scan.outputs.attestation-path }} source-repo: ${{ inputs.source_repo }} tag: ${{ matrix.tag }} threshold: ${{ inputs.severity_threshold }} diff --git a/docs/architecture/catalog/README.md b/docs/architecture/catalog/README.md index 6b39023..d257719 100644 --- a/docs/architecture/catalog/README.md +++ b/docs/architecture/catalog/README.md @@ -13,3 +13,7 @@ on a vulnerability policy, and promoting the ones that pass into `golden/*`. - [Promotion override approval](promote-from-quarantine-override-approval.md) — the human-in-the-loop approve/deny path for promoting an image that a policy would otherwise block. +- [Vulnerability-attestation scan report](promote-from-quarantine-vuln-attestation.md) + — replacing the empty scan-report referrer with a content-bearing in-toto + vulnerability attestation while keeping the `com.cssc.scan.*` summary + annotations. (Also serves **Supply Chain Observability**.) diff --git a/docs/architecture/catalog/promote-from-quarantine-vuln-attestation.md b/docs/architecture/catalog/promote-from-quarantine-vuln-attestation.md new file mode 100644 index 0000000..6cb50c9 --- /dev/null +++ b/docs/architecture/catalog/promote-from-quarantine-vuln-attestation.md @@ -0,0 +1,205 @@ +# Vulnerability-attestation scan report + +> **Status: implemented.** The promote-from-quarantine scan-report referrer is +> a content-bearing in-toto vulnerability attestation (see the +> [scan-image](../../../.github/actions/scan-image/action.yml), +> [scan-sbom](../../../.github/actions/scan-sbom/action.yml), and +> [attach-scan-report](../../../.github/actions/attach-scan-report/action.yml) +> actions). + +This document describes replacing the **empty** OCI scan-report referrer that +the promote-from-quarantine workflows attach today with a **content-bearing +vulnerability attestation** — an in-toto statement wrapping Trivy's own +vulnerability scan record — while keeping the human-readable +`com.cssc.scan.*` summary annotations on that same referrer manifest. + +For the workflows this changes, see +[promote-from-quarantine workflows](promote-from-quarantine-workflows.md). For +the referrer conventions this follows, see +[image attestations](../../reference/image-attestations.md) and the +[action catalogue](../../reference/workflow-actions.md). + +## Motivation + +Every image promoted into `golden/` (or `base/hardened/`) gets an +OCI referrer recording *how and when it was cleared*. Today that referrer is an +**empty artifact** — a manifest with the standard empty config and **no layer +blobs** — carrying the decision only as annotations +(`com.cssc.scan.threshold`, `com.cssc.scan.exceptions`, scanner name/version, +etc.). See the +[scan-report referrer artifact](promote-from-quarantine-workflows.md#scan-report-referrer-artifact). + +The annotations answer *"did it pass, and under what policy?"* but the actual +finding set — which CVEs Trivy saw, in which packages, at which versions — is +discarded after the run. A consumer auditing a golden image cannot retrieve the +evidence behind the promotion decision; they can only see the summary. + +The rest of this framework already stores richer supply-chain evidence as +**in-toto attestation referrers**: the SBOM and provenance attestations on the +CSSC Dashboard images are `application/vnd.in-toto+json` referrers with an +`in-toto.io/predicate-type` annotation (see +[image attestations](../../reference/image-attestations.md)). The scan report is +the natural next attestation to store the same way. + +## Goals + +- Attach the **full vulnerability finding set** to each promoted image as a + retrievable OCI referrer, not just a pass/fail summary. +- Store it as a standard **in-toto attestation** so it is consistent with the + SBOM and provenance referrers already used in this repo and consumable by + generic tooling (`oras discover`, `cosign verify-attestation`, policy engines). +- **Keep the existing `com.cssc.scan.*` annotations** on the referrer manifest so + current consumers that read annotations (and the dashboard) keep working, and + a quick summary is still available without pulling the payload blob. +- Cover both scan paths: the filesystem scan (`_promote-from-quarantine.yml`) + and the SBOM-based scan for hardened images + (`_promote-from-quarantine-sbom.yml`). + +## Non-goals + +- **Signing.** This records the report as an unsigned attestation, consistent + with the current "no signing" scope. Signing (e.g. `cosign attest`) can be + layered on later without changing the payload. +- **Changing the gate.** The promote/block decision, severity floor, and + exception handling are unchanged. This only changes what is *recorded* for a + promoted image. +- **Per-platform vulnerability attestations for the filesystem path.** The + filesystem scan (`trivy image`) produces one report for the image; the + attestation is attached to the promoted tag as today. (The SBOM path already + reasons per platform; see below.) + +## Options considered + +### Payload format + +| Option | Predicate type | Pros | Cons | +| ------ | -------------- | ---- | ---- | +| **A. Cosign vuln record in an in-toto statement** *(recommended)* | `https://cosign.sigstore.dev/attestation/vuln/v1` | Trivy emits it natively (`--format cosign-vuln`); purpose-built to be an attestation predicate; verifiable with `cosign verify-attestation --type vuln`. | Predicate is a scan *record* (metadata + scanner), not the full per-CVE table. | +| B. Raw Trivy JSON as the payload blob | *(none — proprietary)* | Full per-CVE detail; already produced for gating. | Not an in-toto attestation; Trivy-proprietary schema; not verifiable as an attestation. | +| C. In-toto native vuln predicate | `https://in-toto.io/attestation/vulns/v0.1` | in-toto-native; scanner-agnostic. | Trivy does not emit this directly; would require hand-assembly of the predicate. | + +**Recommendation: Option A** — attach the cosign-vuln in-toto Statement as the +single referrer payload. The cosign-vuln predicate gives a standard, verifiable +attestation and embeds the per-package finding set from Trivy's report, so it is +both a real attestation and retrievable evidence. + +> **Optional future enhancement:** additionally embed the full +> `trivy --format json` report as a second layer (or an annotation-referenced +> companion) for consumers that want Trivy's native schema. This is **not** +> implemented — the current referrer carries only the cosign-vuln statement. + +### Referrer shape + +`oras attach` produces a manifest that can carry **both** a payload layer **and** +manifest annotations — these are not mutually exclusive. So the referrer becomes: + +- `artifactType`: `application/vnd.in-toto+json` (matching the SBOM/provenance + referrers) instead of the current `application/vnd.cssc.scan-report.v1+json`. +- `layers[0]`: the in-toto vulnerability statement blob + (`application/vnd.in-toto+json`). +- `annotations`: the existing `com.cssc.scan.*` keys **plus** + `in-toto.io/predicate-type=https://cosign.sigstore.dev/attestation/vuln/v1`. +- `subject`: the promoted image (unchanged). + +This is fully backward compatible for annotation readers and adds the payload. + +## Design + +### Scan (`scan-image`, `scan-sbom`) + +`scan-image` already runs `trivy image --format json` for the gate. It gains a +second Trivy invocation (or a `trivy convert` of the JSON) producing the +**cosign-vuln** record, and wraps it in an in-toto `Statement` whose `subject` +is the scanned image digest: + +```json +{ + "_type": "https://in-toto.io/Statement/v1", + "subject": [{ "name": "", "digest": { "sha256": "" } }], + "predicateType": "https://cosign.sigstore.dev/attestation/vuln/v1", + "predicate": { /* trivy --format cosign-vuln output */ } +} +``` + +The action emits the path to this statement file as a new output +(`attestation-path`) alongside the existing `report-path` and +`blocking-ids-path`. `scan-sbom` does the same per platform and, for the +image-level attestation, over the unioned finding set. + +### Attach (`attach-scan-report`) + +`attach-scan-report` stops attaching an empty artifact and instead attaches the +statement file as the payload layer while keeping every existing annotation: + +```bash +oras attach \ + --artifact-type application/vnd.in-toto+json \ + --annotation "in-toto.io/predicate-type=https://cosign.sigstore.dev/attestation/vuln/v1" \ + --annotation "org.opencontainers.image.created=${created}" \ + --annotation "com.cssc.scan.source=${SOURCE_REPO}" \ + --annotation "com.cssc.scan.tag=${TAG}" \ + --annotation "com.cssc.scan.threshold=${THRESHOLD}" \ + --annotation "com.cssc.scan.exceptions=${EXCEPTED_STR}" \ + --annotation "com.cssc.scan.scanner=trivy" \ + --annotation "com.cssc.scan.scanner-version=${SCANNER_VERSION}" \ + --annotation "com.cssc.scan.method=${METHOD}" \ + --disable-path-validation \ + "${IMAGE_REF}" \ + "${STATEMENT_FILE}:application/vnd.in-toto+json" +``` + +`--disable-path-validation` is required for the absolute temp-file path (the +same oras path-validation behaviour already handled in the build workflow's +referrer step). The override annotations (`com.cssc.scan.override*`) are +unchanged. A new required input carries the statement file path; the `method`, +`source-repo`, `tag`, etc. inputs are unchanged. + +### Workflow wiring + +Both reusable workflows pass the new `scan-*` output into +`attach-scan-report`. No new inputs, secrets, or permissions are needed — the +referrer is still pushed with the built-in `GITHUB_TOKEN` (`packages: write`). +The SBOM path continues to promote with `oras cp -r` so the attestation travels +with the image. + +### Control-flow change + +```mermaid +flowchart LR + A[scan-image / scan-sbom] --> B[trivy json report - gate] + A --> C[trivy cosign-vuln - wrap in in-toto Statement] + B --> D[evaluate-findings] + D -->|promote| E[mirror-image] + E --> F[attach-scan-report] + C --> F + F --> G["referrer: in-toto vuln attestation + com.cssc.scan.* annotations"] +``` + +## Compatibility and migration + +- **Annotation readers** (the CSSC Dashboard, `oras discover` consumers) see the + same `com.cssc.scan.*` keys and keep working. +- **Artifact type changes** from `application/vnd.cssc.scan-report.v1+json` to + `application/vnd.in-toto+json`. Any consumer filtering referrers by the old + artifact type must also match the new predicate-type annotation. Already- + promoted images keep their old empty referrers; only images promoted after the + change carry the attestation. +- The reference docs + ([image-attestations.md](../../reference/image-attestations.md), + [image-annotations.md](../../reference/image-annotations.md), + [workflow-actions.md](../../reference/workflow-actions.md)) and the + [promote-from-quarantine workflows](promote-from-quarantine-workflows.md) doc + are updated to describe the payload-bearing referrer. + +## Verification + +- Retrieve and inspect the attestation: + + ```bash + oras discover --format tree ghcr.io//golden/: + oras pull -o out ghcr.io//golden/@ + jq . out/*.json + ``` + +- Confirm the manifest still carries the `com.cssc.scan.*` annotations + (`oras manifest fetch --descriptor` / `crane manifest`). diff --git a/docs/architecture/catalog/promote-from-quarantine-workflows.md b/docs/architecture/catalog/promote-from-quarantine-workflows.md index 63b0806..5211650 100644 --- a/docs/architecture/catalog/promote-from-quarantine-workflows.md +++ b/docs/architecture/catalog/promote-from-quarantine-workflows.md @@ -227,7 +227,7 @@ promoted into a dedicated `base/hardened/` namespace rather than the | **GitHub Actions** | Orchestration: scheduling, manual dispatch, reusable-workflow composition, concurrency, and job summaries. | | **[Trivy](https://github.com/aquasecurity/trivy)** | Vulnerability scanner. Scans each remote image and emits JSON filtered to the configured severity floor. | | **[`crane`](https://github.com/google/go-containerregistry/blob/main/cmd/crane/README.md)** | Registry client: `crane ls` to enumerate tags and `crane copy` to promote images (preserving multi-architecture manifest lists). | -| **[`oras`](https://oras.land)** | Creates and attaches the empty scan-report referrer (`oras attach` with annotations only). | +| **[`oras`](https://oras.land)** | Builds and attaches the scan-report referrer — an in-toto vulnerability attestation payload plus summary annotations (`oras attach`). | | **`jq`** | Parses Trivy JSON and computes the blocking, excepted, and remaining CVE sets. | | **`GITHUB_TOKEN`** | Built-in token used to authenticate to GHCR for scan, copy, and attach (`packages: write`). | | **Optional `ghcr_delete_token` PAT** | Used only for deleting quarantine tags via the GitHub Packages REST API. | @@ -243,13 +243,18 @@ Key characteristics: ## Scan-report referrer artifact For every promoted image the workflow attaches an **OCI referrer artifact** to -the image in `golden/`. It is an *empty* artifact — a manifest with the -standard empty config descriptor and **no layer blobs** — created with -`oras attach` using annotations only. Its `subject` is the promoted image, so -registry clients (`oras discover`, `crane manifest`, etc.) can list it as a -referrer of the image. - -- **Artifact type:** `application/vnd.cssc.scan-report.v1+json` +the image in `golden/`. It carries the scan result as an **in-toto +vulnerability attestation** in its payload layer — a +[Cosign vulnerability scan record](https://trivy.dev/docs/latest/supply-chain/attestation/vuln/) +(produced by `trivy convert --format cosign-vuln`, no re-scan) wrapped in an +in-toto `Statement` — and records how/when the image was cleared as manifest +**annotations**. Its `subject` is the promoted image, so registry clients +(`oras discover`, `crane manifest`, etc.) can list it as a referrer of the +image; pulling the referrer yields the full finding set. + +- **Artifact type:** `application/vnd.in-toto+json` +- **Predicate type:** `https://cosign.sigstore.dev/attestation/vuln/v1` + (recorded in the `in-toto.io/predicate-type` annotation) | Annotation key | Example value | Meaning | | -------------- | ------------- | ------- | @@ -272,8 +277,9 @@ referrer of the image. - **Per-repository tag enumeration.** Every tag in the quarantine repository is scanned and gated independently in one run. - **Multi-architecture preservation** via `crane copy`. -- **Scan-report provenance.** Each promoted image gets an empty OCI referrer - artifact recording scan date, source, tag, threshold, excepted CVEs, and +- **Scan-report provenance.** Each promoted image gets an in-toto + vulnerability-attestation referrer carrying the full finding set, plus + annotations recording scan date, source, tag, threshold, excepted CVEs, and scanner name/version. - **Quarantine cleanup.** Promoted tags are deleted from quarantine when a delete token is configured (see below). @@ -305,11 +311,12 @@ The workflow therefore treats deletion as configurable: ### Not implemented (deliberately out of scope) -- **No signing.** Promoted images are not signed (e.g. cosign). The image-based - scanner (`_promote-from-quarantine.yml`) produces no SBOM/provenance; the SBOM-based - scanner (`_promote-from-quarantine-sbom.yml`) does not generate attestations but copies the - upstream's existing SBOM/provenance/VEX/signature referrers verbatim during - promotion. +- **No signing.** The vulnerability-attestation referrer is recorded but **not + signed** (e.g. with cosign); signing can be layered on later without changing + the payload. The image-based scanner (`_promote-from-quarantine.yml`) produces + no SBOM/provenance of its own; the SBOM-based scanner + (`_promote-from-quarantine-sbom.yml`) copies the upstream's existing + SBOM/provenance/VEX/signature referrers verbatim during promotion. - **No automatic remediation.** Blocked images are left in quarantine; the workflow does not patch, rebuild, or open tickets for them. - **No cross-scanner support.** Trivy is the only scanner; the referrer schema diff --git a/docs/reference/workflow-actions.md b/docs/reference/workflow-actions.md index 1a8e1f5..e481417 100644 --- a/docs/reference/workflow-actions.md +++ b/docs/reference/workflow-actions.md @@ -30,7 +30,7 @@ phrasings it replaces. | __scan-image__ | Scan one image filesystem with `trivy image`. | "scan images with Trivy" | | __scan-sbom__ | Scan one image's per-platform SBOM attestations with `trivy sbom`. | "scan platform SBOMs" | | __evaluate-findings__ | Apply the severity threshold + CVE exceptions to produce a gate decision. | "gate on scan findings" | -| __attach-scan-report__ | Attach the OCI scan-report referrer to a promoted image. | "attach scan-report attestation" | +| __attach-scan-report__ | Attach the OCI scan-report referrer — an in-toto vulnerability attestation payload plus summary annotations — to a promoted image. | "attach scan-report attestation" | | __attach-acquisition-provenance__ | Attach the acquisition-provenance in-toto referrer to a mirrored image. | "acquisition provenance" | | __delete-image__ | Delete one tag from a GHCR repository via the Packages API. | "delete promoted tags from quarantine" |