Skip to content
Draft
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
30 changes: 30 additions & 0 deletions .github/actions/read-attest-pins/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Single source for the verifier-side cosign/crane pins: the workflow_call
# defaults (and crane digest) already documented on attest.yml. Dependabot
# cannot raise those defaults; see .github/dependabot.yml.

name: Read attest.yml tool pins
description: >
Read the cosign and crane pins from attest.yml so verifier jobs use the
same versions the signer documents.

outputs:
cosign_version:
description: workflow_call default for cosign_version
value: ${{ steps.read.outputs.cosign_version }}
crane_version:
description: workflow_call default for crane_version
value: ${{ steps.read.outputs.crane_version }}
crane_sha256:
description: CRANE_SHA256 next to the crane install in attest.yml
value: ${{ steps.read.outputs.crane_sha256 }}

runs:
using: composite
steps:
- id: read
name: Parse attest.yml defaults
shell: bash
run: python3 "${GITHUB_ACTION_PATH}/read.py"
87 changes: 87 additions & 0 deletions .github/actions/read-attest-pins/read.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
"""Emit the signer tool pins from attest.yml.

Verifier jobs (release.yml verify-release, docs-verify.yml) must consume these
values rather than carrying their own copies. Dependabot cannot raise
workflow_call input defaults; reading the file is how the copies stay one pin.
"""

from __future__ import annotations

import os
import re
import sys
from pathlib import Path

VERSION_RE = re.compile(r"^v[0-9]+\.[0-9]+\.[0-9]+$")
SHA256_RE = re.compile(r"^[0-9a-f]{64}$")


def attest_path() -> Path:
here = Path(__file__).resolve()
# .github/actions/read-attest-pins/read.py -> .github/workflows/attest.yml
bundled = here.parents[2] / "workflows" / "attest.yml"
candidates = [bundled]
workspace = os.environ.get("GITHUB_WORKSPACE")
if workspace:
candidates.append(Path(workspace) / ".github/workflows/attest.yml")
candidates.append(Path(".github/workflows/attest.yml"))
for path in candidates:
if path.is_file():
return path
raise SystemExit(
"could not find .github/workflows/attest.yml; looked in: "
+ ", ".join(str(p) for p in candidates)
)


def input_default(text: str, name: str) -> str:
# workflow_call inputs are indented six spaces; their fields, eight.
# The job-output assignment `cosign_version: ${{ ... }}` has no `default:`.
match = re.search(
rf"(?m)^ {re.escape(name)}:\n(?: .*\n)*? default: ['\"]([^'\"]+)['\"]",
text,
)
if not match:
raise SystemExit(f"could not find workflow_call default for {name}")
return match.group(1)


def crane_sha256(text: str) -> str:
match = re.search(r"(?m)^ CRANE_SHA256: ([0-9a-f]{64})$", text)
if not match:
raise SystemExit("could not find CRANE_SHA256 in attest.yml")
return match.group(1)


def emit(key: str, value: str) -> None:
line = f"{key}={value}"
print(line)
output = os.environ.get("GITHUB_OUTPUT")
if output:
with open(output, "a", encoding="utf-8") as handle:
handle.write(line + "\n")


def main() -> int:
path = attest_path()
text = path.read_text(encoding="utf-8")
cosign = input_default(text, "cosign_version")
crane = input_default(text, "crane_version")
digest = crane_sha256(text)
if not VERSION_RE.match(cosign):
raise SystemExit(f"cosign_version {cosign!r} is not vMAJOR.MINOR.PATCH")
if not VERSION_RE.match(crane):
raise SystemExit(f"crane_version {crane!r} is not vMAJOR.MINOR.PATCH")
if not SHA256_RE.match(digest):
raise SystemExit("CRANE_SHA256 is not a 64-char lowercase hex digest")
emit("cosign_version", cosign)
emit("crane_version", crane)
emit("crane_sha256", digest)
return 0


if __name__ == "__main__":
sys.exit(main())
19 changes: 19 additions & 0 deletions .github/actions/setup-helm/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Helm CLI pin for ci.yml, release.yml, and uat.yml. Dependabot's
# github-actions ecosystem updates the setup-helm action SHA below; it does
# not touch `version:`. Bump that input here — one place — during release
# prep. See .github/dependabot.yml.

name: Setup Helm
description: >
Install the pinned Helm CLI. The CLI version is a manual pin (Dependabot
only updates the setup-helm action SHA).

runs:
using: composite
steps:
- uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1
with:
version: v3.19.2
61 changes: 53 additions & 8 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,62 @@ updates:
- "k8s.io/*"
- "sigs.k8s.io/*"

