mirror of
https://github.com/luxfi/mpc.git
synced 2026-09-24 00:26:24 +00:00
Merge release-policy-require-flags-2026-04-28
This commit is contained in:
+82
-1
@@ -100,21 +100,56 @@ type CompositeAttestation interface {
|
||||
// lives only inside the worker's GPU TEE. Used to seal the session
|
||||
// key so only the attested TEE can unwrap it.
|
||||
TEEPublicKey() [32]byte
|
||||
|
||||
// EvidenceIssuers returns the canonical issuer strings present in
|
||||
// the composite envelope. Stable wire values:
|
||||
// - IssuerSEVSNP ("amd.sev.snp") — AMD SEV-SNP CPU TEE
|
||||
// - IssuerTDX ("intel.tdx") — Intel TDX CPU TEE
|
||||
// - IssuerNVNRAS ("nvidia.nras.v1") — NVIDIA NRAS GPU
|
||||
// ReleasePolicy.Permits uses this to enforce RequireSEVSNP /
|
||||
// RequireTDX / RequireNVNRAS gates: a missing issuer string when
|
||||
// the corresponding flag is set is a hard refusal. Order is
|
||||
// caller-supplied; Permits() does set-membership only.
|
||||
EvidenceIssuers() []string
|
||||
}
|
||||
|
||||
// Canonical evidence issuer strings. Mirrors pkg/attestation issuer
|
||||
// values and the cc/attest verifier dispatch keys. Stable on the wire;
|
||||
// any change here is a breaking interface change.
|
||||
const (
|
||||
IssuerSEVSNP = "amd.sev.snp"
|
||||
IssuerTDX = "intel.tdx"
|
||||
IssuerNVNRAS = "nvidia.nras.v1"
|
||||
)
|
||||
|
||||
// ReleasePolicy is the static allowlist a gate enforces. Bind one
|
||||
// policy per workload kind — e.g. "training-confidential" gets a
|
||||
// stricter RIM set than "inference-rwq".
|
||||
//
|
||||
// RequiredRIM and AllowedHardware are sets — membership semantics, not
|
||||
// ordered. Empty sets fail closed (no RIM ⇒ no release).
|
||||
//
|
||||
// RequireSEVSNP / RequireTDX / RequireNVNRAS are hard floors that mirror
|
||||
// the C++ AttestationBaseline.require_* fields (#203 O5 fix). A flag set
|
||||
// to true means: the attestation's evidence MUST include that issuer's
|
||||
// blob, or the policy refuses release. Production deployments hardcode
|
||||
// all three to true (CPU SEV-SNP and TDX + NVIDIA NRAS GPU); tests
|
||||
// opt-out by leaving the flags false. Setting RequireSEVSNP and
|
||||
// RequireTDX simultaneously is valid — strict policies running on
|
||||
// heterogeneous fleets want both axes asserted.
|
||||
type ReleasePolicy struct {
|
||||
RequiredRIM map[[32]byte]struct{}
|
||||
AllowedHardware map[[32]byte]struct{}
|
||||
|
||||
RequireSEVSNP bool // require AMD SEV-SNP CPU evidence (issuer "amd.sev.snp")
|
||||
RequireTDX bool // require Intel TDX CPU evidence (issuer "intel.tdx")
|
||||
RequireNVNRAS bool // require NVIDIA NRAS GPU evidence (issuer "nvidia.nras.v1")
|
||||
}
|
||||
|
||||
// NewReleasePolicy builds a policy from slices of digests, deduplicating
|
||||
// into the allowlist sets.
|
||||
// into the allowlist sets. Require* flags default to false; production
|
||||
// callers MUST use NewReleasePolicyStrict or set the flags by direct
|
||||
// field assignment immediately after this call.
|
||||
func NewReleasePolicy(rims, hardware [][32]byte) ReleasePolicy {
|
||||
p := ReleasePolicy{
|
||||
RequiredRIM: make(map[[32]byte]struct{}, len(rims)),
|
||||
@@ -129,9 +164,27 @@ func NewReleasePolicy(rims, hardware [][32]byte) ReleasePolicy {
|
||||
return p
|
||||
}
|
||||
|
||||
// NewReleasePolicyStrict builds a production-ready policy: RequiredRIM +
|
||||
// AllowedHardware as in NewReleasePolicy, AND RequireSEVSNP +
|
||||
// RequireTDX + RequireNVNRAS all set to true. This is the default-deny
|
||||
// posture mandated for any non-test deployment — every release MUST
|
||||
// present a CPU TEE quote (SEV-SNP and TDX both required for
|
||||
// heterogeneous fleets) and an NVIDIA NRAS GPU report. Tests should
|
||||
// call NewReleasePolicy and leave the flags false.
|
||||
func NewReleasePolicyStrict(rims, hardware [][32]byte) ReleasePolicy {
|
||||
p := NewReleasePolicy(rims, hardware)
|
||||
p.RequireSEVSNP = true
|
||||
p.RequireTDX = true
|
||||
p.RequireNVNRAS = true
|
||||
return p
|
||||
}
|
||||
|
||||
// Permits reports whether the attestation's measurements satisfy the
|
||||
// policy. Membership-check only; nonce + signature verification is the
|
||||
// gate's job.
|
||||
//
|
||||
// Default-deny: when any Require* flag is true and the corresponding
|
||||
// issuer is absent from att.EvidenceIssuers(), the release is refused.
|
||||
func (p ReleasePolicy) Permits(att CompositeAttestation) bool {
|
||||
if len(p.RequiredRIM) == 0 || len(p.AllowedHardware) == 0 {
|
||||
return false
|
||||
@@ -142,9 +195,37 @@ func (p ReleasePolicy) Permits(att CompositeAttestation) bool {
|
||||
if _, ok := p.AllowedHardware[att.HardwareFingerprint()]; !ok {
|
||||
return false
|
||||
}
|
||||
if p.RequireSEVSNP || p.RequireTDX || p.RequireNVNRAS {
|
||||
issuers := issuerSet(att.EvidenceIssuers())
|
||||
if p.RequireSEVSNP {
|
||||
if _, ok := issuers[IssuerSEVSNP]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if p.RequireTDX {
|
||||
if _, ok := issuers[IssuerTDX]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if p.RequireNVNRAS {
|
||||
if _, ok := issuers[IssuerNVNRAS]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// issuerSet folds a slice into a lookup map. Empty / nil input returns
|
||||
// an empty (non-nil) map so subsequent membership checks short-circuit.
|
||||
func issuerSet(in []string) map[string]struct{} {
|
||||
out := make(map[string]struct{}, len(in))
|
||||
for _, s := range in {
|
||||
out[s] = struct{}{}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// SealedSessionKey is the artifact returned to the worker. It contains
|
||||
// the ChaCha20-Poly1305 ciphertext of a freshly-generated 32-byte
|
||||
// session key, sealed under ECDH(gate_ephemeral, tee_pubkey).
|
||||
|
||||
@@ -24,6 +24,11 @@ func x25519Basepoint(priv []byte) ([]byte, error) {
|
||||
// VerifyEvidence simulates the cc/attest chain dispatch. Default is
|
||||
// success. Set failChain=true to force a chain-verify failure — used
|
||||
// by TestRelease_RefusesOnChainFailure.
|
||||
//
|
||||
// issuers is the set of canonical evidence-issuer strings the
|
||||
// attestation envelope claims to carry. Permits() inspects this when
|
||||
// ReleasePolicy.Require* flags are set. nil / empty issuers ⇒ Require*
|
||||
// gates refuse.
|
||||
type fakeAttestation struct {
|
||||
expectNonce [32]byte
|
||||
rim [32]byte
|
||||
@@ -31,6 +36,7 @@ type fakeAttestation struct {
|
||||
teePub [32]byte
|
||||
failVerify bool
|
||||
failChain bool
|
||||
issuers []string
|
||||
}
|
||||
|
||||
func (f *fakeAttestation) Verify(expectedNonce [32]byte) (bool, error) {
|
||||
@@ -49,6 +55,7 @@ func (f *fakeAttestation) VerifyEvidence(ctx context.Context, opts ...attest.Opt
|
||||
func (f *fakeAttestation) RIMDigest() [32]byte { return f.rim }
|
||||
func (f *fakeAttestation) HardwareFingerprint() [32]byte { return f.hw }
|
||||
func (f *fakeAttestation) TEEPublicKey() [32]byte { return f.teePub }
|
||||
func (f *fakeAttestation) EvidenceIssuers() []string { return f.issuers }
|
||||
|
||||
func mustRand(t *testing.T, n int) []byte {
|
||||
t.Helper()
|
||||
@@ -384,3 +391,103 @@ func TestRelease_AcceptsOnChainPass(t *testing.T) {
|
||||
t.Fatalf("Release: expected success, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// newRequireFlagPolicy builds a policy with all three Require* flags set
|
||||
// to true (production posture per #203 O5 fix). Any test that asserts
|
||||
// strict default-deny behavior calls this.
|
||||
func newRequireFlagPolicy(rim, hw [32]byte) ReleasePolicy {
|
||||
return NewReleasePolicyStrict([][32]byte{rim}, [][32]byte{hw})
|
||||
}
|
||||
|
||||
// Test: happy path — SEV-SNP + TDX + NVNRAS all present + valid →
|
||||
// Permits returns true. Asserts the strict policy posture accepts a
|
||||
// fully attested envelope.
|
||||
func TestPermits_RequireFlags_AllPresent(t *testing.T) {
|
||||
var rim, hw, teePub [32]byte
|
||||
copy(rim[:], mustRand(t, 32))
|
||||
copy(hw[:], mustRand(t, 32))
|
||||
copy(teePub[:], mustRand(t, 32))
|
||||
|
||||
policy := newRequireFlagPolicy(rim, hw)
|
||||
att := &fakeAttestation{
|
||||
rim: rim,
|
||||
hw: hw,
|
||||
teePub: teePub,
|
||||
issuers: []string{
|
||||
IssuerSEVSNP,
|
||||
IssuerTDX,
|
||||
IssuerNVNRAS,
|
||||
},
|
||||
}
|
||||
if !policy.Permits(att) {
|
||||
t.Fatalf("Permits: strict policy rejected envelope with all required issuers present")
|
||||
}
|
||||
}
|
||||
|
||||
// Test: missing SEV-SNP when RequireSEVSNP=true → Permits refuses.
|
||||
// Default-deny: a strict policy MUST refuse if the SEV-SNP CPU TEE
|
||||
// quote is absent, even when TDX and NVNRAS are present.
|
||||
func TestPermits_RequireFlags_MissingSEVSNP(t *testing.T) {
|
||||
var rim, hw, teePub [32]byte
|
||||
copy(rim[:], mustRand(t, 32))
|
||||
copy(hw[:], mustRand(t, 32))
|
||||
copy(teePub[:], mustRand(t, 32))
|
||||
|
||||
policy := newRequireFlagPolicy(rim, hw)
|
||||
att := &fakeAttestation{
|
||||
rim: rim,
|
||||
hw: hw,
|
||||
teePub: teePub,
|
||||
issuers: []string{IssuerTDX, IssuerNVNRAS}, // no SEV-SNP
|
||||
}
|
||||
if policy.Permits(att) {
|
||||
t.Fatalf("Permits: strict policy accepted envelope missing SEV-SNP")
|
||||
}
|
||||
}
|
||||
|
||||
// Test: missing TDX when RequireTDX=true → Permits refuses.
|
||||
func TestPermits_RequireFlags_MissingTDX(t *testing.T) {
|
||||
var rim, hw, teePub [32]byte
|
||||
copy(rim[:], mustRand(t, 32))
|
||||
copy(hw[:], mustRand(t, 32))
|
||||
copy(teePub[:], mustRand(t, 32))
|
||||
|
||||
policy := newRequireFlagPolicy(rim, hw)
|
||||
att := &fakeAttestation{
|
||||
rim: rim,
|
||||
hw: hw,
|
||||
teePub: teePub,
|
||||
issuers: []string{IssuerSEVSNP, IssuerNVNRAS}, // no TDX
|
||||
}
|
||||
if policy.Permits(att) {
|
||||
t.Fatalf("Permits: strict policy accepted envelope missing TDX")
|
||||
}
|
||||
}
|
||||
|
||||
// Test: missing NVNRAS when RequireNVNRAS=true → Permits refuses.
|
||||
// Empty issuer list also tests the nil-slice path: Permits MUST treat
|
||||
// an attestation that publishes no evidence issuers as a hard refusal
|
||||
// under any Require* flag.
|
||||
func TestPermits_RequireFlags_MissingNVNRAS(t *testing.T) {
|
||||
var rim, hw, teePub [32]byte
|
||||
copy(rim[:], mustRand(t, 32))
|
||||
copy(hw[:], mustRand(t, 32))
|
||||
copy(teePub[:], mustRand(t, 32))
|
||||
|
||||
policy := newRequireFlagPolicy(rim, hw)
|
||||
att := &fakeAttestation{
|
||||
rim: rim,
|
||||
hw: hw,
|
||||
teePub: teePub,
|
||||
issuers: []string{IssuerSEVSNP, IssuerTDX}, // no NVNRAS
|
||||
}
|
||||
if policy.Permits(att) {
|
||||
t.Fatalf("Permits: strict policy accepted envelope missing NVNRAS")
|
||||
}
|
||||
|
||||
// Belt-and-suspenders: empty issuer list ⇒ refuse.
|
||||
att2 := &fakeAttestation{rim: rim, hw: hw, teePub: teePub, issuers: nil}
|
||||
if policy.Permits(att2) {
|
||||
t.Fatalf("Permits: strict policy accepted envelope with no issuers at all")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user