Skip to content
Open
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
208 changes: 208 additions & 0 deletions .github/workflows/server-kms-e2e.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. 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.

# Server-side (aicrd) HashiCorp Vault-backed bundle attestation e2e. The
# companion of vault-kms-e2e.yaml, which exercises the same hashivault:// path
# through the aicr CLI; this one drives the aicrd HTTP SERVER's
# POST /v1/bundle?attest=true surface (#1150) with a non-interactive,
# operator-supplied KMS signing identity.
#
# It builds an ATTESTED aicrd binary (goreleaser + the cosign attest-blob hook,
# fed by generate-slsa-predicate which sets SLSA_PREDICATE + AICR_SIGNING_CONFIG),
# then delegates to the suite's run.sh. run.sh owns the full runtime lifecycle:
# it starts OpenBAO in dev mode (plain docker run, so it can reference the
# load-versions image pin — a services: container would start before that step),
# provisions an ecdsa-p256 Transit key, starts aicrd in the background with the
# signing identity, drives the sign + verify round-trip, and tears both down.
# Keeping OpenBAO + server ownership inside run.sh means the exact same script
# runs locally and in CI (no drift between an inlined workflow copy and the
# runner).
#
# Dev-mode OpenBAO serves plain HTTP with a known root token, so no TLS/mkcert
# dance is needed. The image is pinned in .settings.yaml
# (testing_tools.openbao_image) and resolved via load-versions, never :latest.

name: Server KMS E2E

on:
workflow_dispatch: {}
push:
branches:
- main
paths-ignore:
- '**.md'
- 'docs/**'
- 'LICENSE'
# Validate on PRs that touch the suite, its workflow/tooling, or the
# server-signing / verify code under test. Gated to same-repo in the job `if`
# below: fork PRs lack the OIDC token needed to attest the binary, so they
# skip rather than fail.
pull_request:
branches:
- main
paths:
- 'tests/chainsaw/signing/server-bundle-attestation-vault/**'
- '.github/workflows/server-kms-e2e.yaml'
- '.github/actions/load-versions/**'
- '.github/actions/generate-slsa-predicate/**'
- '.github/actions/setup-build-tools/**'
- '.settings.yaml'
- '.goreleaser.yaml'
# The server signing/attestation surface under test.
- 'pkg/server/**'
- 'pkg/bundler/**'
# The keyless/trust-root verification path imports pkg/trust directly.
- 'pkg/trust/**'
# `verify --key`/`--insecure-ignore-tlog` plumbing lives in pkg/cli;
# cmd/aicrd is the server entrypoint, cmd/aicr the CLI entrypoint. go.mod/
# sum cover dependency bumps (e.g. sigstore) that change the signing path.
- 'pkg/cli/**'
- 'cmd/aicrd/**'
- 'cmd/aicr/**'
- 'go.mod'
- 'go.sum'
- 'vendor/**'

# Least privilege at the workflow level; id-token is granted only to the job
# that needs it (below).
permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

env:
# Dev-mode OpenBAO: plain HTTP, fixed root token. Nothing here is a real secret.
VAULT_ADDR: http://127.0.0.1:8200
VAULT_TOKEN: root
VAULT_KMS_KEY: aicr
# The aicrd binary built here is attested by THIS workflow's identity (via
# generate-slsa-predicate workflow_file: server-kms-e2e.yaml + the goreleaser
# cosign attest-blob hook), not the release workflow (on-tag.yaml) the server
# pins by default. Retarget the certificate-identity pattern to this workflow
# so the server can verify its own binary attestation at startup. Still pinned
# to NVIDIA/aicr (enforced by verifier.ValidateIdentityPattern). run.sh
# defaults to the same value; set here explicitly for clarity.
AICR_BINARY_ATTESTATION_IDENTITY_REGEXP: '^https://github\.com/NVIDIA/aicr/\.github/workflows/server-kms-e2e\.yaml@.*'

jobs:
server-kms-e2e:
name: Server KMS E2E
runs-on: ubuntu-latest
timeout-minutes: 20
# id-token: write mints the GitHub OIDC token for Sigstore keyless signing.
# The "Build binaries" step runs goreleaser, whose cosign attest-blob hook
# uses that identity to produce the aicrd binary's SLSA provenance (fed by
# "Generate SLSA predicate"), which the server embeds as tool provenance.
permissions:
contents: read
id-token: write
# Skip on fork PRs: they get a read-only token, so `cosign attest-blob`
# (binary attestation) cannot run. push/workflow_dispatch always run.
if: >-
github.event_name != 'pull_request' ||
github.event.pull_request.head.repo.full_name == github.repository

steps:
- name: Checkout Code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false

- name: Load versions
id: versions
uses: ./.github/actions/load-versions

- name: Setup Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: ${{ steps.versions.outputs.go }}
cache: true

- name: Install GoReleaser
uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7.2.3
with:
version: ${{ steps.versions.outputs.goreleaser }}
install-only: true

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

