Merge release-policy-require-flags-2026-04-28

This commit is contained in:
2026-04-28 09:30:21 -07:00
2 changed files with 189 additions and 1 deletions
+82 -1
View File
@@ -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).
+107
View File
@@ -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")
}
}