Files
mpc/pkg/attestation/nvidia/composite_test.go
zeekay 1996f1adc4 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

150 lines
3.7 KiB
Go

package nvidia
import (
"errors"
"testing"
"time"
)
// helper — fully verified composite.
func okBundle() VerifiedBundle {
gpuNonce := [32]byte{1, 2, 3, 4}
workerID := "worker-7"
gpuUUID := "GPU-DEAD-BEEF"
cpuRD := BindCPUReportData(workerID, gpuUUID, gpuNonce)
return VerifiedBundle{
WorkerID: workerID,
CPU: CPUQuote{
Kind: TEESEVSNP,
Verified: true,
MeasurementHash: [32]byte{0x11},
ReportData: cpuRD,
VerifiedAt: time.Now().Add(-1 * time.Minute),
},
GPU: GPUEvidence{
UUID: gpuUUID,
DriverVersion: "535.104.05",
VBIOSVersion: "96.00.74.00.01",
Architecture: "Hopper",
Verified: true,
RIMMatched: true,
Nonce: gpuNonce,
VerifiedAt: time.Now().Add(-2 * time.Minute),
MeasurementRoot: [32]byte{0x22},
},
KernelManifestRoot: [32]byte{0x33, 0x44},
BuiltAt: time.Now(),
}
}
func TestVerifiedBundle_HappyPath(t *testing.T) {
c := okBundle()
res, err := c.Verify(time.Now(), time.Hour)
if err != nil {
t.Fatalf("verify: %v", err)
}
if !res.CPUAttested || !res.GPUAttested {
t.Fatalf("expected attested, got %+v", res)
}
if res.KernelManifestRoot != c.KernelManifestRoot {
t.Fatalf("kernel root drift")
}
}
func TestVerifiedBundle_NilReceiver(t *testing.T) {
var c *VerifiedBundle
if _, err := c.Verify(time.Now(), 0); err == nil {
t.Fatal("expected error")
}
}
func TestVerifiedBundle_RejectsTEENone(t *testing.T) {
c := okBundle()
c.CPU.Kind = TEENone
if _, err := c.Verify(time.Now(), 0); !errors.Is(err, ErrBundleNoCPUQuote) {
t.Fatalf("got %v", err)
}
}
func TestVerifiedBundle_RejectsUnverifiedCPU(t *testing.T) {
c := okBundle()
c.CPU.Verified = false
if _, err := c.Verify(time.Now(), 0); !errors.Is(err, ErrBundleBadCPU) {
t.Fatalf("got %v", err)
}
}
func TestVerifiedBundle_RejectsUnverifiedGPU(t *testing.T) {
c := okBundle()
c.GPU.Verified = false
if _, err := c.Verify(time.Now(), 0); !errors.Is(err, ErrBundleBadGPU) {
t.Fatalf("got %v", err)
}
}
func TestVerifiedBundle_RejectsRIMMismatch(t *testing.T) {
c := okBundle()
c.GPU.RIMMatched = false
if _, err := c.Verify(time.Now(), 0); !errors.Is(err, ErrBundleRIMMismatch) {
t.Fatalf("got %v", err)
}
}
func TestVerifiedBundle_RejectsBindingMismatch(t *testing.T) {
c := okBundle()
c.CPU.ReportData = [32]byte{0xff} // wrong binding
if _, err := c.Verify(time.Now(), 0); !errors.Is(err, ErrBundleBindingMissing) {
t.Fatalf("got %v", err)
}
}
func TestVerifiedBundle_RejectsStaleCPU(t *testing.T) {
c := okBundle()
c.CPU.VerifiedAt = time.Now().Add(-25 * time.Hour)
if _, err := c.Verify(time.Now(), time.Hour); !errors.Is(err, ErrBundleStale) {
t.Fatalf("got %v", err)
}
}
func TestVerifiedBundle_RejectsStaleGPU(t *testing.T) {
c := okBundle()
c.GPU.VerifiedAt = time.Now().Add(-25 * time.Hour)
if _, err := c.Verify(time.Now(), time.Hour); !errors.Is(err, ErrBundleStale) {
t.Fatalf("got %v", err)
}
}
func TestVerifiedBundle_RejectsMissingWorker(t *testing.T) {
c := okBundle()
c.WorkerID = ""
if _, err := c.Verify(time.Now(), 0); !errors.Is(err, ErrBundleMissingWorker) {
t.Fatalf("got %v", err)
}
}
func TestBindCPUReportData_StableAcrossInputs(t *testing.T) {
a := BindCPUReportData("w", "u", [32]byte{1})
b := BindCPUReportData("w", "u", [32]byte{1})
c := BindCPUReportData("w", "u", [32]byte{2})
if a != b {
t.Fatalf("not deterministic")
}
if a == c {
t.Fatalf("nonce collision")
}
}
func TestTEEKind_String(t *testing.T) {
cases := map[TEEKind]string{
TEENone: "none",
TEESEVSNP: "sev_snp",
TEETDX: "tdx",
TEEKind(99): "invalid",
}
for k, want := range cases {
if k.String() != want {
t.Fatalf("k=%d got %s want %s", k, k.String(), want)
}
}
}