Skip to content

Commit c541d23

Browse files
authored
Merge branch 'main' into fix-kubeclient-error-classification
2 parents afc51eb + 3b3e3a3 commit c541d23

221 files changed

Lines changed: 14664 additions & 1204 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/actions/cli-e2e/action.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,21 @@ inputs:
3434
helmfile_sha256:
3535
description: 'Helmfile SHA256 checksum for linux/amd64 (from .settings.yaml)'
3636
required: true
37+
cosign_version:
38+
description: 'Cosign version (from .settings.yaml via load-versions; must be >= v3.1.0 so DSSE attestations are logged to Rekor v2 as hashedrekord/PAE)'
39+
required: true
3740

3841
runs:
3942
using: 'composite'
4043
steps:
44+
# `required: true` on composite-action inputs is not enforced at runtime, so
45+
# a caller that omits cosign_version would silently get the cosign-installer
46+
# default (which lags the v3.1.0 floor DSSE-on-v2 needs). Fail closed. See #1650.
47+
- name: Require cosign_version
48+
shell: bash
49+
env:
50+
COSIGN_VERSION: ${{ inputs.cosign_version }}
51+
run: '[ -n "$COSIGN_VERSION" ] || { echo "cosign_version input is required (>= v3.1.0 for Rekor v2)" >&2; exit 1; }'
4152
- name: Setup Go
4253
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
4354
with:
@@ -46,6 +57,11 @@ runs:
4657

4758
- name: Install Cosign
4859
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
60+
with:
61+
# Pin from .settings.yaml (>= v3.1.0) so cosign logs DSSE attestations to
62+
# Rekor v2 as hashedrekord/PAE. The installer default (v3.0.2) writes the
63+
# legacy dsse entry type, which sigstore-go cannot verify. See #1650.
64+
cosign-release: '${{ inputs.cosign_version }}'
4965

5066
- name: Install GoReleaser
5167
uses: goreleaser/goreleaser-action@ec59f474b9834571250b370d4735c50f8e2d1e29 # v7.0.0

.github/actions/generate-slsa-predicate/action.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,30 @@ runs:
5252
}
5353
EOF
5454
echo "SLSA_PREDICATE=${PREDICATE}" >> "$GITHUB_ENV"
55+
56+
# AICR signs release attestations to Rekor v2 (NVIDIA/aicr#1650). Fetch
57+
# the TUF-distributed v2 SigningConfig (Rekor v2 endpoint + timestamp
58+
# authority) so the goreleaser cosign + sign-catalog hooks can point
59+
# --signing-config at it. Co-located with SLSA_PREDICATE so the invariant
60+
# "signing enabled (SLSA_PREDICATE set) => signing config present" holds
61+
# for every signing workflow, not just the release. go run compiles aicr
62+
# from source (the built binaries do not exist yet). Requires Go, which
63+
# every caller sets up before this action.
64+
SC="${RUNNER_TEMP}/aicr-signing-config.json"
65+
# Retry the TUF fetch (3 attempts, 5s/10s backoff) to absorb transient
66+
# Sigstore TUF CDN blips, mirroring the goreleaser cosign hook — this is a
67+
# hard prerequisite for the whole signing flow, which fails closed if the
68+
# config is missing. The `-s` check treats an exit-0-but-empty-file result
69+
# as a failure so a truncated write is retried, not silently exported.
70+
for n in 1 2 3; do
71+
GOFLAGS=-mod=vendor go run ./cmd/aicr trust update --emit-signing-config "${SC}" && [ -s "${SC}" ] && break
72+
if [ "$n" -lt 3 ]; then
73+
echo "trust update attempt $n failed; retrying" >&2
74+
sleep $((n * 5))
75+
else
76+
echo "trust update failed after 3 attempts" >&2
77+
exit 1
78+
fi
79+
done
80+
echo "AICR_SIGNING_CONFIG=${SC}" >> "$GITHUB_ENV"
81+
echo "Rekor v2 signing config written to ${SC}"

.github/actions/go-build-release/action.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ runs:
7575
with:
7676
registry: ${{ inputs.registry }}
7777

