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
54 changes: 54 additions & 0 deletions .github/actions/generate-slsa-predicate/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

name: 'Generate SLSA Predicate'
description: 'Generate a SLSA Build Provenance v1 predicate JSON for cosign attest-blob'

inputs:
workflow_file:
description: 'Workflow filename for builder ID (e.g., on-tag.yaml)'
required: true

runs:
using: 'composite'
steps:
- name: Generate SLSA predicate
shell: bash
run: |
set -euo pipefail
PREDICATE="${RUNNER_TEMP}/slsa-predicate.json"
cat > "$PREDICATE" <<-EOF
{
"buildDefinition": {
"buildType": "https://aicr.nvidia.com/binary/v1",
"externalParameters": {
"repository": "${{ github.repository }}",
"ref": "${{ github.ref }}"
},
"resolvedDependencies": [{
"uri": "git+https://github.com/${{ github.repository }}@${{ github.ref }}",
"digest": { "gitCommit": "${{ github.sha }}" }
}]
},
"runDetails": {
"builder": {
"id": "https://github.com/${{ github.repository }}/.github/workflows/${{ inputs.workflow_file }}"
},
"metadata": {
"invocationId": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
}
}
}
EOF
echo "SLSA_PREDICATE=${PREDICATE}" >> "$GITHUB_ENV"
92 changes: 92 additions & 0 deletions .github/workflows/build-attested.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Build attested binaries on-demand without cutting a release tag.
# Produces tar.gz archives with SLSA Build Provenance v1 attestation
# as downloadable job artifacts.

## NOTE: THIS WORKFLOW IS FOR TESTING PURPOSES ONLY.
## if you need something attested by ci. This does not run tests or security scans.
## The only complete/valid way to attest that passes all validation is via on-tag.yaml.
## Validate attestations requires to pass the following certificate identity regexp:
## --certificate-identity-regexp 'https://github.com/NVIDIA/aicr/.github/workflows/on-tag\.yaml@refs/tags/.*'
## so this attestation is not the same as a production release.

name: Build Attested Binaries

on:
workflow_dispatch: {}

permissions:
contents: read
id-token: write

jobs:
build-and-attest:
needs: [tests, security-scan]
name: Build and Attest Binaries
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout Code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
persist-credentials: false

- name: Setup Go
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
with:
go-version-file: go.mod
cache: true

- name: Install Cosign
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0

- name: Install GoReleaser
uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0
with:
install-only: true

- name: Generate SLSA predicate
uses: ./.github/actions/generate-slsa-predicate
with:
workflow_file: build-attested.yaml

- name: Build and attest
env:
GOFLAGS: -mod=vendor
run: |
set -euo pipefail
goreleaser release --snapshot --clean --skip=publish,ko,sbom --timeout 10m

- name: Verify archive contents
run: |
set -euo pipefail
echo "=== Archives ==="
ls -la dist/aicr_v*.tar.gz 2>/dev/null || echo "No archives found"
echo ""
for archive in dist/aicr_v*.tar.gz; do
[ -f "$archive" ] || continue
echo "--- $(basename "$archive") ---"
tar -tzf "$archive"
echo ""
done

- name: Upload archives
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: aicr-attested-binaries
path: dist/*.tar.gz
retention-days: 3
8 changes: 8 additions & 0 deletions .github/workflows/on-tag.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,14 @@ jobs:
go-version: ${{ steps.versions.outputs.go }}
cache: true

- name: Install Cosign
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0

- name: Generate SLSA predicate
uses: ./.github/actions/generate-slsa-predicate
with:
workflow_file: on-tag.yaml

- name: Build and Release
id: release
uses: ./.github/actions/go-build-release
Expand Down
19 changes: 17 additions & 2 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,18 @@ builds:
-X github.com/NVIDIA/aicr/pkg/cli.version={{.Version}}
-X github.com/NVIDIA/aicr/pkg/cli.commit={{.ShortCommit}}
-X github.com/NVIDIA/aicr/pkg/cli.date={{.Date}}
hooks:
post:
## cosign v1 attestation with slsa provenance v1.
## NOTE: below aicrd currently attests via github attestation
- cmd: >-
bash -c '[ -z "${SLSA_PREDICATE:-}" ] && exit 0;
cosign attest-blob
--predicate "${SLSA_PREDICATE}"
--type https://slsa.dev/provenance/v1
--bundle "$(dirname "{{ .Path }}")/aicr-attestation.sigstore.json"
--yes "{{ .Path }}"'
output: true
goos:
- darwin
- linux
Expand Down Expand Up @@ -122,8 +134,11 @@ archives:
ids:
- aicr
formats:
- binary
name_template: "{{ .Binary }}_v{{ .Version }}_{{ .Os }}_{{ .Arch }}"
- tar.gz
name_template: "{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
files:
- src: "dist/aicr_{{ .Os }}_{{ .Arch }}*/aicr-attestation.sigstore.json"
strip_parent: true

