Skip to content

Commit b56d2dc

Browse files
authored
Merge branch 'main' into worktree-conformance-artifacts
2 parents dfd601e + 1e3474d commit b56d2dc

File tree

7 files changed

+265
-22
lines changed

7 files changed

+265
-22
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
name: 'Generate SLSA Predicate'
16+
description: 'Generate a SLSA Build Provenance v1 predicate JSON for cosign attest-blob'
17+
18+
inputs:
19+
workflow_file:
20+
description: 'Workflow filename for builder ID (e.g., on-tag.yaml)'
21+
required: true
22+
23+
runs:
24+
using: 'composite'
25+
steps:
26+
- name: Generate SLSA predicate
27+
shell: bash
28+
run: |
29+
set -euo pipefail
30+
PREDICATE="${RUNNER_TEMP}/slsa-predicate.json"
31+
cat > "$PREDICATE" <<-EOF
32+
{
33+
"buildDefinition": {
34+
"buildType": "https://aicr.nvidia.com/binary/v1",
35+
"externalParameters": {
36+
"repository": "${{ github.repository }}",
37+
"ref": "${{ github.ref }}"
38+
},
39+
"resolvedDependencies": [{
40+
"uri": "git+https://github.com/${{ github.repository }}@${{ github.ref }}",
41+
"digest": { "gitCommit": "${{ github.sha }}" }
42+
}]
43+
},
44+
"runDetails": {
45+
"builder": {
46+
"id": "https://github.com/${{ github.repository }}/.github/workflows/${{ inputs.workflow_file }}"
47+
},
48+
"metadata": {
49+
"invocationId": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
50+
}
51+
}
52+
}
53+
EOF
54+
echo "SLSA_PREDICATE=${PREDICATE}" >> "$GITHUB_ENV"
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# Build attested binaries on-demand without cutting a release tag.
16+
# Produces tar.gz archives with SLSA Build Provenance v1 attestation
17+
# as downloadable job artifacts.
18+
19+
## NOTE: THIS WORKFLOW IS FOR TESTING PURPOSES ONLY.
20+
## if you need something attested by ci. This does not run tests or security scans.
21+
## The only complete/valid way to attest that passes all validation is via on-tag.yaml.
22+
## Validate attestations requires to pass the following certificate identity regexp:
23+
## --certificate-identity-regexp 'https://github.com/NVIDIA/aicr/.github/workflows/on-tag\.yaml@refs/tags/.*'
24+
## so this attestation is not the same as a production release.
25+
26+
name: Build Attested Binaries
27+
28+
on:
29+
workflow_dispatch: {}
30+
31+
permissions:
32+
contents: read
33+
id-token: write
34+
35+
jobs:
36+
build-and-attest:
37+
needs: [tests, security-scan]
38+
name: Build and Attest Binaries
39+
runs-on: ubuntu-latest
40+
timeout-minutes: 15
41+
steps:
42+
- name: Checkout Code
43+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
44+
with:
45+
fetch-depth: 0
46+
persist-credentials: false
47+
48+
- name: Setup Go
49+
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
50+
with:
51+
go-version-file: go.mod
52+
cache: true
53+
54+
- name: Install Cosign
55+
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
56+
57+
- name: Install GoReleaser
58+
uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0
59+
with:
60+
install-only: true
61+
62+
- name: Generate SLSA predicate
63+
uses: ./.github/actions/generate-slsa-predicate
64+
with:
65+
workflow_file: build-attested.yaml
66+
67+
- name: Build and attest
68+
env:
69+
GOFLAGS: -mod=vendor
70+
run: |
71+
set -euo pipefail
72+
goreleaser release --snapshot --clean --skip=publish,ko,sbom --timeout 10m
73+
74+
- name: Verify archive contents
75+
run: |
76+
set -euo pipefail
77+
echo "=== Archives ==="
78+
ls -la dist/aicr_v*.tar.gz 2>/dev/null || echo "No archives found"
79+
echo ""
80+
for archive in dist/aicr_v*.tar.gz; do
81+
[ -f "$archive" ] || continue
82+
echo "--- $(basename "$archive") ---"
83+
tar -tzf "$archive"
84+
echo ""
85+
done
86+
87+
- name: Upload archives
88+
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
89+
with:
90+
name: aicr-attested-binaries
91+
path: dist/*.tar.gz
92+
retention-days: 3

.github/workflows/on-tag.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,14 @@ jobs:
9595
go-version: ${{ steps.versions.outputs.go }}
9696
cache: true
9797

98+
- name: Install Cosign
99+
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
100+
101+
- name: Generate SLSA predicate
102+
uses: ./.github/actions/generate-slsa-predicate
103+
with:
104+
workflow_file: on-tag.yaml
105+
98106
- name: Build and Release
99107
id: release
100108
uses: ./.github/actions/go-build-release

.goreleaser.yaml

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,18 @@ builds:
3333
-X github.com/NVIDIA/aicr/pkg/cli.version={{.Version}}
3434
-X github.com/NVIDIA/aicr/pkg/cli.commit={{.ShortCommit}}
3535
-X github.com/NVIDIA/aicr/pkg/cli.date={{.Date}}
36+
hooks:
37+
post:
38+
## cosign v1 attestation with slsa provenance v1.
39+
## NOTE: below aicrd currently attests via github attestation
40+
- cmd: >-
41+
bash -c '[ -z "${SLSA_PREDICATE:-}" ] && exit 0;
42+
cosign attest-blob
43+
--predicate "${SLSA_PREDICATE}"
44+
--type https://slsa.dev/provenance/v1
45+
--bundle "$(dirname "{{ .Path }}")/aicr-attestation.sigstore.json"
46+
--yes "{{ .Path }}"'
47+
output: true
3648
goos:
3749
- darwin
3850
- linux
@@ -122,8 +134,11 @@ archives:
122134
ids:
123135
- aicr
124136
formats:
125-
- binary
126-
name_template: "{{ .Binary }}_v{{ .Version }}_{{ .Os }}_{{ .Arch }}"
137+
- tar.gz
138+
name_template: "{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
139+
files:
140+
- src: "dist/aicr_{{ .Os }}_{{ .Arch }}*/aicr-attestation.sigstore.json"
141+
strip_parent: true
127142