# The nested tools module (tools/go.mod) carries the `tool` directives for the
# Go CLIs the Makefile installs: controller-gen, addlicense, golangci-lint,
# govulncheck, gotestsum, gocover-cobertura. It lives in its own module so
# those tools' dependency trees stay out of the root module graph -- this
# module is importable (consumers use api/v1alpha1 for the CRD types) and
# would otherwise inherit ~200 extra requires that MVS considers when
# resolving shared dependencies. Same schedule and cooldown as the root
# module above.
- package-ecosystem: gomod
directory: /tools
schedule:
interval: weekly
day: monday
open-pull-requests-limit: 5
cooldown:
default-days: 7
semver-major-days: 14
semver-minor-days: 7
semver-patch-days: 3
groups:
# One group: everything here is a tool pin or its transitive tree, so a
# single reviewable PR per week is the useful granularity. A stale
# scanner pin (govulncheck especially) is the thing worth noticing, and
# it is not worth splitting from the rest.
go-tools:
patterns:
- "*"

# No cooldown here: GitHub Actions is not one of the ecosystems that supports
# it. Actions are pinned to commit SHAs, which Dependabot raises.
#
# NOT covered by any ecosystem below: tool versions pinned outside a manifest
# Dependabot parses -- the *_VERSION variables in the Makefile (controller-gen,
# addlicense, golangci-lint, govulncheck, gotestsum, gocover-cobertura) and
# COSIGN_VERSION in publish.yml. Dependabot reads neither Makefiles nor
# workflow `env:` values, so those pins are bumped by hand and must be checked
# during release prep. Moving the Go tools into a `tools.go` build-tagged file
# would place them in go.mod and bring them under the gomod ecosystem; that
# adds direct dependencies and is deliberately left as a separate decision.
# The Makefile's Go tool pins are covered: they are `tool` directives in
# tools/go.mod, raised by the /tools gomod entry above. The Makefile reads
# those versions with gomodver instead of hard-coding them.
#
# Still manual, because Dependabot cannot parse these surfaces (and several
# need a companion digest or a release-format review a blind bump would
# skip). Check during release prep:
# - cosign_version / crane_version defaults on attest.yml workflow_call
# inputs, plus CRANE_SHA256 on that workflow's install step. This is the
# single pin: release.yml verify-release and docs-verify.yml read those
# defaults via .github/actions/read-attest-pins rather than carrying
# their own copies (COSIGN_VERSION, a literal cosign-release, or a
# second CRANE_VERSION/CRANE_SHA256 pair). Dependabot does not update
# workflow_call input defaults or env: values; cosign governs the
# attestation bundle format (ADR-074), and crane's download digest is
# known for one version only.
# - Helm CLI `version:` in .github/actions/setup-helm (consumed by ci.yml,
# release.yml, uat.yml). Dependabot updates the azure/setup-helm action
# SHA; it does not touch the input that selects the helm binary. One
# home so those three workflows cannot drift.
# - SYFT_VERSION + SYFT_SHA256 in release.yml: version and digest must move
# together, and there is no atomic pair update for that.
# - YQ_VERSION in publish-fern-docs.yml, KIND_VERSION / TILT_VERSION in
# .github/actions/install-uat-tools, KWOK_VERSION in the Makefile --
# GitHub-release binaries outside any module graph.
Comment on lines +72 to +92

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The manual-pin list is not the full surface #279 asked to track. Dependabot's github-actions ecosystem updates uses: SHAs only. These values are on this branch and will not move:

  • COSIGN_VERSION in release.yml (verify-release env, passed to cosign-installer as cosign-release). This is the workflow env: pin the issue named. It left publish.yml, and it is still an env value.
  • cosign-release: v3.1.3 as a literal with: input in docs-verify.yml.
  • CRANE_VERSION + CRANE_SHA256 in docs-verify.yml. Same digest-pair constraint as crane in attest.yml and as syft, in a second copy.
  • version: v3.19.2 on azure/setup-helm in ci.yml, release.yml, and uat.yml (twice on current main).

attest.yml's defaults are the signer. release.yml and docs-verify.yml are the verifiers. Bumping only the documented default leaves verification on a different cosign or crane. The Helm CLI pin can drift the same way across those three workflows.

Minimum correction: add each pin to this list with the reason it stays manual. For cosign and crane, better to have the verifier jobs read the same default attest.yml already documents, so there is one pin.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 5d68e84.

Cosign / crane — unified (preferred path): attest.yml remains the single pin (workflow_call defaults + CRANE_SHA256). release.yml verify-release and docs-verify.yml now read those values via .github/actions/read-attest-pins, so there is no second COSIGN_VERSION, no literal cosign-release: v3.1.3, and no duplicate crane digest pair on the verifiers.

Helm CLI — centralized: ci.yml, release.yml, and uat.yml install through .github/actions/setup-helm. Dependabot still moves the azure/setup-helm SHA; the CLI version: input lives in one place.

Manual-pin list: .github/dependabot.yml now documents the full #279 surface you named (attest.yml cosign/crane and that verifiers consume it, Helm version: in setup-helm, plus the existing GitHub-release / digest pins) with why each stays manual.