changelog:
sort: asc
Expand Down
11 changes: 11 additions & 0 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,17 @@ See [kwok/README.md](kwok/README.md) for adding recipes, profiles, and troublesh
| `make bump-minor` | Bump minor version (1.2.3 → 1.3.0) |
| `make bump-patch` | Bump patch version (1.2.3 → 1.2.4) |

### Binary Attestation

Release binaries are attested with SLSA Build Provenance v1 via a GoReleaser build
hook that calls `cosign attest-blob`. The hook is guarded by the `$SLSA_PREDICATE`
environment variable — it only runs when a workflow explicitly generates the predicate.
Local `make build` is unaffected.

To produce attested binaries without a release tag, use the **Build Attested Binaries**
workflow (`.github/workflows/build-attested.yaml`) from the Actions tab. It runs
`goreleaser release --snapshot` with cosign and uploads tar.gz archives as artifacts.

### Local Development

| Target | Description |
Expand Down
30 changes: 30 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,36 @@ For more information:
- [SPDX Specification](https://spdx.dev/)
- [Sigstore Cosign](https://docs.sigstore.dev/cosign/overview/)

### CLI Binary Attestation

CLI binary releases are attested with SLSA Build Provenance v1 using Cosign keyless
signing via GitHub Actions OIDC. Each release archive (`.tar.gz`) contains:

- `aicr` — the binary
- `aicr-attestation.sigstore.json` — SLSA Build Provenance v1 attestation (Sigstore bundle format)

The attestation cryptographically proves which repository, commit, and workflow produced
the binary. It is logged to the public [Rekor](https://rekor.sigstore.dev/) transparency
log and can be verified offline.

**Verify a binary attestation:**

```shell
cosign verify-blob-attestation \
--bundle aicr-attestation.sigstore.json \
--type https://slsa.dev/provenance/v1 \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--certificate-identity-regexp 'https://github.com/NVIDIA/aicr/.github/workflows/on-tag\.yaml@refs/tags/.*' \
aicr
```

The install script (`./install`) performs this verification automatically when
[Cosign](https://docs.sigstore.dev/cosign/system_config/installation/) is available.

**On-demand attested builds:** The `Build Attested Binaries` workflow
(`.github/workflows/build-attested.yaml`) can be triggered manually from the
Actions tab to produce attested binaries from any branch without cutting a release.

### Setup

Export variables for the image you want to verify:
Expand Down
73 changes: 53 additions & 20 deletions install
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,11 @@ has_tools() {
}

normalize_arch() {
local arch="$1"
[[ $arch == "x86_64" ]] && echo "amd64" || echo "$arch"
case "$1" in
x86_64) echo "amd64" ;;
aarch64) echo "arm64" ;;
*) echo "$1" ;;
esac
}

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

get_archive_name() {
echo "${BIN_NAME}_${1}_${2}_${3}.tar.gz" # version, os, arch
}

# ==============================================================================
# GitHub API Functions
# ==============================================================================
Expand Down Expand Up @@ -192,44 +199,70 @@ main() {
has_tools "${REQUIRED_TOOLS[@]}"

# Fetch release information
local release_json
local release_json archive_name
release_json=$(fetch_latest_release)
version=$(extract_version "$release_json")
binary_name=$(get_binary_name "$version" "$os" "$arch")
archive_name=$(get_archive_name "$version" "$os" "$arch")

msg "Platform: $os/$arch"
msg "Version: $version"
# Download and verify binary

# Download archive and checksums
temp_dir=$(mktemp -d)
trap "rm -rf $temp_dir" EXIT

# Extract asset URLs from release JSON (handles both public and private repos)
local binary_url checksum_url
binary_url=$(extract_asset_url "$release_json" "$binary_name")
local archive_url checksum_url
archive_url=$(extract_asset_url "$release_json" "$archive_name")
checksum_url=$(extract_asset_url "$release_json" "$CHECKSUMS_FILE")

download_release_asset "$binary_url" "${temp_dir}/${binary_name}" "$binary_name"
download_release_asset "$archive_url" "${temp_dir}/${archive_name}" "$archive_name"
download_release_asset "$checksum_url" "${temp_dir}/checksums.txt" "checksums"
# Verify checksum

# Verify archive checksum
msg "Verifying checksum..."
(cd "$temp_dir" && grep "$binary_name" checksums.txt | shasum -a 256 --check --strict) \
(cd "$temp_dir" && grep "$archive_name" checksums.txt | shasum -a 256 --check --strict) \
|| err "Checksum verification failed"

# Install binary
chmod +x "${temp_dir}/${binary_name}"

# Extract archive
msg "Extracting archive..."
tar -xzf "${temp_dir}/${archive_name}" -C "$temp_dir"

# Optional: verify attestation if cosign is available
if command -v cosign &>/dev/null && [[ -f "${temp_dir}/${BIN_NAME}-attestation.sigstore.json" ]]; then
msg "Verifying attestation with cosign..."
if cosign verify-blob-attestation \
--bundle "${temp_dir}/${BIN_NAME}-attestation.sigstore.json" \
--type https://slsa.dev/provenance/v1 \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--certificate-identity-regexp 'https://github.com/NVIDIA/aicr/.github/workflows/on-tag\.yaml@refs/tags/.*' \
"${temp_dir}/${BIN_NAME}" 2>/dev/null; then
msg "Attestation verified: binary built by github.com/NVIDIA/aicr"
else
msg "Warning: attestation verification failed — cannot confirm this binary was built by the official CI pipeline"
fi
elif [[ -f "${temp_dir}/${BIN_NAME}-attestation.sigstore.json" ]]; then
msg "Tip: install cosign to verify binary attestation (https://docs.sigstore.dev/cosign/system_config/installation/)"
fi

# Install binary and attestation
chmod +x "${temp_dir}/${BIN_NAME}"
msg "Installing $BIN_NAME to $INSTALL_DIR"
mkdir -p "$INSTALL_DIR"
if [[ -w "$INSTALL_DIR" ]]; then
mv "${temp_dir}/${binary_name}" "${INSTALL_DIR}/${BIN_NAME}"
mv "${temp_dir}/${BIN_NAME}" "${INSTALL_DIR}/${BIN_NAME}"
[[ -f "${temp_dir}/${BIN_NAME}-attestation.sigstore.json" ]] && \
mv "${temp_dir}/${BIN_NAME}-attestation.sigstore.json" "${INSTALL_DIR}/${BIN_NAME}-attestation.sigstore.json"
else
sudo mv "${temp_dir}/${binary_name}" "${INSTALL_DIR}/${BIN_NAME}"
sudo mv "${temp_dir}/${BIN_NAME}" "${INSTALL_DIR}/${BIN_NAME}"
[[ -f "${temp_dir}/${BIN_NAME}-attestation.sigstore.json" ]] && \
sudo mv "${temp_dir}/${BIN_NAME}-attestation.sigstore.json" "${INSTALL_DIR}/${BIN_NAME}-attestation.sigstore.json"
fi

# Verify installation
msg "$BIN_NAME installed successfully!"
"${BIN_NAME}" --version
[[ -f "${INSTALL_DIR}/${BIN_NAME}-attestation.sigstore.json" ]] && \
msg "Attestation: ${INSTALL_DIR}/${BIN_NAME}-attestation.sigstore.json"
}

# Run main function
Expand Down