Add an offline attribute-admission branch beside exact-record review.
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 59s

Compact-sitting v8 can admit a new memo from carried class, submitter, and approval attributes, but shipped sets are empty and exact-record decisions stay on their pins. The sitting image pin is unchanged.

Assistant: grok
Assistant-Session: 01a10329-31cb-78d3-b7a3-788c2f91d6d4
This commit is contained in:
tegwick 2026-10-03 22:03:46 +02:00
parent d014453a1a
commit e85f511c4f
8 changed files with 1237 additions and 10 deletions

View file

@ -0,0 +1,303 @@
{
"policy_id": "informed-decision.compact-sitting",
"policy_version": "v8",
"note": "Offline request/response fixtures. Shipped admitted sets are empty, so attribute_eligible is not in this file. An allow here is an exact-record pin, not proof of a live approval.",
"cases": [
{
"id": "exact-record-allow",
"kind": "exact",
"assurance": "now",
"expect": {"effect": "allow", "reason": "operator_admitted_compact_sitting"},
"request": {
"id": "exact-record-allow",
"tenant": "tenant:platform",
"subject": {
"id": "synthetic-reviewer",
"type": "human",
"tenant": "tenant:platform",
"attributes": {
"principal_type_source": "authentication-derived",
"tenant_source": "registration-supplied",
"groups": ["net-kingdom-admins"],
"assurance": {"level": "aal2", "mfa": true, "source": "key-cape", "methods": ["pwd", "otp"], "at": 0}
}
},
"action": "accept",
"resource": {"id": "memo:infd-20260927-b02", "type": "decision-memo", "system": "informed-decision", "tenant": "tenant:platform"},
"context": {
"memo_version": 1,
"approval_id": "1e73e514-ef74-4bff-83e7-fa88a3eaba0c",
"approval_binding_digest": "sha256:3e5951847e1909abe1561f561b4591228848a2e1a939b9bf5acab61e2db42773"
},
"policy_version": "v8"
}
},
{
"id": "exact-record-wrong-digest",
"kind": "exact",
"assurance": "now",
"expect": {"effect": "deny", "reason": "compact_sitting_scope_or_identity_refused"},
"request": {
"id": "exact-record-wrong-digest",
"tenant": "tenant:platform",
"subject": {
"id": "synthetic-reviewer",
"type": "human",
"tenant": "tenant:platform",
"attributes": {
"principal_type_source": "authentication-derived",
"tenant_source": "registration-supplied",
"groups": ["net-kingdom-admins"],
"assurance": {"level": "aal2", "mfa": true, "source": "key-cape", "methods": ["pwd", "otp"], "at": 0}
}
},
"action": "accept",
"resource": {"id": "memo:infd-20260927-b02", "type": "decision-memo", "system": "informed-decision", "tenant": "tenant:platform"},
"context": {
"memo_version": 1,
"approval_id": "1e73e514-ef74-4bff-83e7-fa88a3eaba0c",
"approval_binding_digest": "sha256:0000000000000000000000000000000000000000000000000000000000000000"
},
"policy_version": "v8"
}
},
{
"id": "exact-record-with-attribute-context",
"kind": "attribute",
"assurance": "now",
"expect": {"effect": "allow", "reason": "operator_admitted_compact_sitting"},
"request": {
"id": "exact-record-with-attribute-context",
"tenant": "tenant:platform",
"subject": {
"id": "synthetic-reviewer",
"type": "human",
"tenant": "tenant:platform",
"attributes": {
"principal_type_source": "authentication-derived",
"tenant_source": "registration-supplied",
"groups": ["net-kingdom-admins"],
"assurance": {"level": "aal2", "mfa": true, "source": "key-cape", "methods": ["pwd", "otp"], "at": 0}
}
},
"action": "read",
"resource": {"id": "memo:infd-20260927-b02", "type": "decision-memo", "system": "informed-decision", "tenant": "tenant:platform"},
"context": {
"review_group": "net-kingdom-admins",
"memo_class": "credential-lane",
"intake_submitter": "state-hub",
"approval_requester": "informed-decision",
"approval_human_control": true,
"memo_version": 1,
"approval_id": "1e73e514-ef74-4bff-83e7-fa88a3eaba0c",
"approval_binding_digest": "sha256:3e5951847e1909abe1561f561b4591228848a2e1a939b9bf5acab61e2db42773"
},
"policy_version": "v8"
}
},
{
"id": "exact-pin-mismatch-not-rescued",
"kind": "attribute",
"assurance": "now",
"expect": {"effect": "deny", "reason": "compact_sitting_scope_or_identity_refused"},
"request": {
"id": "exact-pin-mismatch-not-rescued",
"tenant": "tenant:platform",
"subject": {
"id": "synthetic-reviewer",
"type": "human",
"tenant": "tenant:platform",
"attributes": {
"principal_type_source": "authentication-derived",
"tenant_source": "registration-supplied",
"groups": ["net-kingdom-admins"],
"assurance": {"level": "aal2", "mfa": true, "source": "key-cape", "methods": ["pwd", "otp"], "at": 0}
}
},
"action": "accept",
"resource": {"id": "memo:infd-20260927-b02", "type": "decision-memo", "system": "informed-decision", "tenant": "tenant:platform"},
"context": {
"review_group": "net-kingdom-admins",
"memo_class": "credential-lane",
"intake_submitter": "state-hub",
"approval_requester": "informed-decision",
"approval_human_control": true,
"memo_version": 1,
"approval_id": "1e73e514-ef74-4bff-83e7-fa88a3eaba0c",
"approval_binding_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
},
"policy_version": "v8"
}
},
{
"id": "attribute-unknown-class",
"kind": "attribute",
"assurance": "now",
"expect": {"effect": "deny", "reason": "unknown_class"},
"request": {
"id": "attribute-unknown-class",
"tenant": "tenant:platform",
"subject": {
"id": "synthetic-reviewer",
"type": "human",
"tenant": "tenant:platform",
"attributes": {
"principal_type_source": "authentication-derived",
"tenant_source": "registration-supplied",
"groups": ["net-kingdom-admins"],
"assurance": {"level": "aal2", "mfa": true, "source": "key-cape", "methods": ["pwd", "otp"], "at": 0}
}
},
"action": "decline",
"resource": {"id": "memo:intake-new-memo", "type": "decision-memo", "system": "informed-decision", "tenant": "tenant:platform"},
"context": {
"review_group": "net-kingdom-admins",
"memo_class": "credential-lane",
"intake_submitter": "state-hub",
"approval_requester": "informed-decision",
"approval_human_control": true,
"memo_version": 1,
"approval_id": "706f4205-aaa7-44f6-be30-d0f57664d69c",
"approval_binding_digest": "sha256:c8a1a9c8fb22616e3aacfb877ba7974feb5eedaed491f856a6c90719c502ebe4"
},
"policy_version": "v8"
}
},
{
"id": "attribute-stale-mfa",
"kind": "attribute",
"assurance": "stale",
"expect": {"effect": "deny", "reason": "stale_mfa"},
"request": {
"id": "attribute-stale-mfa",
"tenant": "tenant:platform",
"subject": {
"id": "synthetic-reviewer",
"type": "human",
"tenant": "tenant:platform",
"attributes": {
"principal_type_source": "authentication-derived",
"tenant_source": "registration-supplied",
"groups": ["net-kingdom-admins"],
"assurance": {"level": "aal2", "mfa": true, "source": "key-cape", "methods": ["pwd", "otp"], "at": 0}
}
},
"action": "accept",
"resource": {"id": "memo:intake-new-memo", "type": "decision-memo", "system": "informed-decision", "tenant": "tenant:platform"},
"context": {
"review_group": "net-kingdom-admins",
"memo_class": "credential-lane",
"intake_submitter": "state-hub",
"approval_requester": "informed-decision",
"approval_human_control": false,
"memo_version": 1,
"approval_id": "not-a-uuid",
"approval_binding_digest": "sha256:wrong"
},
"policy_version": "v8"
}
},
{
"id": "attribute-review-group",
"kind": "attribute",
"assurance": "now",
"expect": {"effect": "deny", "reason": "review_group_not_verified"},
"request": {
"id": "attribute-review-group",
"tenant": "tenant:platform",
"subject": {
"id": "synthetic-reviewer",
"type": "human",
"tenant": "tenant:platform",
"attributes": {
"principal_type_source": "authentication-derived",
"tenant_source": "registration-supplied",
"groups": ["other-group"],
"assurance": {"level": "aal2", "mfa": true, "source": "key-cape", "methods": ["pwd", "otp"], "at": 0}
}
},
"action": "discuss",
"resource": {"id": "memo:intake-new-memo", "type": "decision-memo", "system": "informed-decision", "tenant": "tenant:platform"},
"context": {
"review_group": "net-kingdom-admins",
"memo_class": "credential-lane",
"intake_submitter": "state-hub",
"approval_requester": "informed-decision",
"approval_human_control": true,
"memo_version": 2,
"approval_id": "706f4205-aaa7-44f6-be30-d0f57664d69c",
"approval_binding_digest": "sha256:c8a1a9c8fb22616e3aacfb877ba7974feb5eedaed491f856a6c90719c502ebe4"
},
"policy_version": "v8"
}
},
{
"id": "attribute-bad-memo-id",
"kind": "attribute",
"assurance": "now",
"expect": {"effect": "deny", "reason": "resource_refused"},
"request": {
"id": "attribute-bad-memo-id",
"tenant": "tenant:platform",
"subject": {
"id": "synthetic-reviewer",
"type": "human",
"tenant": "tenant:platform",
"attributes": {
"principal_type_source": "authentication-derived",
"tenant_source": "registration-supplied",
"groups": ["net-kingdom-admins"],
"assurance": {"level": "aal2", "mfa": true, "source": "key-cape", "methods": ["pwd", "otp"], "at": 0}
}
},
"action": "return",
"resource": {"id": "memo:NO", "type": "decision-memo", "system": "informed-decision", "tenant": "tenant:platform"},
"context": {
"review_group": "net-kingdom-admins",
"memo_class": "credential-lane",
"intake_submitter": "state-hub",
"approval_requester": "informed-decision",
"approval_human_control": true,
"memo_version": 1,
"approval_id": "706f4205-aaa7-44f6-be30-d0f57664d69c",
"approval_binding_digest": "sha256:c8a1a9c8fb22616e3aacfb877ba7974feb5eedaed491f856a6c90719c502ebe4"
},
"policy_version": "v8"
}
},
{
"id": "attribute-list-stays-exact",
"kind": "attribute",
"assurance": "now",
"expect": {"effect": "deny", "reason": "compact_sitting_scope_or_identity_refused"},
"request": {
"id": "attribute-list-stays-exact",
"tenant": "tenant:platform",
"subject": {
"id": "synthetic-reviewer",
"type": "human",
"tenant": "tenant:platform",
"attributes": {
"principal_type_source": "authentication-derived",
"tenant_source": "registration-supplied",
"groups": ["net-kingdom-admins"],
"assurance": {"level": "aal2", "mfa": true, "source": "key-cape", "methods": ["pwd", "otp"], "at": 0}
}
},
"action": "list",
"resource": {"id": "memo:intake-new-memo", "type": "decision-memo", "system": "informed-decision", "tenant": "tenant:platform"},
"context": {
"review_group": "net-kingdom-admins",
"memo_class": "credential-lane",
"intake_submitter": "state-hub",
"approval_requester": "informed-decision",
"approval_human_control": true,
"memo_version": 1,
"approval_id": "706f4205-aaa7-44f6-be30-d0f57664d69c",
"approval_binding_digest": "sha256:c8a1a9c8fb22616e3aacfb877ba7974feb5eedaed491f856a6c90719c502ebe4"
},
"policy_version": "v8"
}
}
]
}

