148 lines
4.2 KiB
Go
148 lines
4.2 KiB
Go
|
|
package policy
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"path/filepath"
|
||
|
|
"strings"
|
||
|
|
"testing"
|
||
|
|
)
|
||
|
|
|
||
|
|
// loadRealPolicy compiles the committed policies/sinks/signing_service.rego
|
||
|
|
// so the tests fail if we rename the package or break the file.
|
||
|
|
func loadRealPolicy(t *testing.T) *OPAEvaluator {
|
||
|
|
t.Helper()
|
||
|
|
// From services/signing-service, walk up to repo root (two parents),
|
||
|
|
// then into policies/sinks/signing_service.rego.
|
||
|
|
p := filepath.Join("..", "..", "..", "..", "policies", "sinks", "signing_service.rego")
|
||
|
|
opa, err := NewOPAEvaluator(context.Background(), p)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("load policy: %v", err)
|
||
|
|
}
|
||
|
|
return opa
|
||
|
|
}
|
||
|
|
|
||
|
|
func makeInput() Input {
|
||
|
|
by := InputRegistryRow{
|
||
|
|
AgentID: "agent-approval-service",
|
||
|
|
PocketIDClient: "agent-approval-service",
|
||
|
|
K8sNamespace: "agent-trust",
|
||
|
|
K8sServiceAccount: "approval-service",
|
||
|
|
AllowedActions: []string{"expense.submit"},
|
||
|
|
}
|
||
|
|
return Input{
|
||
|
|
PocketIDClient: "agent-approval-service",
|
||
|
|
K8s: InputK8s{
|
||
|
|
Namespace: "agent-trust",
|
||
|
|
ServiceAccount: "approval-service",
|
||
|
|
},
|
||
|
|
KeyRef: "trust-api/approval-signer-2026-q4",
|
||
|
|
Registry: InputRegistry{
|
||
|
|
ByPocketID: &by,
|
||
|
|
ByK8s: &by,
|
||
|
|
},
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestOPA_Allows_ApprovalService_HappyPath(t *testing.T) {
|
||
|
|
opa := loadRealPolicy(t)
|
||
|
|
dec, err := opa.Decide(context.Background(), makeInput())
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("Decide: %v", err)
|
||
|
|
}
|
||
|
|
if !dec.Allow {
|
||
|
|
t.Fatalf("expected allow, got deny: %q", dec.Reason)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestOPA_Allows_TestAgentTransform_HappyPath(t *testing.T) {
|
||
|
|
opa := loadRealPolicy(t)
|
||
|
|
by := InputRegistryRow{
|
||
|
|
AgentID: "agent-testagent-transform",
|
||
|
|
PocketIDClient: "agent-testagent-transform",
|
||
|
|
K8sNamespace: "agent-trust",
|
||
|
|
K8sServiceAccount: "testagent-transform",
|
||
|
|
}
|
||
|
|
in := Input{
|
||
|
|
PocketIDClient: "agent-testagent-transform",
|
||
|
|
K8s: InputK8s{Namespace: "agent-trust", ServiceAccount: "testagent-transform"},
|
||
|
|
KeyRef: "trust-api/agent-testagent-transform-2026-q4",
|
||
|
|
Registry: InputRegistry{ByPocketID: &by, ByK8s: &by},
|
||
|
|
}
|
||
|
|
dec, err := opa.Decide(context.Background(), in)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("Decide: %v", err)
|
||
|
|
}
|
||
|
|
if !dec.Allow {
|
||
|
|
t.Fatalf("expected allow, got deny: %q", dec.Reason)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestOPA_Denies_RegistryMismatch(t *testing.T) {
|
||
|
|
opa := loadRealPolicy(t)
|
||
|
|
in := makeInput()
|
||
|
|
// By-K8s returns a DIFFERENT agent row than By-PocketID — the double
|
||
|
|
// identity check must deny.
|
||
|
|
byK := InputRegistryRow{
|
||
|
|
AgentID: "agent-testagent-transform",
|
||
|
|
PocketIDClient: "agent-testagent-transform",
|
||
|
|
K8sNamespace: "agent-trust",
|
||
|
|
K8sServiceAccount: "testagent-transform",
|
||
|
|
}
|
||
|
|
in.Registry.ByK8s = &byK
|
||
|
|
dec, err := opa.Decide(context.Background(), in)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("Decide: %v", err)
|
||
|
|
}
|
||
|
|
if dec.Allow {
|
||
|
|
t.Fatalf("expected deny on registry mismatch, got allow")
|
||
|
|
}
|
||
|
|
if !strings.Contains(dec.Reason, "do not resolve to the same registry agent") {
|
||
|
|
t.Errorf("unexpected reason: %q", dec.Reason)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestOPA_Denies_SubjectMismatchFromCaller(t *testing.T) {
|
||
|
|
opa := loadRealPolicy(t)
|
||
|
|
in := makeInput()
|
||
|
|
// Caller presented a different Pocket-ID subject than the one stored
|
||
|
|
// in the registry row — the policy must deny because the row no longer
|
||
|
|
// matches the authenticated identity.
|
||
|
|
in.PocketIDClient = "spoofed-client"
|
||
|
|
dec, err := opa.Decide(context.Background(), in)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("Decide: %v", err)
|
||
|
|
}
|
||
|
|
if dec.Allow {
|
||
|
|
t.Fatalf("expected deny when caller subject mismatches registry, got allow")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestOPA_Denies_WrongKeyRefForRole(t *testing.T) {
|
||
|
|
opa := loadRealPolicy(t)
|
||
|
|
in := makeInput()
|
||
|
|
in.KeyRef = "trust-api/agent-testagent-transform-2026-q4" // belongs to other agent
|
||
|
|
dec, err := opa.Decide(context.Background(), in)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("Decide: %v", err)
|
||
|
|
}
|
||
|
|
if dec.Allow {
|
||
|
|
t.Fatalf("expected deny on wrong key_ref, got allow")
|
||
|
|
}
|
||
|
|
if !strings.Contains(dec.Reason, "not permitted to sign") {
|
||
|
|
t.Errorf("unexpected reason: %q", dec.Reason)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestOPA_Denies_NilRegistryRow(t *testing.T) {
|
||
|
|
opa := loadRealPolicy(t)
|
||
|
|
in := makeInput()
|
||
|
|
in.Registry.ByK8s = nil
|
||
|
|
dec, err := opa.Decide(context.Background(), in)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("Decide: %v", err)
|
||
|
|
}
|
||
|
|
if dec.Allow {
|
||
|
|
t.Fatalf("expected deny on missing registry row, got allow")
|
||
|
|
}
|
||
|
|
}
|