Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 38 additions & 5 deletions .github/workflows/attest-selftest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,23 @@
# the input validation for every subject kind, but validation only -- no
# signing branch.
#
# The run signs under a NON-RELEASE identity: the caller ref is a branch, so the
# Fulcio SAN carries `@refs/heads/...` and attest.yml logs a loud non-production
# warning. It cannot be mistaken for, or verified as, a release attestation.
# The run signs under a NON-RELEASE identity: the smoke job's ref guard holds
# github.ref to refs/heads/main, so the Fulcio SAN carries `@refs/heads/main`
# and attest.yml logs a loud non-production warning. It cannot be mistaken for,
# or verified as, a release attestation. That guard is load-bearing — see the
# smoke job comment — because workflow_dispatch accepts a tag as its ref.
#
# Rollout: this file on `main` is not what a dispatch at an older `v*` tag runs.
# Tags cut before the ref guard (`v0.2.0`, `v0.2.0-rc.1`, `v0.2.0-rc.2`) still
# ship the repository-only gate; see RELEASE.md "Existing-tag attest-selftest
# rollout". Do not treat merge of this guard as closing that path on those refs.
#
# Repository setting: Attest Self-Test is disabled at the Actions workflow level
# (`state: disabled_manually`) so workflow_dispatch is refused at every ref —
# including those older tags — until the v0.2.0 series is out of use. Re-enabling
# this workflow reopens the old-tag path; only re-enable for a maintainer smoke
# run from refs/heads/main, then disable again. A dispatch does nothing while
# the workflow stays disabled.

name: Attest Self-Test

Expand Down Expand Up @@ -68,7 +82,19 @@ concurrency:
jobs:
smoke:
name: Sign a non-release image through attest.yml
if: github.repository == 'NVIDIA/cluster-readiness-engine'
# The ref guard is load-bearing, not hygiene. workflow_dispatch accepts a
# TAG as its ref, and this workflow always passes allow_untagged: true.
# Dispatched from refs/tags/v*, CALLER_REF in attest.yml is the tag, so the
# non-tag refusal never fires, the non-production warning never prints, and
# Fulcio mints attest.yml@refs/tags/v* — the exact identity SECURITY.md
# tells users proves an official release — for whatever image_tag and
# expected_digest the dispatch supplied. The v* tag ruleset does not cover
# this: minting that identity creates no tag and moves no tag. allow_untagged
# cannot prevent it either; it only relaxes a check, it does not influence
# the certificate. Match publish.yml: hold the caller to refs/heads/main.
if: >-
github.repository == 'NVIDIA/cluster-readiness-engine'
&& github.ref == 'refs/heads/main'
permissions:
contents: read
packages: write
Expand All @@ -89,7 +115,14 @@ jobs:
report:
name: Report outcome
needs: [smoke]
if: always() && github.repository == 'NVIDIA/cluster-readiness-engine'
# Carries the same ref guard as smoke. Without it a workflow_dispatch from a
# non-main ref skips smoke — which is correct, that is what the guard is for
# — and then this job still runs on always() and treats the skip as a failed
# caller gate, turning a deliberate no-op into a red run.
if: >-
always()
&& github.repository == 'NVIDIA/cluster-readiness-engine'
&& github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
timeout-minutes: 5
permissions: {}
Expand Down
14 changes: 13 additions & 1 deletion .github/workflows/attest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,19 @@ jobs:
# Release attestations come from tags. A non-tag run is permitted only
# when the caller says so explicitly, so a test run cannot quietly
# produce something shaped like a release attestation.
if [[ "${CALLER_REF}" != refs/tags/v* ]]; then
#
# The converse is also refused: allow_untagged: true on a v* tag is
# contradictory. Callers that intend a non-production run must not
# reach here on a release ref (see attest-selftest.yml / publish.yml
# ref guards); callers that intend a release run must not pass the
# escape hatch. Without this, a future caller that forgets its own
# ref guard and passes allow_untagged: true from refs/tags/v* would
# mint the release identity while believing it asked for a test run.
if [[ "${CALLER_REF}" == refs/tags/v* ]]; then
if [[ "${IN_ALLOW_UNTAGGED}" == "true" ]]; then
fail "allow_untagged: true is contradictory on release ref ${CALLER_REF}; refuse rather than mint the release identity for a caller that claimed a non-production run"
fi
else
if [[ "${IN_ALLOW_UNTAGGED}" != "true" ]]; then
fail "attest.yml refuses to run on ${CALLER_REF}; pass allow_untagged: true for a non-production test run"
fi
Expand Down
31 changes: 31 additions & 0 deletions RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,37 @@ if nothing published, commit your work, and tag again.
**`releases/latest` returns 404.** No stable release exists yet. Use an explicit version
in the download URL.

