Files
mpc/pkg/scheduler/policy_gate_test.go
zeekay 9c6ee31cd4 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

244 lines
6.6 KiB
Go

package scheduler
import (
"errors"
"testing"
)
// canonical policy used across the truth table — model confidentiality
// over private lane on attested CPU + GPU.
var privatePolicy = WorkloadPolicy{
WorkloadID: "model-private",
MinTrust: TrustGPU,
MinIO: IOPrivateLane,
RequireModelConfidentiality: true,
RequireGPUAttested: true,
RequireCPUAttested: true,
ApprovedKernelRoots: [][32]byte{{1, 2, 3}, {4, 5, 6}},
}
func okWorker() Worker {
return Worker{
ID: "node-1",
TrustMode: TrustGPU,
IOLevel: IOPrivateLane,
KernelManifestRoot: [32]byte{1, 2, 3},
GPUAttested: true,
CPUAttested: true,
Online: true,
}
}
func TestEligible_TruthTable(t *testing.T) {
tests := []struct {
name string
mutate func(*Worker)
wantErr bool
match string
}{
{"happy path", func(w *Worker) {}, false, ""},
{"offline", func(w *Worker) { w.Online = false }, true, "offline"},
{"trust below floor", func(w *Worker) { w.TrustMode = TrustCVM }, true, "trust"},
{"io below floor", func(w *Worker) { w.IOLevel = IOPrivateNet }, true, "io"},
{"gpu unattested", func(w *Worker) { w.GPUAttested = false }, true, "GPU attestation"},
{"cpu unattested", func(w *Worker) { w.CPUAttested = false }, true, "CPU attestation"},
{"kernel root not approved", func(w *Worker) { w.KernelManifestRoot = [32]byte{99} }, true, "KernelManifestRoot not in approved set"},
{"kernel root second match", func(w *Worker) { w.KernelManifestRoot = [32]byte{4, 5, 6} }, false, ""},
{"missing kernel root", func(w *Worker) { w.KernelManifestRoot = [32]byte{} }, true, "missing KernelManifestRoot"},
{"missing ID", func(w *Worker) { w.ID = "" }, true, "missing ID"},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
w := okWorker()
tc.mutate(&w)
err := privatePolicy.Eligible(w)
if tc.wantErr {
if err == nil {
t.Fatalf("expected error containing %q, got nil", tc.match)
}
if tc.match != "" && !contains(err.Error(), tc.match) {
t.Fatalf("expected %q in error, got %q", tc.match, err.Error())
}
return
}
if err != nil {
t.Fatalf("expected nil, got %v", err)
}
})
}
}
func TestEligible_NilPolicy(t *testing.T) {
var p *WorkloadPolicy
if err := p.Eligible(okWorker()); err == nil {
t.Fatal("expected error for nil policy")
}
}
func TestEligible_PublicWorkloadAcceptsBareMetal(t *testing.T) {
publicPolicy := WorkloadPolicy{
WorkloadID: "public",
MinTrust: TrustNone,
MinIO: IOPublic,
}
w := Worker{ID: "n", Online: true}
if err := publicPolicy.Eligible(w); err != nil {
t.Fatalf("public workload should accept bare worker: %v", err)
}
}
func TestEligible_ConfidentialityWithoutKernelRoots(t *testing.T) {
p := WorkloadPolicy{
WorkloadID: "broken",
MinTrust: TrustGPU,
MinIO: IOPrivateLane,
RequireModelConfidentiality: true,
RequireGPUAttested: true,
// no ApprovedKernelRoots — must reject
}
if err := p.Eligible(okWorker()); err == nil {
t.Fatal("expected error when confidentiality required without approved roots")
}
}
func TestStaticPolicySource(t *testing.T) {
src := NewStaticPolicySource(map[string]*WorkloadPolicy{
"a": &privatePolicy,
})
got, err := src.Lookup("a")
if err != nil {
t.Fatalf("lookup a: %v", err)
}
if got == nil || got.WorkloadID != "model-private" {
t.Fatalf("got %+v", got)
}
if _, err := src.Lookup("missing"); err == nil {
t.Fatal("expected error for missing workload")
}
}
func TestScheduler_Eligible(t *testing.T) {
src := NewStaticPolicySource(map[string]*WorkloadPolicy{"a": &privatePolicy})
s := New(src)
if err := s.Eligible(okWorker(), "a"); err != nil {
t.Fatalf("happy: %v", err)
}
bad := okWorker()
bad.TrustMode = TrustCVM
if err := s.Eligible(bad, "a"); err == nil {
t.Fatal("expected eligibility failure")
}
if err := s.Eligible(okWorker(), "missing"); err == nil {
t.Fatal("expected lookup failure")
}
}
func TestScheduler_AuthorizeSession_PrivateLaneRefusal(t *testing.T) {
src := NewStaticPolicySource(map[string]*WorkloadPolicy{"a": &privatePolicy})
s := New(src)
good1 := okWorker()
good1.ID = "n1"
good2 := okWorker()
good2.ID = "n2"
bad := okWorker()
bad.ID = "n3"
bad.IOLevel = IOPublic // private-lane refusal: this worker MUST be excluded
out, err := s.AuthorizeSession("a", []Worker{good1, bad, good2}, 2)
if err != nil {
t.Fatalf("expected 2 eligible: %v", err)
}
if len(out) != 2 {
t.Fatalf("expected 2 eligible, got %d", len(out))
}
for _, w := range out {
if w.ID == "n3" {
t.Fatalf("public-IO worker n3 leaked through gate")
}
}
if _, err := s.AuthorizeSession("a", []Worker{bad}, 1); !errors.Is(err, ErrInsufficientTrust) {
t.Fatalf("expected ErrInsufficientTrust, got %v", err)
}
}
func TestScheduler_AuthorizeDrain(t *testing.T) {
src := NewStaticPolicySource(map[string]*WorkloadPolicy{"a": &privatePolicy})
s := New(src)
w1 := okWorker()
w1.ID = "n1"
w2 := okWorker()
w2.ID = "n2"
w3 := okWorker()
w3.ID = "n3"
// drain n1 with 3 healthy → 2 remain → ok for threshold=2
if err := s.AuthorizeDrain(DrainRequest{
NodeID: "n1",
WorkloadID: "a",
Workers: []Worker{w1, w2, w3},
Threshold: 2,
}); err != nil {
t.Fatalf("happy drain: %v", err)
}
// drain n1 with 3 healthy → 2 remain → fail for threshold=3
err := s.AuthorizeDrain(DrainRequest{
NodeID: "n1",
WorkloadID: "a",
Workers: []Worker{w1, w2, w3},
Threshold: 3,
})
if !errors.Is(err, ErrInsufficientTrust) {
t.Fatalf("expected ErrInsufficientTrust, got %v", err)
}
// pre-existing ineligible worker doesn't count toward remaining
w3bad := w3
w3bad.GPUAttested = false
err = s.AuthorizeDrain(DrainRequest{
NodeID: "n1",
WorkloadID: "a",
Workers: []Worker{w1, w2, w3bad},
Threshold: 2,
})
if !errors.Is(err, ErrInsufficientTrust) {
t.Fatalf("expected ErrInsufficientTrust when only 1 eligible remains, got %v", err)
}
// missing workload
err = s.AuthorizeDrain(DrainRequest{
NodeID: "n1",
WorkloadID: "missing",
Workers: []Worker{w1, w2},
Threshold: 1,
})
if err == nil || errors.Is(err, ErrInsufficientTrust) {
t.Fatalf("expected lookup error, got %v", err)
}
}
func TestNew_NilSourcePanics(t *testing.T) {
defer func() {
if recover() == nil {
t.Fatal("expected panic")
}
}()
New(nil)
}
// contains is a tiny strings.Contains shim to keep test deps minimal.
func contains(s, sub string) bool {
if sub == "" {
return true
}
for i := 0; i+len(sub) <= len(s); i++ {
if s[i:i+len(sub)] == sub {
return true
}
}
return false
}