View file

@ -0,0 +1,140 @@
[
{
"id": "attribute-non-human-refused",
"request": {
"id": "attribute-non-human",
"tenant": "tenant:platform",
"subject": {
"id": "not-a-person",
"type": "service",
"tenant": "tenant:platform",
"attributes": {
"principal_type_source": "authentication-derived",
"tenant_source": "registration-supplied",
"groups": ["net-kingdom-admins"],
"assurance": {
"level": "aal2",
"mfa": true,
"source": "key-cape",
"methods": ["pwd", "otp"],
"at": 1
}
}
},
"action": "accept",
"resource": {
"id": "memo:intake-new-memo",
"type": "decision-memo",
"system": "informed-decision",
"tenant": "tenant:platform"
},
"context": {
"review_group": "net-kingdom-admins",
"memo_class": "credential-lane",
"intake_submitter": "state-hub",
"approval_requester": "informed-decision",
"approval_human_control": true,
"memo_version": 1,
"approval_id": "706f4205-aaa7-44f6-be30-d0f57664d69c",
"approval_binding_digest": "sha256:c8a1a9c8fb22616e3aacfb877ba7974feb5eedaed491f856a6c90719c502ebe4"
},
"policy_version": "v8"
},
"expect": {
"effect": "deny",
"reason": "identity_refused"
}
},
{
"id": "attribute-stale-mfa",
"request": {
"id": "attribute-stale-mfa",
"tenant": "tenant:platform",
"subject": {
"id": "test-human",
"type": "human",
"tenant": "tenant:platform",
"attributes": {
"principal_type_source": "authentication-derived",
"tenant_source": "registration-supplied",
"groups": ["net-kingdom-admins"],
"assurance": {
"level": "aal2",
"mfa": true,
"source": "key-cape",
"methods": ["pwd", "otp"],
"at": 1
}
}
},
"action": "accept",
"resource": {
"id": "memo:intake-new-memo",
"type": "decision-memo",
"system": "informed-decision",
"tenant": "tenant:platform"
},
"context": {
"review_group": "net-kingdom-admins",
"memo_class": "credential-lane",
"intake_submitter": "state-hub",
"approval_requester": "informed-decision",
"approval_human_control": true,
"memo_version": 1,
"approval_id": "706f4205-aaa7-44f6-be30-d0f57664d69c",
"approval_binding_digest": "sha256:c8a1a9c8fb22616e3aacfb877ba7974feb5eedaed491f856a6c90719c502ebe4"
},
"policy_version": "v8"
},
"expect": {
"effect": "deny",
"reason": "stale_mfa"
}
},
{
"id": "attribute-future-mfa",
"request": {
"id": "attribute-future-mfa",
"tenant": "tenant:platform",
"subject": {
"id": "test-human",
"type": "human",
"tenant": "tenant:platform",
"attributes": {
"principal_type_source": "authentication-derived",
"tenant_source": "registration-supplied",
"groups": ["net-kingdom-admins"],
"assurance": {
"level": "aal2",
"mfa": true,
"source": "key-cape",
"methods": ["pwd", "otp"],
"at": 9999999999
}
}
},
"action": "read",
"resource": {
"id": "memo:intake-new-memo",
"type": "decision-memo",
"system": "informed-decision",
"tenant": "tenant:platform"
},
"context": {
"review_group": "net-kingdom-admins",
"memo_class": "credential-lane",
"intake_submitter": "state-hub",
"approval_requester": "informed-decision",
"approval_human_control": true,
"memo_version": 1,
"approval_id": "706f4205-aaa7-44f6-be30-d0f57664d69c",
"approval_binding_digest": "sha256:c8a1a9c8fb22616e3aacfb877ba7974feb5eedaed491f856a6c90719c502ebe4"
},
"policy_version": "v8"
},
"expect": {
"effect": "deny",
"reason": "identity_refused"
}
}
]