- name: Generate SLSA predicate
uses: ./.github/actions/generate-slsa-predicate
with:
workflow_file: server-kms-e2e.yaml

- name: Build binaries
env:
GOFLAGS: -mod=vendor
run: |
set -euo pipefail
# Builds aicr + aicrd for the host target. With SLSA_PREDICATE +
# AICR_SIGNING_CONFIG set (by "Generate SLSA predicate"), the
# .goreleaser.yaml aicrd post-hook runs cosign attest-blob and writes
# aicrd-attestation.sigstore.json next to the binary — the server's
# own tool provenance the e2e embeds.
goreleaser build --clean --single-target --snapshot --timeout 10m

- name: Locate binaries
run: |
set -euo pipefail

AICRD_BIN=""
for pattern in dist/aicrd_linux_amd64_v1/aicrd dist/aicrd_linux_amd64/aicrd; do
if [ -x "$pattern" ]; then
AICRD_BIN="$(pwd)/$pattern"
break
fi
done
if [ -z "$AICRD_BIN" ]; then
echo "::error::Built aicrd server binary not found in dist/"
exit 1
fi

AICR_BIN=""
for pattern in dist/aicr_linux_amd64_v1/aicr dist/aicr_linux_amd64/aicr; do
if [ -x "$pattern" ]; then
AICR_BIN="$(pwd)/$pattern"
break
fi
done
if [ -z "$AICR_BIN" ]; then
echo "::error::Built aicr CLI binary not found in dist/"
exit 1
fi

echo "AICRD_BIN=${AICRD_BIN}" >> "$GITHUB_ENV"
echo "AICR_BIN=${AICR_BIN}" >> "$GITHUB_ENV"
echo "Server binary: ${AICRD_BIN}"
echo "CLI binary: ${AICR_BIN}"

ATTEST_FILE="$(dirname "$AICRD_BIN")/aicrd-attestation.sigstore.json"
if [ -f "$ATTEST_FILE" ]; then
echo "aicrd binary attestation found: $ATTEST_FILE (full mode)"
else
echo "::warning::No aicrd binary attestation found; run.sh will fall back to smoke mode"
Comment thread
mchmarny marked this conversation as resolved.
fi

- name: Run Server KMS e2e (starts OpenBAO + aicrd, sign + verify)
run: |
set -euo pipefail
./tests/chainsaw/signing/server-bundle-attestation-vault/run.sh
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ bin/
/aicr-attestation.sigstore.json
/recipe-catalog.sigstore.json

# Generated aicrd binary attestations copied into ko data at release time
cmd/aicrd/kodata/*.sigstore.json

# =============================================================================
# Build & Release
# =============================================================================
Expand Down
26 changes: 26 additions & 0 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,32 @@ builds:
-X github.com/NVIDIA/aicr/pkg/server.version={{.Version}}
-X github.com/NVIDIA/aicr/pkg/server.commit={{.FullCommit}}
-X github.com/NVIDIA/aicr/pkg/server.date={{.Date}}
hooks:
post:
## Binary attestation for the aicrd server binary. Same cosign attest-blob
## flow as the aicr CLI build above (Rekor v2 via SLSA_PREDICATE +
## AICR_SIGNING_CONFIG; skipped when SLSA_PREDICATE is unset, e.g. local
## snapshot builds). Writes the bundle next to the binary AND copies it into
## cmd/aicrd/kodata/ with a per-arch name so ko ships it at KO_DATA_PATH for
## the multi-arch image. The server (pkg/server/signing.go
## resolveBinaryAttestationPath) reads KO_DATA_PATH/aicrd-<GOARCH>-attestation.sigstore.json;
## keep the filename in sync with defaults.BinaryAttestationKoDataNameFormat.
- cmd: >-
bash -c '[ -z "${SLSA_PREDICATE:-}" ] && exit 0;
[ -z "${AICR_SIGNING_CONFIG:-}" ] && { echo "AICR_SIGNING_CONFIG unset while signing; refusing to fall back to Rekor v1" >&2; exit 1; };
for n in 1 2 3; do
cosign attest-blob
--predicate "${SLSA_PREDICATE}"
--type https://slsa.dev/provenance/v1
--signing-config "${AICR_SIGNING_CONFIG}"
--bundle "$(dirname "{{ .Path }}")/aicrd-attestation.sigstore.json"
--yes "{{ .Path }}" && break;
echo "cosign attest-blob attempt $n failed; retrying" >&2;
if [ "$n" -lt 3 ]; then sleep $((n * 5)); else echo "cosign attest-blob failed after 3 attempts" >&2; exit 1; fi;
done;
mkdir -p cmd/aicrd/kodata;
cp "$(dirname "{{ .Path }}")/aicrd-attestation.sigstore.json" "cmd/aicrd/kodata/aicrd-{{ .Arch }}-attestation.sigstore.json"'
output: true
Comment thread
coderabbitai[bot] marked this conversation as resolved.
goos:
- linux
goarch:
Expand Down
Loading
Loading