kms/release: add Require* flags mirroring AttestationBaseline (#203 O5)

Per LP-137 §2.7, ReleasePolicy now exposes RequireSEVSNP / RequireTDX /
RequireNVNRAS boolean flags matching the C++ AttestationBaseline.require_*
pattern. Closes the asymmetry where the C++ side enforced explicit issuer
presence but the Go side only checked RIM + AllowedHardware membership.

Permits() default-deny: if a flag is true and the corresponding canonical
issuer string ("amd.sev.snp", "intel.tdx", "nvidia.nras.v1") is absent
from att.EvidenceIssuers(), the policy refuses release.

NewReleasePolicyStrict bundles the production posture (all three flags
true) so deployments cannot accidentally ship a wide-open policy. Tests
opt out via NewReleasePolicy + leaving the flags false.

Interface change: CompositeAttestation gains EvidenceIssuers() []string.
Wire-stable strings exported as IssuerSEVSNP / IssuerTDX / IssuerNVNRAS
matching cc/attest dispatch keys.

Tests: 4 new cases - happy path (all 3 issuers present + valid),
missing SEV-SNP, missing TDX, missing NVNRAS (incl. nil-issuer edge).
All 19 pkg/kms tests pass with -race -count=1 -short -timeout 60s.
This commit is contained in:
2026-04-28 08:37:19 -07:00
parent b87fe045b8
commit c6bd028e90
2 changed files with 189 additions and 1 deletions
+82 -1
View File
@@ -74,21 +74,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)),
@@ -103,9 +138,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
@@ -116,9 +169,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
@@ -17,12 +17,18 @@ func x25519Basepoint(priv []byte) ([]byte, error) {
// fakeAttestation is a deterministic stub. Verify() succeeds iff
// expectedNonce equals the nonce captured at construction. RIM and
// hardware are caller-supplied so the test exercises Permits().
//
// 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
hw [32]byte
teePub [32]byte
failVerify bool
issuers []string
}
func (f *fakeAttestation) Verify(expectedNonce [32]byte) (bool, error) {
@@ -34,6 +40,7 @@ func (f *fakeAttestation) Verify(expectedNonce [32]byte) (bool, error) {
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()
@@ -320,3 +327,103 @@ func TestAAD_BindsIssuedNonce(t *testing.T) {
t.Fatalf("AAD did not bind IssuedNonce (collision)")
}
}
// 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")
}
}