test/releasepolicy/tool_pins_test.go fails if a verifier re-embeds those versions, if helm is called beside the composite action, or if a pin drops off the dependabot comment.

- package-ecosystem: github-actions
directory: /
schedule:
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/attest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,18 @@ on:
required: false
type: boolean
default: false
# Deliberately manual pin: Dependabot cannot raise workflow_call input
# defaults, and a cosign bump changes the attestation bundle format
# (ADR-074). Verifier jobs read this default (see
# .github/actions/read-attest-pins) so a bump here is the only edit.
# See .github/dependabot.yml for the full manual-pin list.
cosign_version:
description: 'Pinned cosign version. Governs the emitted bundle format.'
required: false
type: string
default: 'v3.1.3'
# Deliberately manual: download SHA below is known only for this version.
# Verifiers read this default and CRANE_SHA256 from this file.
crane_version:
description: 'Pinned crane version, used to resolve registry digests.'
required: false
Expand Down
4 changes: 1 addition & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,7 @@ jobs:
go-version-file: go.mod
cache: true

- uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1
with:
version: v3.19.2
- uses: ./.github/actions/setup-helm

# gotestsum and gocover-cobertura are installed by `make test-ci` at the
# versions pinned in the Makefile, so CI and a local run report against
Expand Down
17 changes: 13 additions & 4 deletions .github/workflows/docs-verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ on:
paths:
- 'docs/operations/verifying-artifacts.md'
- '.github/workflows/docs-verify.yml'
- '.github/workflows/attest.yml'
- '.github/actions/read-attest-pins/**'
schedule:
# Weekly. The commands depend on Rekor, GHCR and the release assets, none of
# which this repository controls.
Expand All @@ -42,19 +44,26 @@ jobs:
with:
persist-credentials: false

# Same pin the signer documents. A literal cosign-release or a second
# CRANE_VERSION/CRANE_SHA256 pair here would let the docs verifier drift
# from the bundle format and digest-resolution tool attest.yml uses.
- name: Read signer tool pins
id: pins
uses: ./.github/actions/read-attest-pins

- name: Install cosign
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
with:
cosign-release: v3.1.3
cosign-release: ${{ steps.pins.outputs.cosign_version }}

# Same pattern as attest.yml: download from the project that publishes it
# and check a digest pinned here. A setup action would have installed
# and check the digest pinned there. A setup action would have installed
# whatever crane is newest at run time, unverified, which is the opposite
# of what this workflow is checking.
- name: Install crane
env:
CRANE_VERSION: v0.20.6
CRANE_SHA256: c1d593d01551f2c9a3df5ca0a0be4385a839bd9b86d4a76e18d7b17d16559127
CRANE_VERSION: ${{ steps.pins.outputs.crane_version }}
CRANE_SHA256: ${{ steps.pins.outputs.crane_sha256 }}
run: |
set -euo pipefail
base="https://github.com/google/go-containerregistry/releases/download/${CRANE_VERSION}"
Expand Down
16 changes: 11 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -198,9 +198,7 @@ jobs:
go-version-file: go.mod
cache: true

- uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1
with:
version: v3.19.2
- uses: ./.github/actions/setup-helm

- name: Log in to GHCR
env:
Expand Down Expand Up @@ -331,6 +329,9 @@ jobs:
# that release's own checksums, rather than through a setup action. Same
# reasoning as crane in attest.yml: fewer third parties in the release
# path, and the binary is verified on arrival.
#
# Deliberately manual pin: SYFT_VERSION and SYFT_SHA256 must move together.
# Dependabot cannot update that pair atomically — see .github/dependabot.yml.
- name: Install syft
id: syft
env:
Expand Down Expand Up @@ -914,7 +915,6 @@ jobs:
packages: read
id-token: none
env:
COSIGN_VERSION: v3.1.3
TAG: ${{ needs.release-tag.outputs.value }}
IMAGE: ${{ needs.build-image.outputs.image_name }}
INDEX: ${{ needs.build-image.outputs.index_digest }}
Expand All @@ -928,10 +928,16 @@ jobs:
with:
persist-credentials: false

# Same pin the signer documents. A second COSIGN_VERSION here would let
# verification drift from the bundle format attest.yml emits (ADR-074).
- name: Read signer tool pins
id: pins
uses: ./.github/actions/read-attest-pins

- name: Install cosign
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
with:
cosign-release: ${{ env.COSIGN_VERSION }}
cosign-release: ${{ steps.pins.outputs.cosign_version }}

# The identity every check below pins. Exact, never a regexp: a pattern
# naming no workflow and no ref also accepts a branch build, which is how
Expand Down
4 changes: 1 addition & 3 deletions .github/workflows/uat.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,7 @@ jobs:
go-version-file: go.mod
cache: true

- uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1
with:
version: v3.19.2
- uses: ./.github/actions/setup-helm

- name: Install UAT tools
uses: ./.github/actions/install-uat-tools
Expand Down
Loading