Files
mpc/pkg/attestation/nvidia/gpu_report_test.go
zeekay 3f1f30de35 v0.61+v0.62: scheduler trust gate + NVIDIA attestation primitives
v0.61 — wire the scheduler workload-policy gate into the MPC SignGate.
SignGate gains optional Scheduler + Worker fields; AuthorizeSign now
runs scheduler.Eligible(worker, ci.PolicyID) BEFORE the policy
verifier and short-circuits on insufficient trust posture. Existing
callers that pass nil Scheduler are unaffected.

  pkg/scheduler/policy_gate.go     — TrustMode/IOLevel enums,
                                     WorkloadPolicy.Eligible(Worker)
  pkg/scheduler/service_drain.go   — AuthorizeDrain + AuthorizeSession
  pkg/mpc/sign_orchestrator.go     — Scheduler/Worker fields,
                                     WithScheduler(...), two-stage gate

v0.62 — NVIDIA Confidential-Compute attestation primitives. Clean-room
parsers + verifiers against the publicly documented nvTrust schema
(Apache-2 derived); no NVIDIA proprietary code vendored.

  pkg/attestation/nvidia/gpu_report.go   — GPU evidence-report parser
  pkg/attestation/nvidia/nras_client.go  — NRAS HTTP client + JWS verify
  pkg/attestation/nvidia/rim.go          — RIM signature + value match
  pkg/attestation/nvidia/composite.go    — VerifiedBundle (CPU+GPU bind)

Tests (57 passing, -race clean):
  pkg/scheduler                        — 11 (truth-table, drain,
                                          session, orchestrator
                                          integration)
  pkg/attestation/nvidia               — 46 (parser, RIM Ed25519/ECDSA/
                                          RSA-PSS, NRAS happy/error/
                                          signature paths, ECDSA JOSE)
  pkg/attestation/nvidia (hwattest)    — 3, env-skipped without real GPU

Sibling v0.60 reconciliation: pkg/registry + pkg/attestation already
land WorkerCapability + CompositeAttestation envelope using lattice
trust types. nvidia.VerifiedBundle is renamed (was Composite-) to
avoid collision with pkg/attestation.CompositeAttestation; the
canonical envelope wraps the verified blobs this package emits.

Residual: lattice/v7/types is not yet published, so pkg/registry +
pkg/attestation cannot compile against luxfi/[email protected]; the
sibling v0.60 work tracks that. pkg/policy still has a pre-existing
unresolved import of luxfi/fhe/threshold (should be /pkg/threshold);
sign_orchestrator_scheduler_test.go is ready to run as soon as that
upstream issue is fixed by its owner.
2025-12-28 16:53:46 -08:00

116 lines
3.7 KiB
Go