## Existing-tag attest-selftest rollout

`attest-selftest.yml` on `main` holds `github.ref` to `refs/heads/main`, so a
`workflow_dispatch` at a `v*` ref cannot reach `attest.yml` through that caller.
GitHub selects workflow files from the ref you dispatch at, not from `main`, so
**tags that still contain the pre-fix workflow remain reachable.**

Affected published tags today: `v0.2.0`, `v0.2.0-rc.1`, `v0.2.0-rc.2` (same
`attest-selftest.yml` blob; repository gate only, `allow_untagged: true`). Older
tags without that workflow are out of scope for this path.

Merging the `main` fix does **not** close [#340](https://github.com/NVIDIA/cluster-readiness-engine/issues/340)
for those refs. Close the residual gap with both of the following before treating
the trust gap as closed:

1. **Operational mitigation (immediate).** *Attest Self-Test* is disabled at the
repository Actions level (`state: disabled_manually`), so GitHub refuses
`workflow_dispatch` on it at every ref — including existing vulnerable tags —
independently of this merge. That setting is not visible to tests: if someone
re-enables the workflow for a smoke run and leaves it on, the old-tag path
reopens until the `v0.2.0` series is out of use. Re-enable only for a
maintainer smoke run from `main`, then disable again. Do not dispatch at a
`v*` ref. The `v*` tag ruleset does not block this path.
2. **Next release (durable for new tags).** Cut the next `v*` release from
`main` after the fix lands. New tags carry the guarded workflow. Do not move
or rewrite existing tags to pick up the fix.

The acceptance criterion "dispatching at a `v*` ref does not reach `attest.yml`"
applies to refs that contain the fix; existing vulnerable tags need the
mitigation above.

## See also

- [CONTRIBUTING.md](CONTRIBUTING.md) — how to get a change into `main` before it ships
Expand Down
15 changes: 15 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,21 @@ We credit reporters of confirmed vulnerabilities in the release notes of the fix

Use `--certificate-identity`, not `--certificate-identity-regexp`. An identity that names no workflow and no ref also accepts images built from branches, which are not releases and are labelled non-production when they are signed.

The release signing identity is minted only when `attest.yml` runs on a `refs/tags/v*`
caller ref. On `main`, every `workflow_dispatch` caller of that workflow carries a
ref guard (or `release.yml`'s `GITHUB_REF` check) so a dispatch at a tag cannot reach
the attestor through those callers. **Existing release tags cut before that guard
landed — notably `v0.2.0`, `v0.2.0-rc.1`, and `v0.2.0-rc.2` — still ship the older
`attest-selftest.yml`, which gates on repository alone and passes `allow_untagged:
true`.** A dispatch at one of those refs uses the workflow files *on that tag*, not
the fixed copies on `main`, and can still mint `attest.yml@refs/tags/<that-tag>`.
Tag protection / the `v*` ruleset does not cover this path (no tag is created or
moved). Merging the `main` fix alone does not close [#340](https://github.com/NVIDIA/cluster-readiness-engine/issues/340)
for those refs; the residual control is the repository-level disable of *Attest
Self-Test* documented in [RELEASE.md](RELEASE.md#existing-tag-attest-selftest-rollout)
(re-enabling reopens the old-tag path).


Retrieve the provenance with `cosign verify-attestation --type slsaprovenance1` against the index digest, and a platform's SBOM with `--type cyclonedx` against that platform's manifest digest (`crane digest --platform linux/amd64 "${IMAGE}:${TAG}"`).

- CLI binaries, the installer and the SBOMs are each signed, and every release asset ships with a detached Sigstore bundle (`<asset>.sigstore.json`) verified under the same identity as the image:
Expand Down
Loading