- module path renamed (no external importers); golangci local-prefixes - ACCESS_ENGINE_RUN_TOPAZ_INTEGRATION replaces FLEX_AUTH_RUN_TOPAZ_INTEGRATION - README title and identity paragraph reflect the landed renames - docs/runtime-rename-inventory.md: per-consumer references by kind and order - chart name, image package and /opt paths deliberately left for T05 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 735967@bnt-lap001 Assistant-Session: 5971967f-77f8-4ced-9e02-c6ef67040ffc
160 lines
7.8 KiB
Go
160 lines
7.8 KiB
Go
package policy_test
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/netkingdom/access-engine/internal/policy"
|
|
"github.com/netkingdom/access-engine/pkg/api"
|
|
)
|
|
|
|
func TestInformedDecisionLifecycle(t *testing.T) {
|
|
pkg, err := policy.LoadAndValidateFile(context.Background(), filepath.Join("..", "..", "examples", "informed-decision-lifecycle", "policy.md"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !pkg.Valid {
|
|
t.Fatalf("invalid package: %+v", pkg.Validation)
|
|
}
|
|
base := map[string]any{
|
|
"id": "fixture", "tenant": "tenant:platform", "policy_version": "v1",
|
|
"subject": map[string]any{"id": "human-one", "type": "human", "tenant": "tenant:platform", "attributes": map[string]any{
|
|
"principal_type_source": "authentication-derived", "tenant_source": "registration-supplied", "groups": []any{"net-kingdom-admins"},
|
|
"assurance": map[string]any{"level": "aal2", "mfa": true, "source": "key-cape", "methods": []any{"pwd", "otp"}, "at": time.Now().Unix()},
|
|
}},
|
|
"resource": map[string]any{"id": "memo:new-record", "type": "decision-memo", "system": "informed-decision", "tenant": "tenant:platform"},
|
|
"action": "lifecycle.register",
|
|
"context": map[string]any{"contract": "informed-decision.lifecycle.v1", "memo_version": 1, "command_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "record": map[string]any{"registered": false}},
|
|
}
|
|
human := func(d map[string]any) map[string]any { return d["subject"].(map[string]any) }
|
|
ctx := func(d map[string]any) map[string]any { return d["context"].(map[string]any) }
|
|
attrs := func(d map[string]any) map[string]any { return human(d)["attributes"].(map[string]any) }
|
|
owned := func(d map[string]any) {
|
|
ctx(d)["record"] = map[string]any{"registered": true, "registered_by": map[string]any{"sub": "human-one", "kind": "person"}, "revision_by": nil, "published_audience": nil, "current_version": 1}
|
|
d["action"] = "lifecycle.read"
|
|
}
|
|
hub := func(d map[string]any) {
|
|
human(d)["id"] = "system:serviceaccount:state-hub:state-hub"
|
|
human(d)["type"] = "service"
|
|
human(d)["attributes"] = map[string]any{"principal_type_source": "authentication-derived", "tenant_source": "registration-supplied", "workload_uid": "verified-uid", "authentication_audience": "informed-decision-lifecycle"}
|
|
}
|
|
// FLEX-WP-0035-T05: the readback asks for the application, not a memo.
|
|
app := func(d map[string]any) {
|
|
d["action"] = "application.access"
|
|
d["purpose"] = "readback"
|
|
d["resource"] = map[string]any{"id": "informed-decision", "type": "application", "system": "informed-decision"}
|
|
delete(d, "context")
|
|
delete(attrs(d), "principal_type_source")
|
|
delete(attrs(d), "tenant_source")
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
effect string
|
|
change func(map[string]any)
|
|
}{
|
|
{"readback application access", "allow", app},
|
|
{"application access without readback purpose", "deny", func(d map[string]any) { app(d); delete(d, "purpose") }},
|
|
{"workload application access", "deny", func(d map[string]any) { hub(d); app(d) }},
|
|
{"application access outside group", "deny", func(d map[string]any) { app(d); attrs(d)["groups"] = []any{"other"} }},
|
|
{"application access stale MFA", "deny", func(d map[string]any) {
|
|
app(d)
|
|
attrs(d)["assurance"].(map[string]any)["at"] = time.Now().Unix() - 43201
|
|
}},
|
|
{"application access without otp", "deny", func(d map[string]any) { app(d); attrs(d)["assurance"].(map[string]any)["methods"] = []any{"pwd"} }},
|
|
{"application access other application", "deny", func(d map[string]any) {
|
|
app(d)
|
|
d["resource"].(map[string]any)["id"] = "markitect"
|
|
}},
|
|
{"application access foreign tenant", "deny", func(d map[string]any) { app(d); d["tenant"] = "tenant:foreign" }},
|
|
{"human registration", "allow", func(d map[string]any) {}},
|
|
{"workload registration", "allow", hub},
|
|
{"foreign workload", "deny", func(d map[string]any) { hub(d); human(d)["id"] = "system:serviceaccount:foreign:foreign" }},
|
|
{"wrong workload audience", "deny", func(d map[string]any) { hub(d); attrs(d)["authentication_audience"] = "informed-decision-intake" }},
|
|
{"missing workload UID", "deny", func(d map[string]any) { hub(d); delete(attrs(d), "workload_uid") }},
|
|
{"wrong tenant", "deny", func(d map[string]any) { d["tenant"] = "tenant:foreign" }},
|
|
{"outside human group", "deny", func(d map[string]any) { attrs(d)["groups"] = []any{"other"} }},
|
|
{"stale author MFA", "deny", func(d map[string]any) { attrs(d)["assurance"].(map[string]any)["at"] = time.Now().Unix() - 43201 }},
|
|
{"future author MFA", "deny", func(d map[string]any) { attrs(d)["assurance"].(map[string]any)["at"] = time.Now().Unix() + 301 }},
|
|
{"asserted identity", "deny", func(d map[string]any) { attrs(d)["principal_type_source"] = "asserted" }},
|
|
{"owner read", "allow", owned},
|
|
{"foreign unpublished read", "deny", func(d map[string]any) { owned(d); human(d)["id"] = "another-admin" }},
|
|
{"published collaborator", "allow", func(d map[string]any) {
|
|
owned(d)
|
|
human(d)["id"] = "another-admin"
|
|
ctx(d)["record"].(map[string]any)["published_audience"] = map[string]any{"subjects": []any{}, "groups": []any{"net-kingdom-admins"}}
|
|
}},
|
|
{"new revision editor", "allow", func(d map[string]any) {
|
|
owned(d)
|
|
human(d)["id"] = "editor"
|
|
ctx(d)["record"].(map[string]any)["revision_by"] = map[string]any{"sub": "editor", "kind": "person"}
|
|
}},
|
|
{"unknown action", "deny", func(d map[string]any) { owned(d); d["action"] = "lifecycle.delete" }},
|
|
{"agent acceptance", "deny", func(d map[string]any) { hub(d); d["action"] = "accept" }},
|
|
{"human acceptance excluded", "deny", func(d map[string]any) { owned(d); d["action"] = "lifecycle.accept" }},
|
|
{"publish initial group", "allow", func(d map[string]any) {
|
|
owned(d)
|
|
d["action"] = "lifecycle.publish"
|
|
ctx(d)["publication_audience"] = map[string]any{"subjects": []any{}, "groups": []any{"net-kingdom-admins"}}
|
|
}},
|
|
{"publish other group", "deny", func(d map[string]any) {
|
|
owned(d)
|
|
d["action"] = "lifecycle.publish"
|
|
ctx(d)["publication_audience"] = map[string]any{"subjects": []any{}, "groups": []any{"other"}}
|
|
}},
|
|
{"review routing", "allow", func(d map[string]any) {
|
|
owned(d)
|
|
d["action"] = "lifecycle.request_review"
|
|
ctx(d)["review_routing"] = map[string]any{"submitter": "state-hub", "memo_class": "decision-record", "review_group": "net-kingdom-admins"}
|
|
}},
|
|
{"wrong review routing", "deny", func(d map[string]any) {
|
|
owned(d)
|
|
d["action"] = "lifecycle.request_review"
|
|
ctx(d)["review_routing"] = map[string]any{"submitter": "unknown", "memo_class": "decision-record", "review_group": "net-kingdom-admins"}
|
|
}},
|
|
}
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
encoded, _ := json.Marshal(base)
|
|
var d map[string]any
|
|
json.Unmarshal(encoded, &d)
|
|
tc.change(d)
|
|
encoded, _ = json.Marshal(d)
|
|
var request api.CheckRequest
|
|
if err := json.Unmarshal(encoded, &request); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, err := pkg.Evaluate(context.Background(), request)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if string(got.Effect) != tc.effect {
|
|
t.Fatalf("got %+v, want %s", got, tc.effect)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestInformedDecisionLifecycleReleasePins(t *testing.T) {
|
|
for _, name := range []string{"informed-decision-lifecycle", "informed-decision-lifecycle-review"} {
|
|
dir := filepath.Join(repoRoot(t), "examples", name)
|
|
pkg, err := policy.LoadAndValidateFile(context.Background(), filepath.Join(dir, "policy.md"))
|
|
if err != nil || !pkg.Valid {
|
|
t.Fatalf("%s invalid: %v %+v", name, err, pkg)
|
|
}
|
|
raw, err := os.ReadFile(filepath.Join(dir, "pin.json"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var pin map[string]string
|
|
if err := json.Unmarshal(raw, &pin); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if pin["package_digest"] != pkg.Digest() || pin["version"] != pkg.Metadata.Version || pin["package"] != pkg.Metadata.ID {
|
|
t.Fatalf("%s stale pin: %+v", name, pin)
|
|
}
|
|
}
|
|
}
|