mirror of
https://github.com/luxfi/mpc.git
synced 2026-09-24 00:26:24 +00:00
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.
115 lines
3.8 KiB
Go
115 lines
3.8 KiB
Go
// Drain — refuse a service drain unless EVERY remaining worker passes
|
|
// the policy gate. A drain that lowers the eligible-worker count below
|
|
// the workload's threshold is rejected; a drain that would force a
|
|
// session to land on an ineligible worker is rejected.
|
|
//
|
|
// This is the operational complement to Eligible(): it answers "can we
|
|
// drain node X right now without weakening the trust posture below
|
|
// what the workload requires?".
|
|
package scheduler
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
)
|
|
|
|
// ErrInsufficientTrust is returned when a drain (or sign authorization)
|
|
// would proceed on a worker set below the workload's required trust
|
|
// floor. Callers may errors.Is against this sentinel.
|
|
var ErrInsufficientTrust = errors.New("scheduler: insufficient trust for workload")
|
|
|
|
// DrainRequest describes a proposed drain. Threshold is the minimum
|
|
// number of eligible workers the workload requires to remain
|
|
// operational AFTER the drain.
|
|
type DrainRequest struct {
|
|
NodeID string
|
|
WorkloadID string
|
|
Workers []Worker // current worker set, including the one to drain
|
|
Threshold int // min eligible after drain (e.g. t+1 for t-of-n MPC)
|
|
}
|
|
|
|
// AuthorizeDrain returns nil iff draining req.NodeID is safe for the
|
|
// workload. Otherwise returns a wrapped ErrInsufficientTrust describing
|
|
// the failed gate.
|
|
//
|
|
// Algorithm:
|
|
// 1. Resolve the WorkloadPolicy.
|
|
// 2. Filter Workers by Eligible(p) AND ID != NodeID.
|
|
// 3. If len(remaining) < Threshold → reject.
|
|
//
|
|
// Workers that are already ineligible BEFORE the drain are not counted —
|
|
// the drain cannot improve their state, but it must not also remove a
|
|
// worker the policy still depended on.
|
|
func (s *Scheduler) AuthorizeDrain(req DrainRequest) error {
|
|
if req.NodeID == "" {
|
|
return errors.New("scheduler: drain missing node ID")
|
|
}
|
|
if req.WorkloadID == "" {
|
|
return errors.New("scheduler: drain missing workload ID")
|
|
}
|
|
if req.Threshold <= 0 {
|
|
return errors.New("scheduler: drain threshold must be positive")
|
|
}
|
|
|
|
p, err := s.src.Lookup(req.WorkloadID)
|
|
if err != nil {
|
|
return fmt.Errorf("scheduler: drain lookup %s: %w", req.WorkloadID, err)
|
|
}
|
|
if p == nil {
|
|
return fmt.Errorf("scheduler: drain no policy for %s", req.WorkloadID)
|
|
}
|
|
|
|
eligible := 0
|
|
for _, w := range req.Workers {
|
|
if w.ID == req.NodeID {
|
|
continue // the worker being drained
|
|
}
|
|
if err := p.Eligible(w); err != nil {
|
|
continue // already ineligible — does not count toward threshold
|
|
}
|
|
eligible++
|
|
}
|
|
|
|
if eligible < req.Threshold {
|
|
return fmt.Errorf("%w: workload=%s would have %d eligible workers, need %d",
|
|
ErrInsufficientTrust, req.WorkloadID, eligible, req.Threshold)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// AuthorizeSession is the per-sign analogue: given the candidate worker
|
|
// set and the workload, return the subset that may participate. Returns
|
|
// ErrInsufficientTrust if fewer than min workers pass the gate.
|
|
//
|
|
// The order of the returned slice matches the input order — callers
|
|
// that depend on stable selection (party-ID assignment in MPC) get
|
|
// deterministic results.
|
|
func (s *Scheduler) AuthorizeSession(workloadID string, candidates []Worker, min int) ([]Worker, error) {
|
|
if workloadID == "" {
|
|
return nil, errors.New("scheduler: session missing workload ID")
|
|
}
|
|
if min <= 0 {
|
|
return nil, errors.New("scheduler: session min must be positive")
|
|
}
|
|
|
|
p, err := s.src.Lookup(workloadID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("scheduler: session lookup %s: %w", workloadID, err)
|
|
}
|
|
if p == nil {
|
|
return nil, fmt.Errorf("scheduler: session no policy for %s", workloadID)
|
|
}
|
|
|
|
out := make([]Worker, 0, len(candidates))
|
|
for _, w := range candidates {
|
|
if err := p.Eligible(w); err == nil {
|
|
out = append(out, w)
|
|
}
|
|
}
|
|
if len(out) < min {
|
|
return nil, fmt.Errorf("%w: workload=%s eligible=%d min=%d",
|
|
ErrInsufficientTrust, workloadID, len(out), min)
|
|
}
|
|
return out, nil
|
|
}
|