mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-11 08:23:45 +00:00
feat: validate workload-bound result credentials
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -57,12 +57,13 @@ func TestOfflineMatchmakingPipelineReachesDurableResult(t *testing.T) {
|
||||
t.Fatalf("assignment = %+v err=%v", assignment, err)
|
||||
}
|
||||
|
||||
store, err := domain.NewResultStore(domain.WorkloadBinding{Issuer: "issuer", Audience: "audience", Namespace: "games", ServiceAcct: "match-server", PodUID: "pod-1", GameServerUID: "gs-1", MatchID: allocation.MatchID, ServerID: allocation.ServerID})
|
||||
binding := domain.WorkloadBinding{Issuer: "issuer", Audience: "audience", Namespace: "games", ServiceAcct: "match-server", PodUID: "pod-1", GameServerUID: "gs-1", AllocationID: allocation.AllocationID, MatchID: allocation.MatchID, ServerID: allocation.ServerID}
|
||||
store, err := domain.NewResultStore(binding)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
result := domain.MatchResult{MatchID: allocation.MatchID, ServerID: allocation.ServerID, ResultNonce: "result-nonce-123456", Team0Score: 3, Team1Score: 2, IntegrityState: domain.IntegrityCertified}
|
||||
receipt, created, err := store.Submit("result-1234567890123456", result, domain.WorkloadBinding{Issuer: "issuer", Audience: "audience", Namespace: "games", ServiceAcct: "match-server", PodUID: "pod-1", GameServerUID: "gs-1", MatchID: allocation.MatchID, ServerID: allocation.ServerID}, now)
|
||||
receipt, created, err := store.Submit("result-1234567890123456", result, binding, now)
|
||||
if err != nil || !created || !domain.RatingEligible(receipt) {
|
||||
t.Fatalf("receipt = %+v created=%v err=%v", receipt, created, err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user