feat: validate workload-bound result credentials

This commit is contained in:
Josh Creek
2026-08-31 21:20:12 +01:00
parent 8d1b407bb0
commit 79e66c7a95
7 changed files with 149 additions and 8 deletions
+2 -1
View File
@@ -55,6 +55,7 @@ type WorkloadBinding struct {
ServiceAcct string
PodUID string
GameServerUID string
AllocationID string
MatchID string
ServerID string
}
@@ -169,7 +170,7 @@ func RatingEligible(receipt ResultReceipt) bool {
}
func validateBinding(binding WorkloadBinding) error {
if binding.Issuer == "" || binding.Audience == "" || binding.Namespace == "" || binding.ServiceAcct == "" || binding.PodUID == "" || binding.GameServerUID == "" || binding.MatchID == "" || binding.ServerID == "" {
if binding.Issuer == "" || binding.Audience == "" || binding.Namespace == "" || binding.ServiceAcct == "" || binding.PodUID == "" || binding.GameServerUID == "" || binding.AllocationID == "" || binding.MatchID == "" || binding.ServerID == "" {
return ErrResultBinding
}
return nil
+1 -1
View File
@@ -7,7 +7,7 @@ import (
)
func testBinding() WorkloadBinding {
return WorkloadBinding{Issuer: "https://issuer", Audience: "cosmic-result", Namespace: "games", ServiceAcct: "match-server", PodUID: "pod-1", GameServerUID: "gs-1", MatchID: "match-1", ServerID: "server-1"}
return WorkloadBinding{Issuer: "https://issuer", Audience: "cosmic-result", Namespace: "games", ServiceAcct: "match-server", PodUID: "pod-1", GameServerUID: "gs-1", AllocationID: "allocation-1", MatchID: "match-1", ServerID: "server-1"}
}
func testResult() MatchResult {
+58
View File
@@ -0,0 +1,58 @@
package domain
import (
"fmt"
"time"
)
// WorkloadCredential is the claim set extracted from a projected service
// account token or a one-match attested credential. Signature verification is
// deliberately supplied by the adapter: the domain must not depend on a JWT
// library or trust claims before the secure boundary has verified them.
type WorkloadCredential struct {
Issuer string
Audience string
IssuedAt time.Time
ExpiresAt time.Time
Namespace string
ServiceAcct string
PodUID string
GameServerUID string
AllocationID string
MatchID string
ServerID string
Signature []byte
}
// WorkloadCredentialPolicy defines the exact one-allocation identity a result
// credential must carry. It is intentionally immutable after construction.
type WorkloadCredentialPolicy struct {
expected WorkloadBinding
verify func(WorkloadCredential) bool
}
var ErrWorkloadCredential = fmt.Errorf("workload credential rejected")
func NewWorkloadCredentialPolicy(expected WorkloadBinding, verify func(WorkloadCredential) bool) (*WorkloadCredentialPolicy, error) {
if err := validateBinding(expected); err != nil || verify == nil {
return nil, ErrWorkloadCredential
}
return &WorkloadCredentialPolicy{expected: expected, verify: verify}, nil
}
// Validate returns the binding only after every claim has matched the
// allocation and the adapter has accepted the credential's signature.
func (p *WorkloadCredentialPolicy) Validate(credential WorkloadCredential, now time.Time) (WorkloadBinding, error) {
if p == nil || len(credential.Signature) == 0 || p.verify == nil || !p.verify(credential) {
return WorkloadBinding{}, ErrWorkloadCredential
}
if credential.Issuer != p.expected.Issuer || credential.Audience != p.expected.Audience ||
credential.Namespace != p.expected.Namespace || credential.ServiceAcct != p.expected.ServiceAcct ||
credential.PodUID != p.expected.PodUID || credential.GameServerUID != p.expected.GameServerUID ||
credential.AllocationID != p.expected.AllocationID || credential.MatchID != p.expected.MatchID ||
credential.ServerID != p.expected.ServerID || credential.IssuedAt.IsZero() || credential.ExpiresAt.IsZero() ||
!credential.IssuedAt.Before(credential.ExpiresAt) || now.Before(credential.IssuedAt) || !now.Before(credential.ExpiresAt) {
return WorkloadBinding{}, ErrWorkloadCredential
}
return p.expected, nil
}
+80
View File
@@ -0,0 +1,80 @@
package domain
import (
"errors"
"testing"
"time"
)
func testCredential(binding WorkloadBinding, now time.Time) WorkloadCredential {
return WorkloadCredential{
Issuer: binding.Issuer, Audience: binding.Audience, IssuedAt: now.Add(-time.Minute), ExpiresAt: now.Add(time.Minute),
Namespace: binding.Namespace, ServiceAcct: binding.ServiceAcct, PodUID: binding.PodUID,
GameServerUID: binding.GameServerUID, AllocationID: binding.AllocationID, MatchID: binding.MatchID,
ServerID: binding.ServerID, Signature: []byte("attestation"),
}
}
func TestWorkloadCredentialValidatesOneAllocationIdentity(t *testing.T) {
now := time.Unix(1000, 0).UTC()
binding := testBinding()
policy, err := NewWorkloadCredentialPolicy(binding, func(credential WorkloadCredential) bool {
return string(credential.Signature) == "attestation"
})
if err != nil {
t.Fatal(err)
}
got, err := policy.Validate(testCredential(binding, now), now)
if err != nil || got != binding {
t.Fatalf("valid credential = %+v, err=%v", got, err)
}
}
func TestWorkloadCredentialRejectsEveryBindingAndTimeMutation(t *testing.T) {
now := time.Unix(1000, 0).UTC()
binding := testBinding()
policy, _ := NewWorkloadCredentialPolicy(binding, func(credential WorkloadCredential) bool { return true })
mutate := []func(*WorkloadCredential){
func(c *WorkloadCredential) { c.Issuer = "other" },
func(c *WorkloadCredential) { c.Audience = "other" },
func(c *WorkloadCredential) { c.Namespace = "other" },
func(c *WorkloadCredential) { c.ServiceAcct = "other" },
func(c *WorkloadCredential) { c.PodUID = "other" },
func(c *WorkloadCredential) { c.GameServerUID = "other" },
func(c *WorkloadCredential) { c.AllocationID = "other" },
func(c *WorkloadCredential) { c.MatchID = "other" },
func(c *WorkloadCredential) { c.ServerID = "other" },
func(c *WorkloadCredential) { c.ExpiresAt = now },
func(c *WorkloadCredential) { c.IssuedAt = now.Add(time.Second) },
}
for i, change := range mutate {
credential := testCredential(binding, now)
change(&credential)
if _, err := policy.Validate(credential, now); !errors.Is(err, ErrWorkloadCredential) {
t.Fatalf("mutation %d accepted: %v", i, err)
}
}
badSignature, _ := NewWorkloadCredentialPolicy(binding, func(WorkloadCredential) bool { return false })
if _, err := badSignature.Validate(testCredential(binding, now), now); !errors.Is(err, ErrWorkloadCredential) {
t.Fatalf("unverified signature accepted: %v", err)
}
}
func TestWorkloadCredentialRejectsMissingClaimsAndBoundaryExpiry(t *testing.T) {
now := time.Unix(1000, 0).UTC()
binding := testBinding()
policy, _ := NewWorkloadCredentialPolicy(binding, func(WorkloadCredential) bool { return true })
credential := testCredential(binding, now)
credential.Signature = nil
if _, err := policy.Validate(credential, now); !errors.Is(err, ErrWorkloadCredential) {
t.Fatalf("missing signature accepted: %v", err)
}
credential = testCredential(binding, now)
if _, err := policy.Validate(credential, credential.ExpiresAt); !errors.Is(err, ErrWorkloadCredential) {
t.Fatalf("expiry boundary accepted: %v", err)
}
credential = testCredential(binding, now)
if _, err := policy.Validate(credential, credential.IssuedAt.Add(-time.Nanosecond)); !errors.Is(err, ErrWorkloadCredential) {
t.Fatalf("not-before boundary accepted: %v", err)
}
}