access-engine/internal/policy/informed_decision_attribute_test.go
tegwick f32a1d4dc6
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Build and Publish Container Image / build-and-push (push) Successful in 50s
Pin the sitting release to the v10 CI image of 58026ea
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 692292@bnt-lap001
Assistant-Session: 89c7d8e8-e1b3-47eb-9411-a0ab227fca2f
2026-10-04 15:04:53 +02:00

255 lines
7.7 KiB
Go

package policy_test
import (
"bytes"
"context"
"encoding/json"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"time"
"github.com/netkingdom/flex-auth/internal/policy"
"github.com/netkingdom/flex-auth/pkg/api"
)
func TestInformedDecisionAttributeAdmission(t *testing.T) {
pkg, err := policy.LoadAndValidateFile(context.Background(), sittingPolicyPath(t))
if err != nil {
t.Fatalf("LoadAndValidateFile: %v", err)
}
if !pkg.Valid {
t.Fatalf("package invalid: %+v", pkg.Validation)
}
if pkg.Metadata.Version != "v10" {
t.Fatalf("version = %s; want v10", pkg.Metadata.Version)
}
if strings.Contains(pkg.RegoModule, "view_hash") {
t.Fatal("rule module reads view_hash")
}
if !strings.Contains(pkg.RegoModule, "admitted_submitters := set()") || !strings.Contains(pkg.RegoModule, "admitted_classes := set()") {
t.Fatal("shipped admitted sets are not empty")
}
raw, err := os.ReadFile(filepath.Join(sittingDir(t), "attribute_admission_v8.json"))
if err != nil {
t.Fatal(err)
}
var doc struct {
PolicyVersion string `json:"policy_version"`
Cases []struct {
ID string `json:"id"`
Kind string `json:"kind"`
Assurance string `json:"assurance"`
Request api.CheckRequest `json:"request"`
Expect api.DecisionExpectation `json:"expect"`
} `json:"cases"`
}
if err := json.Unmarshal(raw, &doc); err != nil {
t.Fatal(err)
}
if doc.PolicyVersion != "v10" {
t.Fatalf("fixture policy_version = %s", doc.PolicyVersion)
}
exactKeys := map[string]bool{"memo_version": true, "approval_id": true, "approval_binding_digest": true}
attributeKeys := map[string]bool{
"review_group": true, "memo_class": true, "intake_submitter": true,
"approval_requester": true, "approval_human_control": true, "memo_version": true,
"approval_id": true, "approval_binding_digest": true,
}
for _, fixture := range doc.Cases {
request := fixture.Request
stampAssurance(t, &request, fixture.Assurance)
if _, ok := request.Context["view_hash"]; ok {
t.Fatalf("%s carries view_hash", fixture.ID)
}
switch fixture.Kind {
case "exact":
assertKeys(t, fixture.ID, request.Context, exactKeys)
case "attribute":
assertKeys(t, fixture.ID, request.Context, attributeKeys)
default:
t.Fatalf("%s kind %q", fixture.ID, fixture.Kind)
}
got, err := pkg.Evaluate(context.Background(), request)
if err != nil {
t.Fatalf("%s: %v", fixture.ID, err)
}
if got.Effect != fixture.Expect.Effect || got.Reason != fixture.Expect.Reason {
t.Fatalf("%s: got %s %s; want %s %s", fixture.ID, got.Effect, got.Reason, fixture.Expect.Effect, fixture.Expect.Reason)
}
if fixture.ID == "exact-record-allow" {
withHash := request
withHash.Context = copyContext(request.Context)
withHash.Context["view_hash"] = "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
hashed, err := pkg.Evaluate(context.Background(), withHash)
if err != nil {
t.Fatal(err)
}
if hashed.Effect != got.Effect || hashed.Reason != got.Reason {
t.Fatalf("view_hash changed exact decision to %s %s", hashed.Effect, hashed.Reason)
}
}
}
}
func TestSittingImagePinMatchesRecordedBuild(t *testing.T) {
raw, err := os.ReadFile(filepath.Join(repoRoot(t), "values", "informed-decision-sitting.yaml"))
if err != nil {
t.Fatal(err)
}
text := string(raw)
// values/informed-decision-sitting.yaml records the v10 image of 58026ea.
// The earlier v9 pin (103d8b6) is no longer the baseline.
if !strings.Contains(text, "sha256:5707748924f255e5ef9f50542fec5defee16294c062762466ea7a4e33a363f04") {
t.Fatal("sitting image digest differs from the recorded v10 build")
}
if !strings.Contains(text, "/opt/flex-auth/examples/informed-decision-sitting/policy.md") {
t.Fatal("sitting policy path changed")
}
}
func TestAdmissionSetGenerator(t *testing.T) {
root := repoRoot(t)
check := exec.Command("python3", "tools/gen_informed_decision_admission.py", "--check")
check.Dir = root
if out, err := check.CombinedOutput(); err != nil {
t.Fatalf("drift check: %v\n%s", err, out)
}
dir := t.TempDir()
policyPath := filepath.Join(dir, "policy.md")
shipped := sittingPolicyPath(t)
source, err := os.ReadFile(shipped)
if err != nil {
t.Fatal(err)
}
if err := os.WriteFile(policyPath, source, 0o644); err != nil {
t.Fatal(err)
}
registry := filepath.Join(dir, "submitters.json")
body := `{
"system:serviceaccount:state-hub:state-hub": {
"submitter_id": "state-hub",
"memo_classes": ["credential-lane", "adr-accept"],
"review_groups": ["net-kingdom-admins"],
"daily_limit": 1
}
}`
if err := os.WriteFile(registry, []byte(body), 0o644); err != nil {
t.Fatal(err)
}
drift := exec.Command("python3", filepath.Join(root, "tools/gen_informed_decision_admission.py"),
"--source", registry, "--policy", policyPath, "--check")
if err := drift.Run(); err == nil {
t.Fatal("non-empty registry matched the empty shipped sets")
}
after, err := os.ReadFile(policyPath)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(after, source) {
t.Fatal("--check wrote the policy")
}
write := exec.Command("python3", filepath.Join(root, "tools/gen_informed_decision_admission.py"),
"--source", registry, "--policy", policyPath)
if out, err := write.CombinedOutput(); err != nil {
t.Fatalf("generate: %v\n%s", err, out)
}
generated, err := os.ReadFile(policyPath)
if err != nil {
t.Fatal(err)
}
text := string(generated)
if !strings.Contains(text, `"adr-accept",`) || !strings.Contains(text, `"credential-lane",`) || !strings.Contains(text, `"state-hub",`) {
t.Fatalf("generated sets missing:\n%s", text)
}
if !bytes.Equal(mustRead(t, shipped), source) {
t.Fatal("generator wrote the shipped policy")
}
again := exec.Command("python3", filepath.Join(root, "tools/gen_informed_decision_admission.py"),
"--source", registry, "--policy", policyPath, "--check")
if out, err := again.CombinedOutput(); err != nil {
t.Fatalf("regenerated policy drifted: %v\n%s", err, out)
}
bad := filepath.Join(dir, "bad.json")
if err := os.WriteFile(bad, []byte("[]"), 0o644); err != nil {
t.Fatal(err)
}
refused := exec.Command("python3", filepath.Join(root, "tools/gen_informed_decision_admission.py"),
"--source", bad, "--policy", policyPath)
if err := refused.Run(); err == nil {
t.Fatal("malformed registry was accepted")
}
kept, err := os.ReadFile(policyPath)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(kept, generated) {
t.Fatal("refused generate changed the policy")
}
}
func stampAssurance(t *testing.T, request *api.CheckRequest, mode string) {
t.Helper()
assurance, _ := request.Subject.Attributes["assurance"].(map[string]any)
if assurance == nil {
t.Fatal("assurance missing")
}
switch mode {
case "now":
assurance["at"] = time.Now().Unix()
case "stale":
assurance["at"] = int64(1)
default:
t.Fatalf("assurance mode %q", mode)
}
}
func assertKeys(t *testing.T, id string, ctx map[string]any, want map[string]bool) {
t.Helper()
if len(ctx) != len(want) {
t.Fatalf("%s context keys = %v", id, ctx)
}
for key := range want {
if _, ok := ctx[key]; !ok {
t.Fatalf("%s missing %s", id, key)
}
}
}
func copyContext(in map[string]any) map[string]any {
out := make(map[string]any, len(in)+1)
for key, value := range in {
out[key] = value
}
return out
}
func sittingDir(t *testing.T) string {
t.Helper()
return filepath.Join(repoRoot(t), "examples", "informed-decision-sitting")
}
func sittingPolicyPath(t *testing.T) string {
t.Helper()
return filepath.Join(sittingDir(t), "policy.md")
}
func repoRoot(t *testing.T) string {
t.Helper()
return filepath.Join("..", "..")
}
func mustRead(t *testing.T, path string) []byte {
t.Helper()
data, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
return data
}