package nvidia
import (
"encoding/hex"
"errors"
"testing"
)
func goodReportJSON() []byte {
return []byte(`{
"evidence_version": "2.1",
"gpu_uuid": "GPU-1234-5678",
"architecture": "Hopper",
"driver_version": "535.104.05",
"vbios_version": "96.00.74.00.01",
"nonce": "` + hex.EncodeToString(make([]byte, 32)) + `",
"measurements": [
{"pcr_index": 0, "name": "FW_RUNTIME", "value": "deadbeef00112233"},
{"pcr_index": 1, "name": "VBIOS_RT", "value": "cafebabe44556677"}
],
"cert_chain": ["-----BEGIN CERTIFICATE-----\nMIIB...\n-----END CERTIFICATE-----"],
"attestation_quote": "AAAA",
"nvswitch_present": false
}`)
}
func TestParseGPUReport_HappyPath(t *testing.T) {
r, err := ParseGPUReport(goodReportJSON())
if err != nil {
t.Fatalf("parse: %v", err)
}
if r.UUID != "GPU-1234-5678" {
t.Fatalf("uuid: %s", r.UUID)
}
if r.Architecture != "Hopper" {
t.Fatalf("arch: %s", r.Architecture)
}
if len(r.Measurements) != 2 {
t.Fatalf("measurements: %d", len(r.Measurements))
}
if r.Measurements[0].Name != "FW_RUNTIME" {
t.Fatalf("name: %s", r.Measurements[0].Name)
}
if hex.EncodeToString(r.Measurements[0].Value) != "deadbeef00112233" {
t.Fatalf("value decode: %x", r.Measurements[0].Value)
}
}
func TestParseGPUReport_RejectsEmpty(t *testing.T) {
if _, err := ParseGPUReport(nil); !errors.Is(err, ErrReportEmpty) {
t.Fatalf("got %v", err)
}
}
func TestParseGPUReport_RejectsBadJSON(t *testing.T) {
if _, err := ParseGPUReport([]byte("{not json")); !errors.Is(err, ErrReportBadJSON) {
t.Fatalf("got %v", err)
}
}
func TestParseGPUReport_RejectsBadVersion(t *testing.T) {
in := []byte(`{"evidence_version":"0.9","gpu_uuid":"x","architecture":"Hopper","nonce":"` + hex.EncodeToString(make([]byte, 32)) + `","measurements":[{"name":"a","value":"00"}]}`)
if _, err := ParseGPUReport(in); !errors.Is(err, ErrReportBadVersion) {
t.Fatalf("got %v", err)
}
}
func TestParseGPUReport_RejectsUnknownArch(t *testing.T) {
in := []byte(`{"evidence_version":"2.1","gpu_uuid":"x","architecture":"Pascal","nonce":"` + hex.EncodeToString(make([]byte, 32)) + `","measurements":[{"name":"a","value":"00"}]}`)
if _, err := ParseGPUReport(in); !errors.Is(err, ErrReportUnknownArch) {
t.Fatalf("got %v", err)
}
}
func TestParseGPUReport_RejectsMissingNonce(t *testing.T) {
in := []byte(`{"evidence_version":"2.1","gpu_uuid":"x","architecture":"Hopper","measurements":[{"name":"a","value":"00"}]}`)
if _, err := ParseGPUReport(in); !errors.Is(err, ErrReportMissingNonce) {
t.Fatalf("got %v", err)
}
}
func TestParseGPUReport_RejectsBadNonce(t *testing.T) {
in := []byte(`{"evidence_version":"2.1","gpu_uuid":"x","architecture":"Hopper","nonce":"deadbeef","measurements":[{"name":"a","value":"00"}]}`)
if _, err := ParseGPUReport(in); !errors.Is(err, ErrReportBadNonce) {
t.Fatalf("got %v", err)
}
}
func TestParseGPUReport_RejectsNoMeasurements(t *testing.T) {
in := []byte(`{"evidence_version":"2.1","gpu_uuid":"x","architecture":"Hopper","nonce":"` + hex.EncodeToString(make([]byte, 32)) + `","measurements":[]}`)
if _, err := ParseGPUReport(in); !errors.Is(err, ErrReportNoMeasurements) {
t.Fatalf("got %v", err)
}
}
func TestParseGPUReport_RejectsBadMeasurementHex(t *testing.T) {
in := []byte(`{"evidence_version":"2.1","gpu_uuid":"x","architecture":"Hopper","nonce":"` + hex.EncodeToString(make([]byte, 32)) + `","measurements":[{"name":"a","value":"GG"}]}`)
if _, err := ParseGPUReport(in); err == nil {
t.Fatal("expected error on bad hex")
}
}
func TestMeasurementMap(t *testing.T) {
r, err := ParseGPUReport(goodReportJSON())
if err != nil {
t.Fatalf("parse: %v", err)
}
m := r.MeasurementMap()
if len(m) != 2 {
t.Fatalf("map size %d", len(m))
}
if _, ok := m["FW_RUNTIME"]; !ok {
t.Fatal("missing FW_RUNTIME")
}
}