128143
changelog:
129144
sort: asc

DEVELOPMENT.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,17 @@ See [kwok/README.md](kwok/README.md) for adding recipes, profiles, and troublesh
549549
| `make bump-minor` | Bump minor version (1.2.3 → 1.3.0) |
550550
| `make bump-patch` | Bump patch version (1.2.3 → 1.2.4) |
551551

552+
### Binary Attestation
553+
554+
Release binaries are attested with SLSA Build Provenance v1 via a GoReleaser build
555+
hook that calls `cosign attest-blob`. The hook is guarded by the `$SLSA_PREDICATE`
556+
environment variable — it only runs when a workflow explicitly generates the predicate.
557+
Local `make build` is unaffected.
558+
559+
To produce attested binaries without a release tag, use the **Build Attested Binaries**
560+
workflow (`.github/workflows/build-attested.yaml`) from the Actions tab. It runs
561+
`goreleaser release --snapshot` with cosign and uploads tar.gz archives as artifacts.
562+
552563
### Local Development
553564

554565
| Target | Description |

SECURITY.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,36 @@ For more information:
139139
- [SPDX Specification](https://spdx.dev/)
140140
- [Sigstore Cosign](https://docs.sigstore.dev/cosign/overview/)
141141

142+
### CLI Binary Attestation
143+
144+
CLI binary releases are attested with SLSA Build Provenance v1 using Cosign keyless
145+
signing via GitHub Actions OIDC. Each release archive (`.tar.gz`) contains:
146+
147+
- `aicr` — the binary
148+
- `aicr-attestation.sigstore.json` — SLSA Build Provenance v1 attestation (Sigstore bundle format)
149+
150+
The attestation cryptographically proves which repository, commit, and workflow produced
151+
the binary. It is logged to the public [Rekor](https://rekor.sigstore.dev/) transparency
152+
log and can be verified offline.
153+
154+
**Verify a binary attestation:**
155+
156+
```shell
157+
cosign verify-blob-attestation \
158+
--bundle aicr-attestation.sigstore.json \
159+
--type https://slsa.dev/provenance/v1 \
160+
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
161+
--certificate-identity-regexp 'https://github.com/NVIDIA/aicr/.github/workflows/on-tag\.yaml@refs/tags/.*' \
162+
aicr
163+
```
164+
165+
The install script (`./install`) performs this verification automatically when
166+
[Cosign](https://docs.sigstore.dev/cosign/system_config/installation/) is available.
167+
168+
**On-demand attested builds:** The `Build Attested Binaries` workflow
169+
(`.github/workflows/build-attested.yaml`) can be triggered manually from the
170+
Actions tab to produce attested binaries from any branch without cutting a release.
171+
142172
### Setup
143173

144174
Export variables for the image you want to verify:

install

Lines changed: 53 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,11 @@ has_tools() {
5454
}
5555

5656
normalize_arch() {
57-
local arch="$1"
58-
[[ $arch == "x86_64" ]] && echo "amd64" || echo "$arch"
57+
case "$1" in
58+
x86_64) echo "amd64" ;;
59+
aarch64) echo "arm64" ;;
60+
*) echo "$1" ;;
61+
esac
5962
}
6063

6164
get_os() {
@@ -70,6 +73,10 @@ get_binary_name() {
7073
echo "${BIN_NAME}_${1}_${2}_${3}" # version, os, arch
7174
}
7275

76+
get_archive_name() {
77+
echo "${BIN_NAME}_${1}_${2}_${3}.tar.gz" # version, os, arch
78+
}
79+
7380
# ==============================================================================
7481
# GitHub API Functions
7582
# ==============================================================================
@@ -192,44 +199,70 @@ main() {
192199
has_tools "${REQUIRED_TOOLS[@]}"
193200

194201
# Fetch release information
195-
local release_json
202+
local release_json archive_name
196203
release_json=$(fetch_latest_release)
197204
version=$(extract_version "$release_json")
198-
binary_name=$(get_binary_name "$version" "$os" "$arch")
199-
205+
archive_name=$(get_archive_name "$version" "$os" "$arch")
206+
200207
msg "Platform: $os/$arch"
201208
msg "Version: $version"
202-
203-
# Download and verify binary
209+
210+
# Download archive and checksums
204211
temp_dir=$(mktemp -d)
205212
trap "rm -rf $temp_dir" EXIT
206213

207-
# Extract asset URLs from release JSON (handles both public and private repos)
208-
local binary_url checksum_url
209-
binary_url=$(extract_asset_url "$release_json" "$binary_name")
214+
local archive_url checksum_url
215+
archive_url=$(extract_asset_url "$release_json" "$archive_name")
210216
checksum_url=$(extract_asset_url "$release_json" "$CHECKSUMS_FILE")
211217

212-
download_release_asset "$binary_url" "${temp_dir}/${binary_name}" "$binary_name"
218+
download_release_asset "$archive_url" "${temp_dir}/${archive_name}" "$archive_name"
213219
download_release_asset "$checksum_url" "${temp_dir}/checksums.txt" "checksums"
214-
215-
# Verify checksum
220+
221+
# Verify archive checksum
216222
msg "Verifying checksum..."
217-
(cd "$temp_dir" && grep "$binary_name" checksums.txt | shasum -a 256 --check --strict) \
223+
(cd "$temp_dir" && grep "$archive_name" checksums.txt | shasum -a 256 --check --strict) \
218224
|| err "Checksum verification failed"
219-
220-
# Install binary
221-
chmod +x "${temp_dir}/${binary_name}"
225+
226+
# Extract archive
227+
msg "Extracting archive..."
228+
tar -xzf "${temp_dir}/${archive_name}" -C "$temp_dir"
229+
230+
# Optional: verify attestation if cosign is available
231+
if command -v cosign &>/dev/null && [[ -f "${temp_dir}/${BIN_NAME}-attestation.sigstore.json" ]]; then
232+
msg "Verifying attestation with cosign..."
233+
if cosign verify-blob-attestation \
234+
--bundle "${temp_dir}/${BIN_NAME}-attestation.sigstore.json" \
235+
--type https://slsa.dev/provenance/v1 \
236+
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
237+
--certificate-identity-regexp 'https://github.com/NVIDIA/aicr/.github/workflows/on-tag\.yaml@refs/tags/.*' \
238+
"${temp_dir}/${BIN_NAME}" 2>/dev/null; then
239+
msg "Attestation verified: binary built by github.com/NVIDIA/aicr"
240+
else
241+
msg "Warning: attestation verification failed — cannot confirm this binary was built by the official CI pipeline"
242+
fi
243+
elif [[ -f "${temp_dir}/${BIN_NAME}-attestation.sigstore.json" ]]; then
244+
msg "Tip: install cosign to verify binary attestation (https://docs.sigstore.dev/cosign/system_config/installation/)"
245+
fi
246+
247+
# Install binary and attestation
248+
chmod +x "${temp_dir}/${BIN_NAME}"
222249
msg "Installing $BIN_NAME to $INSTALL_DIR"
223250
mkdir -p "$INSTALL_DIR"
224251
if [[ -w "$INSTALL_DIR" ]]; then
225-
mv "${temp_dir}/${binary_name}" "${INSTALL_DIR}/${BIN_NAME}"
252+
mv "${temp_dir}/${BIN_NAME}" "${INSTALL_DIR}/${BIN_NAME}"
253+
[[ -f "${temp_dir}/${BIN_NAME}-attestation.sigstore.json" ]] && \
254+
mv "${temp_dir}/${BIN_NAME}-attestation.sigstore.json" "${INSTALL_DIR}/${BIN_NAME}-attestation.sigstore.json"
226255
else
227-
sudo mv "${temp_dir}/${binary_name}" "${INSTALL_DIR}/${BIN_NAME}"
256+
sudo mv "${temp_dir}/${BIN_NAME}" "${INSTALL_DIR}/${BIN_NAME}"
257+
[[ -f "${temp_dir}/${BIN_NAME}-attestation.sigstore.json" ]] && \
258+
sudo mv "${temp_dir}/${BIN_NAME}-attestation.sigstore.json" "${INSTALL_DIR}/${BIN_NAME}-attestation.sigstore.json"
228259
fi
229-
260+
230261
# Verify installation
231262
msg "$BIN_NAME installed successfully!"
232263
"${BIN_NAME}" --version
264+
[[ -f "${INSTALL_DIR}/${BIN_NAME}-attestation.sigstore.json" ]] && \
265+
msg "Attestation: ${INSTALL_DIR}/${BIN_NAME}-attestation.sigstore.json"
233266
}
234267

235268
# Run main function

0 commit comments

Comments
 (0)