78+
# Note: the Rekor v2 signing config (AICR_SIGNING_CONFIG) is fetched and
79+
# exported by the generate-slsa-predicate action, co-located with
80+
# SLSA_PREDICATE, so it is present here without a separate step. See #1650.
81+
7882
- name: Build and Release
7983
id: release
8084
shell: bash

.github/actions/install-aicr-release/action.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,32 @@ inputs:
2727
aicr-version:
2828
description: 'Released aicr version tag to install (e.g. v1.2.3).'
2929
required: true
30+
cosign_version:
31+
description: 'Cosign version (from .settings.yaml via load-versions; must be >= v3.1.0 to verify release provenance logged to Rekor v2 as hashedrekord/PAE).'
32+
required: true
3033

3134
runs:
3235
using: composite
3336
steps:
37+
# GitHub does not enforce `required: true` on composite-action inputs at
38+
# runtime, so a caller that omits cosign_version would pass '' to
39+
# cosign-release and silently get the installer default (which lags the
40+
# v3.1.0 floor). Fail closed instead. See #1650.
41+
- name: Require cosign_version
42+
shell: bash
43+
env:
44+
COSIGN_VERSION: ${{ inputs.cosign_version }}
45+
run: '[ -n "$COSIGN_VERSION" ] || { echo "cosign_version input is required (>= v3.1.0 for Rekor v2)" >&2; exit 1; }'
3446
# verify-blob-attestation needs no OIDC token (verification, not signing);
3547
# it fetches only the Sigstore trusted root, so the caller's contents:read
3648
# ceiling suffices. Pinned to the same SHA the release/build workflows use.
3749
- name: Install Cosign
3850
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
51+
with:
52+
# Pin from .settings.yaml (>= v3.1.0). The release binary provenance is
53+
# logged to Rekor v2 as hashedrekord/PAE; keep the verifier in lockstep
54+
# with the signer rather than the installer default (v3.0.6). See #1650.
55+
cosign-release: '${{ inputs.cosign_version }}'
3956
- name: Download, verify, and install released aicr
4057
shell: bash
4158
env:

.github/actions/kwok-test/action.yml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,54 @@ inputs:
7171
description: 'Number of days to retain debug artifacts'
7272
required: false
7373
default: '7'
74+
job_timeout_minutes:
75+
description: >-
76+
The calling job's timeout-minutes. Used to derive
77+
KWOK_SYNC_DEADLINE_EPOCH (deadline = step start + this budget - 240s
78+
diagnostics margin) so each chainsaw sync gate's budget stays within
79+
that margin, letting the gate finish - and print its catch-block
80+
diagnostics - before GitHub kills the job in the expected
81+
single-gate-dominates case (this bounds each gate operation, not the
82+
job's overall wall time). Required with no default: every caller
83+
must wire its own timeout-minutes here, so a job that changes its
84+
timeout cannot silently drift from a stale default (a missing value
85+
fails the integer validation below loudly).
86+
required: true
7487

7588
runs:
7689
using: 'composite'
7790
steps:
91+
- name: Derive sync-gate deadline
92+
shell: bash
93+
env:
94+
JOB_TIMEOUT_MINUTES: ${{ inputs.job_timeout_minutes }}
95+
run: |
96+
# Anchor the sync-gate deadline as early as possible in the job:
97+
# only checkout + load-versions run before this step, so the
98+
# unaccounted job-start drift stays inside the 240s margin's 60s
99+
# allowance (the other 180s is reserved for post-gate work:
100+
# chainsaw catch diagnostics, verify_pods, debug-artifact upload).
101+
# Anchoring later (at the test step, after setup-go + tool installs
102+
# + make build) lets the deadline land past GitHub's job kill and
103+
# silently re-creates the CANCELLED-without-diagnostics failure
104+
# this mechanism exists to prevent.
105+
# See kwok/scripts/lib/sync-budget.sh for the consuming side.
106+
if ! [[ "${JOB_TIMEOUT_MINUTES}" =~ ^[1-9][0-9]*$ ]]; then
107+
echo "::error::job_timeout_minutes must be a positive integer, no leading zeros, got '${JOB_TIMEOUT_MINUTES}'"
108+
exit 1
109+
fi
110+
# 120 must equal SYNC_BUDGET_FLOOR_SECONDS in kwok/scripts/lib/sync-budget.sh
111+
# (hand-synced literal, guarded by sync-budget_test.sh — same contract as the
112+
# 240s margin). A budget under the floor would compute an already-unusable
113+
# deadline that only fails later, inside the job, after setup burned CI minutes.
114+
if (( 10#${JOB_TIMEOUT_MINUTES} * 60 - 240 < 120 )); then
115+
echo "::error::job_timeout_minutes=${JOB_TIMEOUT_MINUTES} leaves no usable sync budget after the 240s diagnostics margin (need >= 6)"
116+
exit 1
117+
fi
118+
KWOK_SYNC_DEADLINE_EPOCH=$(( $(date +%s) + 10#${JOB_TIMEOUT_MINUTES} * 60 - 240 ))
119+
echo "KWOK_SYNC_DEADLINE_EPOCH=${KWOK_SYNC_DEADLINE_EPOCH}" >> "$GITHUB_ENV"
120+
echo "KWOK_SYNC_DEADLINE_EPOCH=${KWOK_SYNC_DEADLINE_EPOCH} (job_timeout_minutes=${JOB_TIMEOUT_MINUTES}, margin=240s, job kill in ~$(( 10#${JOB_TIMEOUT_MINUTES} * 60 ))s)"
121+
78122
- name: Set up Go
79123
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
80124
with:

.github/actions/load-versions/action.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,18 @@ outputs:
6464
grype:
6565
description: 'Grype version'
6666
value: ${{ steps.versions.outputs.grype }}
67+
cosign:
68+
description: 'Cosign version (must be >= v3.1.0 so DSSE attestations are logged to Rekor v2 as hashedrekord/PAE, not the legacy dsse entry type)'
69+
value: ${{ steps.versions.outputs.cosign }}
6770
yamllint:
6871
description: 'yamllint version'
6972
value: ${{ steps.versions.outputs.yamllint }}
7073
kubectl:
7174
description: 'kubectl version'
7275
value: ${{ steps.versions.outputs.kubectl }}
76+
kubelogin:
77+
description: 'kubelogin version'
78+
value: ${{ steps.versions.outputs.kubelogin }}
7379
kwok:
7480
description: 'KWOK version'
7581
value: ${{ steps.versions.outputs.kwok }}
@@ -165,9 +171,11 @@ runs:
165171
166172
# Security tools
167173
echo "grype=$(yq eval '.security_tools.grype' .settings.yaml)" >> $GITHUB_OUTPUT
174+
echo "cosign=$(yq eval '.security_tools.cosign' .settings.yaml)" >> $GITHUB_OUTPUT
168175
169176
# Testing tools
170177
echo "kubectl=$(yq eval '.testing_tools.kubectl' .settings.yaml)" >> $GITHUB_OUTPUT
178+
echo "kubelogin=$(yq eval '.testing_tools.kubelogin' .settings.yaml)" >> $GITHUB_OUTPUT
171179
echo "kind=$(yq eval '.testing_tools.kind' .settings.yaml)" >> $GITHUB_OUTPUT
172180
echo "nvkind=$(yq eval '.testing_tools.nvkind' .settings.yaml)" >> $GITHUB_OUTPUT
173181
echo "ctlptl=$(yq eval '.testing_tools.ctlptl' .settings.yaml)" >> $GITHUB_OUTPUT
@@ -218,7 +226,9 @@ runs:
218226
echo " addlicense: ${{ steps.versions.outputs.addlicense }}"
219227
echo " go_licenses: ${{ steps.versions.outputs.go_licenses }}"
220228
echo " grype: ${{ steps.versions.outputs.grype }}"
229+
echo " cosign: ${{ steps.versions.outputs.cosign }}"
221230
echo " kubectl: ${{ steps.versions.outputs.kubectl }}"
231+
echo " kubelogin: ${{ steps.versions.outputs.kubelogin }}"
222232
echo " kind: ${{ steps.versions.outputs.kind }}"
223233
echo " nvkind: ${{ steps.versions.outputs.nvkind }}"
224234
echo " ctlptl: ${{ steps.versions.outputs.ctlptl }}"

.github/copy-pr-bot.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,4 @@ additional_trustees:
3232
- yuanchen8911
3333
- JRosenboimNVIDIA
3434
- hkii
35+
- tjrasche
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
#!/usr/bin/env bash
2+
# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3+
# SPDX-License-Identifier: Apache-2.0
4+
#
5+
# Commit the signed/relocated evidence pointers back to the branch through
6+
# GitHub's GraphQL `createCommitOnBranch` mutation so the commit carries
7+
# GitHub's web-flow signature and shows the **Verified** badge (#1551).
8+
#
9+
# Why the API instead of `git push`: GitHub auto-signs only commits it creates
10+
# server-side (REST contents API, GraphQL createCommitOnBranch, web editor,
11+
# merge button). A commit that arrives via `git push` is never signed by
12+
# GitHub, so the runner's client-side commit-back was always Unverified — the
13+
# `github-actions[bot]` identity has no GPG/SSH key on the runner to `-S` with.
14+
# createCommitOnBranch authors the commit as the GITHUB_TOKEN identity
15+
# (github-actions[bot]) and GitHub signs it → Verified.
16+
#
17+
# The signing step's relocation is a delete (flat pointer) + add (nested
18+
# pointer) plus an in-place signer patch, so the mutation sends the FULL
19+
# fileChanges.additions / fileChanges.deletions set computed from the working
20+
# tree against HEAD.
21+
#
22+
# Behavior preserved from the previous `git push` implementation:
23+
# * Clean no-op when nothing under recipes/evidence/ changed (nothing to
24+
# sign): exit 0 without creating a commit.
25+
# * DCO sign-off — a `Signed-off-by:` trailer matching the bot author is
26+
# added to the commit body so the DCO check passes on the commit-back.
27+
# * Loop guard — createCommitOnBranch runs with the default GITHUB_TOKEN, and
28+
# GitHub does not trigger workflow runs for token-authored commits, so the
29+
# commit-back does not re-trigger the sign workflow. The headline is
30+
# unchanged so the workflow's belt-and-suspenders `startsWith(...)` guard
31+
# still matches for any fork pushing via a PAT.
32+
#
33+
# Required env:
34+
# GH_TOKEN token authenticating `gh api` (github.token)
35+
# GITHUB_REPOSITORY owner/repo (provided by Actions)
36+
# GITHUB_REF_NAME branch name to commit onto (provided by Actions)
37+
38+
set -euo pipefail
39+
40+
: "${GH_TOKEN:?GH_TOKEN is required (token authenticating gh api)}"
41+
: "${GITHUB_REPOSITORY:?GITHUB_REPOSITORY is required (owner/repo)}"
42+
: "${GITHUB_REF_NAME:?GITHUB_REF_NAME is required (branch name)}"
43+
44+
# Match the bot author createCommitOnBranch stamps on the commit so the DCO
45+
# sign-off trailer is consistent with the commit author.
46+
readonly BOT_NAME="github-actions[bot]"
47+
readonly BOT_EMAIL="41898282+github-actions[bot]@users.noreply.github.com"
48+
readonly HEADLINE="chore(evidence): sign pending evidence pointers"
49+
50+
# Stage the relocation (delete flat + add nested + in-place signer patch) so an
51+
# untracked relocated file is counted, then decide whether there is anything to
52+
# commit. --no-renames splits every rename into a delete + add pair, which is
53+
# exactly the shape createCommitOnBranch.fileChanges expects.
54+
git add -A recipes/evidence/
55+
if git diff --cached --quiet -- recipes/evidence/; then
56+
echo "No pointer changes to commit (nothing to sign)."
57+
exit 0
58+
fi
59+
60+
additions='[]'
61+
deletions='[]'
62+
while IFS= read -r -d '' status && IFS= read -r -d '' path; do
63+
case "$status" in
64+
D)
65+
deletions=$(jq -c --arg p "$path" '. += [{path: $p}]' <<<"$deletions")
66+
;;
67+
*)
68+
# A (add) or M (modify): send the full file contents, base64-encoded as
69+
# the GraphQL API requires. -w0 keeps it single-line (GNU coreutils on
70+
# the ubuntu runner).
71+
contents=$(base64 -w0 <"$path")
72+
additions=$(jq -c --arg p "$path" --arg c "$contents" \
73+
'. += [{path: $p, contents: $c}]' <<<"$additions")
74+
;;
75+
esac
76+
done < <(git diff --cached --name-status --no-renames -z -- recipes/evidence/)
77+
78+
# expectedHeadOid pins the mutation to the branch tip we checked out; a
79+
# concurrent advance fails the mutation loudly (re-dispatch after pulling)
80+
# rather than silently racing.
81+
head_oid=$(git rev-parse HEAD)
82+
body="Signed-off-by: ${BOT_NAME} <${BOT_EMAIL}>"
83+
84+
variables=$(jq -n \
85+
--arg repo "$GITHUB_REPOSITORY" \
86+
--arg branch "$GITHUB_REF_NAME" \
87+
--arg oid "$head_oid" \
88+
--arg headline "$HEADLINE" \
89+
--arg body "$body" \
90+
--argjson additions "$additions" \
91+
--argjson deletions "$deletions" \
92+
'{
93+
input: {
94+
branch: {repositoryNameWithOwner: $repo, branchName: $branch},
95+
expectedHeadOid: $oid,
96+
message: {headline: $headline, body: $body},
97+
fileChanges: {additions: $additions, deletions: $deletions}
98+
}
99+
}')
100+
101+
read -r -d '' query <<'GRAPHQL' || true
102+
mutation ($input: CreateCommitOnBranchInput!) {
103+
createCommitOnBranch(input: $input) {
104+
commit {
105+
oid
106+
url
107+
}
108+
}
109+
}
110+
GRAPHQL
111+
112+
# Post {query, variables} to the GraphQL endpoint. `input` is an object
113+
# variable, so it cannot be passed via `gh api graphql -f input=...` (that
114+
# would send a string and fail type-checking) — build the full request body
115+
# and stream it in.
116+
commit_oid=$(jq -n --arg q "$query" --argjson v "$variables" '{query: $q, variables: $v}' \
117+
| gh api graphql --input - --jq '.data.createCommitOnBranch.commit.oid')
118+
119+
echo "Committed signed pointers as ${commit_oid} (GitHub-signed, Verified)."

.github/workflows/build-attested.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ jobs:
6060

6161
- name: Install Cosign
6262
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
63+
with:
64+
# Pin from .settings.yaml (>= v3.1.0) so cosign logs DSSE attestations
65+
# to Rekor v2 as hashedrekord/PAE. The installer default (v3.0.6) writes
66+
# the legacy dsse entry type, which sigstore-go cannot verify. See #1650.
67+
cosign-release: ${{ steps.versions.outputs.cosign }}
6368

6469
- name: Install GoReleaser
6570
uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7.2.3

.github/workflows/evidence-ingest.yaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,9 @@ env:
7474
FIRST_PARTY_ISSUER: 'https://token.actions.githubusercontent.com'
7575
# Identity regexp pinning first-party UAT signatures to the NVIDIA/aicr
7676
# UAT workflows. Fully anchored so the verifier's signer match is exact
77-
# (matches what uat-aws.yaml / uat-gcp.yaml pin in their own verify step).
78-
FIRST_PARTY_IDENTITY: '^https://github\.com/NVIDIA/aicr/\.github/workflows/uat-(aws|gcp)\.yaml@refs/heads/.+$'
77+
# (matches what uat-aws.yaml / uat-gcp.yaml / uat-azure.yaml pin in their
78+
# own verify step).
79+
FIRST_PARTY_IDENTITY: '^https://github\.com/NVIDIA/aicr/\.github/workflows/uat-(aws|gcp|azure)\.yaml@refs/heads/.+$'
7980

8081
jobs:
8182
# ---------------------------------------------------------------------

0 commit comments

Comments
 (0)