View file

@ -2,13 +2,13 @@
id: informed-decision.compact-sitting
name: Compact sitting exact-record human review
namespace: informed-decision:decision-memo
version: v7
version: v8
status: ready
package: flexauth.informed_decision.compact_sitting
allow_ttl: 30s
actions: [read, acknowledge, accept, return, discuss, decline, list]
owner: flex-auth
fixtures: [fixtures.json]
fixtures: [fixtures.json, attribute_fixtures.json]
caring:
profile: caring-0.4.0-rc2
enforce: false
@ -64,6 +64,28 @@ v7 (2026-10-03, INFD-WP-ADHOC-2026-10-03-T02) adds the exact
from informed-decision `docs/evidence/2026-10-03-provisioning-window-approval-create.json`.
Existing records and identity requirements are preserved.
v8 (2026-10-03, offline attribute admission) adds one branch beside those
exact-record rules. It is not the deployed sitting pin. Requests whose
resource id is in `records` or `listable` still take only the exact-record
rules, including when they also carry attribute fields, so an exact-record
decision does not change. `list` stays on the exact-record rule.
The attribute branch may allow `read`, `acknowledge`, `accept`, `return`,
`discuss` and `decline` for any other memo id. `attribute_eligible` is
eligibility only. It checks that the carried approval id is a UUID and the
carried digest matches `sha256:` plus 64 lowercase hex digits. It does not
read an approval and it does not prove the memo pin. The effect owner still
reads the approval live and compares that id and the engine's native digest
with the memo. `approval_human_control` must be boolean true. No presentation
hash is read. Context fields are `review_group`, `memo_class`,
`intake_submitter`, `approval_requester`, `approval_human_control`,
`memo_version`, `approval_id` and `approval_binding_digest`.
`admitted_submitters` and `admitted_classes` are generated from
informed-decision `deploy/intake-submitters.json`. The shipped registry is
empty, so both sets are empty and this branch admits nobody. Regenerate with
`python3 tools/gen_informed_decision_admission.py`; `--check` reports drift.
The identity bar matches T03: authenticated informed-decision caller,
`net-kingdom-admins`, fresh KeyCape AAL2 MFA. No permission follows from
memo content or presentation state. TokenReview must admit
@ -172,6 +194,103 @@ listable := object.union(
list_only_records,
)
# BEGIN GENERATED admitted_sets (tools/gen_informed_decision_admission.py; do not edit by hand)
admitted_submitters := set()
admitted_classes := set()
# END GENERATED admitted_sets
exact_id if {
id := object.get(object.get(input, "resource", {}), "id", "")
listable[id]
}
attribute_attempt if {
not exact_id
input.action in {"read", "acknowledge", "accept", "return", "discuss", "decline"}
ctx := object.get(input, "context", {})
some key in {"review_group", "memo_class", "intake_submitter", "approval_requester", "approval_human_control"}
object.get(ctx, key, null) != null
}
identity_core if {
input.tenant == "tenant:platform"
input.subject.tenant == "tenant:platform"
input.subject.type == "human"
is_string(input.subject.id)
input.subject.id != ""
input.subject.attributes.principal_type_source == "authentication-derived"
input.subject.attributes.tenant_source in {"registration-supplied", "directory-asserted"}
assurance := input.subject.attributes.assurance
assurance.level == "aal2"
assurance.mfa == true
assurance.source == "key-cape"
assurance.methods == ["pwd", "otp"]
is_number(assurance.at)
assurance.at > 0
}
mfa_fresh if {
identity_core
age := time.now_ns() / 1000000000 - input.subject.attributes.assurance.at
age >= -30
age <= 900
}
stale_mfa if {
identity_core
age := time.now_ns() / 1000000000 - input.subject.attributes.assurance.at
age > 900
}
resource_ok if {
input.resource.tenant == "tenant:platform"
input.resource.system == "informed-decision"
input.resource.type == "decision-memo"
is_string(input.resource.id)
regex.match(`^memo:[a-z0-9][a-z0-9-]{2,62}$`, input.resource.id)
}
review_group_ok if {
group := input.context.review_group
is_string(group)
group != ""
group in input.subject.attributes.groups
}
class_ok if {
is_string(input.context.memo_class)
input.context.memo_class in admitted_classes
}
submitter_ok if {
is_string(input.context.intake_submitter)
input.context.intake_submitter in admitted_submitters
}
requester_ok if {
input.context.approval_requester == "informed-decision"
}
human_control_ok if {
input.context.approval_human_control == true
}
approval_id_ok if {
is_string(input.context.approval_id)
regex.match(`^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`, input.context.approval_id)
}
digest_ok if {
is_string(input.context.approval_binding_digest)
regex.match(`^sha256:[0-9a-f]{64}$`, input.context.approval_binding_digest)
}
version_ok if {
is_number(input.context.memo_version)
input.context.memo_version >= 1
}
decision := {"effect": "allow", "reason": "operator_admitted_compact_sitting"} if {
input.tenant == "tenant:platform"
input.subject.tenant == "tenant:platform"
@ -226,6 +345,54 @@ decision := {"effect": "allow", "reason": "operator_admitted_compact_sitting"} i
input.context.memo_version == record.memo_version
input.context.approval_id == record.approval_id
input.context.approval_binding_digest == record.binding_digest
} else := {"effect": "allow", "reason": "attribute_eligible"} if {
attribute_attempt
mfa_fresh
resource_ok
review_group_ok
class_ok
submitter_ok
requester_ok
human_control_ok
approval_id_ok
digest_ok
version_ok
} else := {"effect": "deny", "reason": "identity_refused"} if {
attribute_attempt
not identity_core
} else := {"effect": "deny", "reason": "stale_mfa"} if {
attribute_attempt
stale_mfa
} else := {"effect": "deny", "reason": "identity_refused"} if {
attribute_attempt
not mfa_fresh
} else := {"effect": "deny", "reason": "resource_refused"} if {
attribute_attempt
not resource_ok
} else := {"effect": "deny", "reason": "review_group_not_verified"} if {
attribute_attempt
not review_group_ok
} else := {"effect": "deny", "reason": "unknown_class"} if {
attribute_attempt
not class_ok
} else := {"effect": "deny", "reason": "unknown_submitter"} if {
attribute_attempt
not submitter_ok
} else := {"effect": "deny", "reason": "requester_mismatch"} if {
attribute_attempt
not requester_ok
} else := {"effect": "deny", "reason": "human_control_false"} if {
attribute_attempt
not human_control_ok
} else := {"effect": "deny", "reason": "approval_id_malformed"} if {
attribute_attempt
not approval_id_ok
} else := {"effect": "deny", "reason": "digest_mismatch"} if {
attribute_attempt
not digest_ok
} else := {"effect": "deny", "reason": "memo_version_refused"} if {
attribute_attempt
not version_ok
} else := {"effect": "deny", "reason": "compact_sitting_scope_or_identity_refused"} if {
true
}
@ -297,4 +464,153 @@ test_six_exact_metered_memos_allow_human_review if {
}
}
attribute_input := {
"tenant": "tenant:platform",
"subject": {"id": "test-human", "type": "human", "tenant": "tenant:platform",
"attributes": {"principal_type_source": "authentication-derived",
"tenant_source": "registration-supplied", "groups": ["net-kingdom-admins"],
"assurance": {"level": "aal2", "mfa": true, "source": "key-cape",
"methods": ["pwd", "otp"], "at": time.now_ns() / 1000000000}}},
"resource": {"tenant": "tenant:platform", "system": "informed-decision",
"type": "decision-memo", "id": "memo:intake-new-memo"},
"action": "accept",
"context": {
"review_group": "net-kingdom-admins",
"memo_class": "credential-lane",
"intake_submitter": "state-hub",
"approval_requester": "informed-decision",
"approval_human_control": true,
"memo_version": 1,
"approval_id": "706f4205-aaa7-44f6-be30-d0f57664d69c",
"approval_binding_digest": "sha256:c8a1a9c8fb22616e3aacfb877ba7974feb5eedaed491f856a6c90719c502ebe4"
},
}
patched_context(req, patch) := object.union(req, {"context": object.union(req.context, patch)})
with_at(req, at) := object.union(req, {"subject": object.union(req.subject, {"attributes": object.union(req.subject.attributes, {"assurance": object.union(req.subject.attributes.assurance, {"at": at})})})})
eligible(req) := result if {
result := compact_sitting.decision with input as req
with data.flexauth.informed_decision.compact_sitting.admitted_classes as {"credential-lane"}
with data.flexauth.informed_decision.compact_sitting.admitted_submitters as {"state-hub"}
}
classes_only(req) := result if {
result := compact_sitting.decision with input as req
with data.flexauth.informed_decision.compact_sitting.admitted_classes as {"credential-lane"}
}
test_shipped_sets_admit_nobody if {
count(compact_sitting.admitted_classes) == 0
count(compact_sitting.admitted_submitters) == 0
compact_sitting.decision.reason == "unknown_class" with input as attribute_input
}
test_attribute_acts_are_eligible_only_when_sets_admit if {
every action in ["read", "acknowledge", "accept", "return", "discuss", "decline"] {
eligible(object.union(attribute_input, {"action": action})).reason == "attribute_eligible"
}
}
test_allow_does_not_prove_the_pin if {
other := "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
attribute_input.context.approval_binding_digest != other
eligible(attribute_input).reason == "attribute_eligible"
eligible(patched_context(attribute_input, {"approval_binding_digest": other})).reason == "attribute_eligible"
}
test_unknown_class if {
eligible(patched_context(attribute_input, {"memo_class": "not-a-class"})).reason == "unknown_class"
}
test_unknown_submitter if {
classes_only(attribute_input).reason == "unknown_submitter"
}
test_requester_mismatch if {
eligible(patched_context(attribute_input, {"approval_requester": "other-service"})).reason == "requester_mismatch"
}
test_human_control_false if {
eligible(patched_context(attribute_input, {"approval_human_control": false})).reason == "human_control_false"
eligible(patched_context(attribute_input, {"approval_human_control": "true"})).reason == "human_control_false"
}
test_digest_mismatch if {
eligible(patched_context(attribute_input, {"approval_binding_digest": "sha256:wrong"})).reason == "digest_mismatch"
eligible(patched_context(attribute_input, {"approval_binding_digest": "sha256:C8A1A9C8FB22616E3AACFB877BA7974FEB5EEDAED491F856A6C90719C502EBE4"})).reason == "digest_mismatch"
}
test_approval_id_malformed if {
eligible(patched_context(attribute_input, {"approval_id": "not-a-uuid"})).reason == "approval_id_malformed"
}
test_memo_version_refused if {
eligible(patched_context(attribute_input, {"memo_version": 0})).reason == "memo_version_refused"
}
test_stale_mfa_on_attribute_branch if {
eligible(with_at(attribute_input, time.now_ns() / 1000000000 - 901)).reason == "stale_mfa"
}
test_future_mfa_is_identity_refused if {
eligible(with_at(attribute_input, time.now_ns() / 1000000000 + 300)).reason == "identity_refused"
}
test_service_subject_is_identity_refused if {
eligible(object.union(attribute_input, {"subject": object.union(attribute_input.subject, {"type": "service"})})).reason == "identity_refused"
}
test_review_group_must_be_verified if {
req := object.union(attribute_input, {"subject": object.union(attribute_input.subject, {"attributes": object.union(attribute_input.subject.attributes, {"groups": ["other-group"]})})})
eligible(req).reason == "review_group_not_verified"
}
test_memo_review_group_is_not_hardcoded if {
req := object.union(attribute_input, {
"subject": object.union(attribute_input.subject, {"attributes": object.union(attribute_input.subject.attributes, {"groups": ["cabinet-reviewers"]})}),
"context": object.union(attribute_input.context, {"review_group": "cabinet-reviewers"}),
})
eligible(req).reason == "attribute_eligible"
}
test_bad_memo_id_refused if {
eligible(object.union(attribute_input, {"resource": object.union(attribute_input.resource, {"id": "memo:NO"})})).reason == "resource_refused"
}
test_list_does_not_use_attribute_admission if {
got := eligible(object.union(attribute_input, {"action": "list"}))
got.effect == "deny"
got.reason == "compact_sitting_scope_or_identity_refused"
}
test_exact_record_ignores_attribute_context if {
req := object.union(tool_proof_input, {"context": object.union(tool_proof_input.context, {
"review_group": "net-kingdom-admins",
"memo_class": "credential-lane",
"intake_submitter": "state-hub",
"approval_requester": "informed-decision",
"approval_human_control": true,
"view_hash": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
})})
got := compact_sitting.decision with input as req
got.effect == "allow"
got.reason == "operator_admitted_compact_sitting"
}
test_exact_pin_mismatch_is_not_rescued if {
req := object.union(tool_proof_input, {"context": object.union(tool_proof_input.context, {
"review_group": "net-kingdom-admins",
"memo_class": "credential-lane",
"intake_submitter": "state-hub",
"approval_requester": "informed-decision",
"approval_human_control": true,
"approval_binding_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
})})
got := eligible(req)
got.effect == "deny"
got.reason == "compact_sitting_scope_or_identity_refused"
}
```

View file

@ -0,0 +1,87 @@
package decision_test
import (
"context"
"path/filepath"
"testing"
"time"
"github.com/netkingdom/flex-auth/internal/decision"
"github.com/netkingdom/flex-auth/internal/policy"
"github.com/netkingdom/flex-auth/internal/registry"
"github.com/netkingdom/flex-auth/pkg/api"
)
func TestAttributeContextCarriedWithoutViewHash(t *testing.T) {
root := filepath.Join("..", "..")
pkg, err := policy.LoadAndValidateFile(context.Background(), filepath.Join(root, "examples", "informed-decision-sitting", "policy.md"))
if err != nil {
t.Fatalf("LoadAndValidateFile: %v", err)
}
store, err := registry.LoadFile(filepath.Join(root, "examples", "informed-decision-sitting", "registry.json"))
if err != nil {
t.Fatal(err)
}
engine, err := decision.NewEngine(store, pkg)
if err != nil {
t.Fatal(err)
}
request := api.CheckRequest{
ID: "attribute-context",
Tenant: "tenant:platform",
Subject: api.SubjectRef{
ID: "synthetic-attribute-reviewer",
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(),
},
},
},
Action: "accept",
Resource: api.ResourceRef{
ID: "memo:intake-new-memo", Type: "decision-memo",
System: "informed-decision", Tenant: "tenant:platform",
},
Context: map[string]any{
"review_group": "net-kingdom-admins",
"memo_class": "credential-lane",
"intake_submitter": "state-hub",
"approval_requester": "informed-decision",
"approval_human_control": true,
"memo_version": 1,
"approval_id": "706f4205-aaa7-44f6-be30-d0f57664d69c",
"approval_binding_digest": "sha256:c8a1a9c8fb22616e3aacfb877ba7974feb5eedaed491f856a6c90719c502ebe4",
},
PolicyVersion: "v8",
}
got, err := engine.Check(context.Background(), request)
if err != nil {
t.Fatal(err)
}
if got.Effect != api.DecisionEffectDeny || got.Reason != "unknown_class" {
t.Fatalf("effect = %s %s; shipped sets must refuse the class", got.Effect, got.Reason)
}
if got.Binding == nil {
t.Fatal("binding missing")
}
for _, key := range []string{
"review_group", "memo_class", "intake_submitter", "approval_requester",
"approval_human_control", "memo_version", "approval_id", "approval_binding_digest",
} {
if _, ok := got.Binding.Context[key]; !ok {
t.Fatalf("binding context missing %s", key)
}
}
if _, ok := got.Binding.Context["view_hash"]; ok {
t.Fatal("view_hash entered the decision binding")
}
if got.Binding.Context["approval_requester"] != "informed-decision" || got.Binding.Context["approval_human_control"] != true {
t.Fatalf("binding context = %#v", got.Binding.Context)
}
}

View file

@ -0,0 +1,253 @@
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 != "v8" {
t.Fatalf("version = %s; want v8", 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 != "v8" {
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 TestSittingImagePinUnchanged(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)
if !strings.Contains(text, "sha256:f6fa2062f7095e66c7a91f20cd130914b5481d46d9d909339e028f7ecdf0065d") {
t.Fatal("sitting image digest changed")
}
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
}

View file

@ -53,6 +53,11 @@ echo "$fetch_render" | grep -q examples/ops-warden-fetch/policy_package.md \
python3 tools/gen_ops_warden_fetch.py --check >/dev/null 2>&1 || [ ! -f "${HOME}/ops-warden/registry/routing/catalog.yaml" ] \
|| { echo "ops-warden-fetch is out of date with the routing catalog; run tools/gen_ops_warden_fetch.py" >&2; exit 1; }
if [ -f "${HOME}/informed-decision/deploy/intake-submitters.json" ]; then
python3 tools/gen_informed_decision_admission.py --check >/dev/null \
|| { echo "informed-decision attribute admission sets drifted; run tools/gen_informed_decision_admission.py" >&2; exit 1; }
fi
canary_render="$(helm template flex-auth-canary charts/flex-auth -f values/stage2-canary.yaml --namespace flex-auth)"
if echo "$canary_render" | grep -q tokenreviews; then
echo "isolated canary must not create TokenReview RBAC" >&2

View file

@ -92,8 +92,12 @@ with tempfile.TemporaryDirectory() as temp:
# every act stays scoped to the act records, with the 900 s window.
list_only = json.loads((r / 'list_only_records.json').read_text())
listable = {k: dict(v, memo_version=1) for k, v in records.items()} | list_only
now = int(time.time())
for memo, record in listable.items():
# Sample the clock per check. A single `now` goes stale while each
# check reloads the package, and the 12h case has only 60s of slack.
def stamp():
return int(time.time())
request = {
'id': 'local-regression',
'tenant': 'tenant:platform',
@ -104,7 +108,7 @@ with tempfile.TemporaryDirectory() as temp:
'tenant_source': 'registration-supplied',
'principal_type_source': 'authentication-derived',
'assurance': {'level': 'aal2', 'mfa': True, 'methods': ['pwd', 'otp'],
'source': 'key-cape', 'at': now},
'source': 'key-cape', 'at': stamp()},
},
},
'resource': {'id': memo, 'type': 'decision-memo',
@ -125,14 +129,14 @@ with tempfile.TemporaryDirectory() as temp:
return c
at = ['subject', 'attributes', 'assurance', 'at']
check(label + ':fresh', request, 'allow')
check(label + ':fresh', variant(at, stamp()), 'allow')
# The observed production refusal: 1744 s, allowed for list...
check(label + ':observed-1744s', variant(at, now - 1744), 'allow')
check(label + ':inside-12h', variant(at, now - 43200 + 60), 'allow')
check(label + ':observed-1744s', variant(at, stamp() - 1744), 'allow')
check(label + ':inside-12h', variant(at, stamp() - 43200 + 60), 'allow')
# ...but the 12 h bound is real, and read does not follow list.
check(label + ':past-12h', variant(at, now - 43200 - 60), 'deny')
check(label + ':past-12h', variant(at, stamp() - 43200 - 60), 'deny')
is_act_record = memo in records
read_stale = variant(at, now - 1744) | {'action': 'read'}
read_stale = variant(at, stamp() - 1744) | {'action': 'read'}
check(label + ':read-stale-refused', read_stale, 'deny')
if not is_act_record:
# T03 records are listable only; no act widens to them, even fresh.
@ -143,7 +147,7 @@ with tempfile.TemporaryDirectory() as temp:
('no-group', ['subject', 'attributes', 'groups'], []),
('service', ['subject', 'type'], 'service'),
('agent', ['subject', 'type'], 'agent'),
('future-mfa', at, now + 300),
('future-mfa', at, stamp() + 300),
('no-mfa', ['subject', 'attributes', 'assurance', 'mfa'], False),
('aal1', ['subject', 'attributes', 'assurance', 'level'], 'aal1'),
('forged-human-route', ['subject', 'attributes', 'principal_type_source'], 'registration-supplied'),

View file

@ -0,0 +1,119 @@
#!/usr/bin/env python3
"""Generate attribute-admission sets from informed-decision's submitter registry.
The compact-sitting attribute branch does not author who may pose. It copies
submitter ids and the union of their memo classes from
``deploy/intake-submitters.json``. An empty registry generates empty sets, so
the branch admits nobody. A malformed registry refuses to write.
Usage:
python3 tools/gen_informed_decision_admission.py [--source PATH]
python3 tools/gen_informed_decision_admission.py [--source PATH] --check
``--check`` reports drift and writes nothing. ``--policy`` is for tests.
"""
from __future__ import annotations
import argparse
import json
import os
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
POLICY = ROOT / "examples" / "informed-decision-sitting" / "policy.md"
DEFAULT_SOURCE = Path(os.path.expanduser("~/informed-decision/deploy/intake-submitters.json"))
BEGIN = "# BEGIN GENERATED admitted_sets (tools/gen_informed_decision_admission.py; do not edit by hand)"
END = "# END GENERATED admitted_sets"
SA = re.compile(r"^system:serviceaccount:[a-z0-9-]+:[a-z0-9-]+$")
SLUG = re.compile(r"^[a-z0-9][a-z0-9-]{1,62}$")
ROW_KEYS = {"submitter_id", "memo_classes", "review_groups", "daily_limit"}
def fail(message: str) -> None:
raise SystemExit(message)
def load_sets(path: Path) -> tuple[list[str], list[str]]:
try:
document = json.loads(path.read_text())
except (OSError, json.JSONDecodeError) as exc:
fail(f"cannot read submitter registry {path}: {exc}")
if not isinstance(document, dict):
fail("intake submitters must be an object; refusing to generate")
submitters: list[str] = []
classes: set[str] = set()
for username, row in document.items():
if not isinstance(username, str) or not SA.fullmatch(username) or not isinstance(row, dict):
fail(f"refusing submitter row {username!r}")
if set(row) != ROW_KEYS:
fail(f"refusing submitter row {username!r}: keys must be {sorted(ROW_KEYS)}")
submitter_id = row["submitter_id"]
if not isinstance(submitter_id, str) or not SLUG.fullmatch(submitter_id):
fail(f"refusing submitter id {submitter_id!r}")
limit = row["daily_limit"]
if type(limit) is not int or limit < 1:
fail(f"refusing daily_limit for {username}")
for field in ("memo_classes", "review_groups"):
values = row[field]
if not isinstance(values, list) or any(not isinstance(item, str) or not SLUG.fullmatch(item) for item in values):
fail(f"refusing {field} for {username}")
submitters.append(submitter_id)
classes.update(row["memo_classes"])
if len(set(submitters)) != len(submitters):
fail("duplicate submitter_id; refusing to generate")
return sorted(set(submitters)), sorted(classes)
def render_set(name: str, values: list[str]) -> str:
if not values:
return f"{name} := set()"
body = "\n".join(f' "{value}",' for value in values)
return f"{name} := {{\n{body}\n}}"
def render_block(submitters: list[str], classes: list[str]) -> str:
return (
f"{BEGIN}\n"
f"{render_set('admitted_submitters', submitters)}\n"
"\n"
f"{render_set('admitted_classes', classes)}\n"
f"{END}"
)
def replace_block(policy: str, block: str, path: Path) -> str:
pattern = re.compile(re.escape(BEGIN) + r".*?" + re.escape(END), re.S)
if not pattern.search(policy):
fail(f"{path}: generated markers not found")
return pattern.sub(lambda _: block, policy, count=1)
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
parser.add_argument("--source", type=Path, default=DEFAULT_SOURCE)
parser.add_argument("--policy", type=Path, default=POLICY)
parser.add_argument("--check", action="store_true", help="report drift, write nothing")
args = parser.parse_args()
submitters, classes = load_sets(args.source)
current = args.policy.read_text()
updated = replace_block(current, render_block(submitters, classes), args.policy)
if args.check:
if updated != current:
print(f"drift: {args.policy}", file=sys.stderr)
print(f"{len(submitters)} submitters, {len(classes)} classes; drift")
return 1
print(f"{len(submitters)} submitters, {len(classes)} classes; in sync")
return 0
if updated != current:
args.policy.write_text(updated)
print(f"wrote {args.policy}")
print(f"{len(submitters)} submitters, {len(classes)} classes")
return 0
if __name__ == "__main__":
sys.exit(main())