State tenant-engine's tenant relation in the write-api package, v3.
tenant-engine named the relation in TEN-DEC-2026-002: `tenant` is the target tenant record and always equals `resource.id`; the write API is cross-tenant by design and `tenant.guardrail.read` does not differ. v2 carried no tenant rule and a constant fixture tenant, so the deliberate scope and an omitted rule were indistinguishable. tenant-engine.write-api.mutate v3 (FLEX-DEC-2026-016): - allowed requires tenant_is_target; a mismatch or absent tenant is denied tenant_not_target (object.get, so an absent key names the right cause). - the cross-tenant scope is stated in the package and quantified by test_tenant_never_changes_effect over every action, three subjects and four tenants, with guards against passing by denying everything. - fixtures rotate tenant across four tenants; five cross-tenant allows and two tenant_not_target denies added (42 fixtures, 33 tests, all pass). - user-engine's tenant:platform exclusion is named as a fixed-record rule, not a subject/tenant relation, and tested separately. Closes FLEX-WP-0022 (T01, T02 done). Also records TEN-IN-0004 and SECRETS-IN-0002 on FLEX-WP-0020 and acknowledges the GH-DEC-2026-017 replies on FLEX-WP-0030-T04. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 63291@bnt-lap001 Assistant-Session: 8bd77868-ca68-4f49-bb1e-d539ecc0d703
This commit is contained in:
parent
bd3d270531
commit
3081067325
17 changed files with 783 additions and 150 deletions
|
|
@ -2,7 +2,7 @@
|
|||
id: tenant-engine.write-api.mutate
|
||||
name: tenant-engine Write API authorization
|
||||
namespace: tenant-engine:tenant
|
||||
version: v2
|
||||
version: v3
|
||||
status: ready
|
||||
package: flexauth.tenant_engine.write_api
|
||||
actions:
|
||||
|
|
@ -178,6 +178,45 @@ The earlier single-write-subject decisions above describe their dated baseline;
|
|||
this explicit minimal onboarding grant supersedes that baseline only for these
|
||||
operations and this existing service integration.
|
||||
|
||||
## Tenant relation (FLEX-WP-0022-T02, TEN-DEC-2026-002)
|
||||
|
||||
**Version v3.** v2 had no tenant rule and every fixture carried the same
|
||||
tenant, so a deliberate cross-tenant scope and an omitted rule were
|
||||
indistinguishable — the `FLEX-DEC-2026-008` shape. `tenant-engine` has now
|
||||
named the relation in its own record (`TEN-DEC-2026-002`,
|
||||
`tenant-engine/docs/flex-auth-integration.md`); this section states it so a
|
||||
reviewer can check it, and `FLEX-DEC-2026-016` records the version change.
|
||||
|
||||
1. **`tenant` denotes the target tenant record**, not the caller's tenant.
|
||||
tenant-engine verifies no inbound token and holds no caller tenant to send.
|
||||
On the guardrail actions the "tenant the guardrail applies to" is the same
|
||||
record.
|
||||
2. **Invariant — encoded as a rule.** On every check tenant-engine sends,
|
||||
`tenant` equals `resource.id` (`authz.FlexAuthWriteAuthorizer` copies one
|
||||
`tenant_id` onto both). A check where they differ, or where `tenant` is
|
||||
absent, did not come from this engine and is denied `tenant_not_target`.
|
||||
This is fail-closed and denies nothing tenant-engine sends.
|
||||
3. **Scope — deliberately cross-tenant, stated here.** No action in
|
||||
`valid_actions` is refused on the relationship between `subject` and
|
||||
`tenant`. The caller administers tenants: its subjects are platform service
|
||||
identities and the targets are arbitrary tenant records, and `tenant.create`
|
||||
has no existing target at check time. Authorization is a service-identity
|
||||
question over `(subject.id, action)`. **`tenant.guardrail.read` does not
|
||||
differ and must not**: flex-auth calls it while deciding about arbitrary
|
||||
tenants. No rule below compares `tenant` to the subject; the embedded test
|
||||
`test_tenant_never_changes_effect` quantifies that over every action and
|
||||
subject, and the fixtures vary `tenant` so the suite reports on the field.
|
||||
4. **The one target-dependent rule is not a tenant relation.** The
|
||||
`user-engine` onboarding grant (NK-WP-0036) excludes the fixed record
|
||||
`tenant:platform`. That depends on *which* record is targeted, not on any
|
||||
relation between the subject and the target, so it is outside the scope
|
||||
statement above and is tested separately
|
||||
(`test_portal_tenant_changes_effect_only_on_platform_record`).
|
||||
|
||||
**Revisit when** tenant-engine gains a verified inbound identity
|
||||
(`tenancy.yaml` gap I): the caller's tenant becomes knowable, and it arrives as
|
||||
a new field — this one keeps its meaning.
|
||||
|
||||
## Rules
|
||||
|
||||
```rego
|
||||
|
|
@ -208,27 +247,41 @@ read_subjects := {"tenant-engine", "flex-auth"}
|
|||
|
||||
mutate_subjects := {"tenant-engine"}
|
||||
|
||||
# TEN-DEC-2026-002: tenant is the target record and always equals resource.id.
|
||||
# No rule compares tenant to the subject: the scope is cross-tenant by design.
|
||||
request_tenant := object.get(input, "tenant", "")
|
||||
|
||||
tenant_is_target if {
|
||||
request_tenant != ""
|
||||
request_tenant == input.resource.id
|
||||
}
|
||||
|
||||
allowed if {
|
||||
tenant_is_target
|
||||
granted
|
||||
}
|
||||
|
||||
decision := {"effect": "allow", "reason": "write_api_policy_matched"} if {
|
||||
allowed
|
||||
} else := {"effect": "deny", "reason": first_denial} if {
|
||||
true
|
||||
}
|
||||
|
||||
allowed if {
|
||||
granted if {
|
||||
input.resource.system == "tenant-engine"
|
||||
input.action in read_actions
|
||||
input.subject.type == "service"
|
||||
input.subject.id in read_subjects
|
||||
}
|
||||
|
||||
allowed if {
|
||||
granted if {
|
||||
input.resource.system == "tenant-engine"
|
||||
input.action in mutate_actions
|
||||
input.subject.type == "service"
|
||||
input.subject.id in mutate_subjects
|
||||
}
|
||||
|
||||
allowed if {
|
||||
granted if {
|
||||
input.resource.system == "tenant-engine"
|
||||
input.resource.type == "tenant"
|
||||
input.resource.id != "tenant:platform"
|
||||
|
|
@ -237,7 +290,7 @@ allowed if {
|
|||
input.subject.id == "user-engine"
|
||||
}
|
||||
|
||||
allowed if {
|
||||
granted if {
|
||||
input.resource.system == "tenant-engine"
|
||||
input.resource.type == "tenant"
|
||||
input.action == "tenant.read"
|
||||
|
|
@ -249,6 +302,8 @@ default first_denial := "no_matching_rule"
|
|||
|
||||
first_denial := "wrong_system" if {
|
||||
input.resource.system != "tenant-engine"
|
||||
} else := "tenant_not_target" if {
|
||||
not tenant_is_target
|
||||
} else := "unknown_action" if {
|
||||
not input.action in valid_actions
|
||||
} else := "wrong_subject_type" if {
|
||||
|
|
@ -265,7 +320,9 @@ first_denial := "wrong_system" if {
|
|||
```rego test
|
||||
package flexauth.tenant_engine.write_api_test
|
||||
|
||||
import future.keywords.every
|
||||
import future.keywords.if
|
||||
import future.keywords.in
|
||||
import data.flexauth.tenant_engine.write_api
|
||||
|
||||
base_request := {
|
||||
|
|
@ -273,7 +330,7 @@ base_request := {
|
|||
"tenant": "tenant:friendly:binky",
|
||||
"subject": {"id": "tenant-engine", "type": "service"},
|
||||
"action": "tenant.create",
|
||||
"resource": {"id": "t-1", "type": "tenant", "system": "tenant-engine"}
|
||||
"resource": {"id": "tenant:friendly:binky", "type": "tenant", "system": "tenant-engine"}
|
||||
}
|
||||
|
||||
test_known_operator_create_allowed if {
|
||||
|
|
@ -281,170 +338,245 @@ test_known_operator_create_allowed if {
|
|||
}
|
||||
|
||||
test_role_grant_allowed if {
|
||||
write_api.decision.effect == "allow" with input as {
|
||||
write_api.decision.effect == "allow" with input as { "tenant": "tenant:friendly:binky",
|
||||
"subject": {"id": "tenant-engine", "type": "service"},
|
||||
"action": "tenant.role.grant",
|
||||
"resource": {"id": "t-1", "type": "role-grant", "system": "tenant-engine"}
|
||||
"resource": {"id": "tenant:friendly:binky", "type": "role-grant", "system": "tenant-engine"}
|
||||
}
|
||||
}
|
||||
|
||||
test_tenant_update_allowed if {
|
||||
write_api.decision.effect == "allow" with input as {
|
||||
write_api.decision.effect == "allow" with input as { "tenant": "tenant:friendly:binky",
|
||||
"subject": {"id": "tenant-engine", "type": "service"},
|
||||
"action": "tenant.update",
|
||||
"resource": {"id": "t-1", "type": "tenant", "system": "tenant-engine"}
|
||||
"resource": {"id": "tenant:friendly:binky", "type": "tenant", "system": "tenant-engine"}
|
||||
}
|
||||
}
|
||||
|
||||
test_tenant_retire_allowed if {
|
||||
write_api.decision.effect == "allow" with input as {
|
||||
write_api.decision.effect == "allow" with input as { "tenant": "tenant:friendly:binky",
|
||||
"subject": {"id": "tenant-engine", "type": "service"},
|
||||
"action": "tenant.retire",
|
||||
"resource": {"id": "t-1", "type": "tenant", "system": "tenant-engine"}
|
||||
"resource": {"id": "tenant:friendly:binky", "type": "tenant", "system": "tenant-engine"}
|
||||
}
|
||||
}
|
||||
|
||||
test_tenant_reactivate_allowed if {
|
||||
write_api.decision.effect == "allow" with input as {
|
||||
write_api.decision.effect == "allow" with input as { "tenant": "tenant:friendly:binky",
|
||||
"subject": {"id": "tenant-engine", "type": "service"},
|
||||
"action": "tenant.reactivate",
|
||||
"resource": {"id": "t-1", "type": "tenant", "system": "tenant-engine"}
|
||||
"resource": {"id": "tenant:friendly:binky", "type": "tenant", "system": "tenant-engine"}
|
||||
}
|
||||
}
|
||||
|
||||
test_misspelled_lifecycle_action_denied if {
|
||||
write_api.decision.reason == "unknown_action" with input as {
|
||||
write_api.decision.reason == "unknown_action" with input as { "tenant": "tenant:friendly:binky",
|
||||
"subject": {"id": "tenant-engine", "type": "service"},
|
||||
"action": "tenant.retired",
|
||||
"resource": {"id": "t-1", "type": "tenant", "system": "tenant-engine"}
|
||||
"resource": {"id": "tenant:friendly:binky", "type": "tenant", "system": "tenant-engine"}
|
||||
}
|
||||
}
|
||||
|
||||
test_unknown_subject_retire_denied if {
|
||||
write_api.decision.reason == "unknown_subject" with input as {
|
||||
write_api.decision.reason == "unknown_subject" with input as { "tenant": "tenant:friendly:binky",
|
||||
"subject": {"id": "some-other-service", "type": "service"},
|
||||
"action": "tenant.retire",
|
||||
"resource": {"id": "t-1", "type": "tenant", "system": "tenant-engine"}
|
||||
"resource": {"id": "tenant:friendly:binky", "type": "tenant", "system": "tenant-engine"}
|
||||
}
|
||||
}
|
||||
|
||||
test_unknown_subject_denied if {
|
||||
write_api.decision.reason == "unknown_subject" with input as {
|
||||
write_api.decision.reason == "unknown_subject" with input as { "tenant": "tenant:friendly:binky",
|
||||
"subject": {"id": "some-other-service", "type": "service"},
|
||||
"action": "tenant.create",
|
||||
"resource": {"id": "t-1", "type": "tenant", "system": "tenant-engine"}
|
||||
"resource": {"id": "tenant:friendly:binky", "type": "tenant", "system": "tenant-engine"}
|
||||
}
|
||||
}
|
||||
|
||||
test_wrong_system_denied if {
|
||||
write_api.decision.reason == "wrong_system" with input as {
|
||||
write_api.decision.reason == "wrong_system" with input as { "tenant": "tenant:friendly:binky",
|
||||
"subject": {"id": "tenant-engine", "type": "service"},
|
||||
"action": "tenant.create",
|
||||
"resource": {"id": "t-1", "type": "tenant", "system": "some-other-system"}
|
||||
"resource": {"id": "tenant:friendly:binky", "type": "tenant", "system": "some-other-system"}
|
||||
}
|
||||
}
|
||||
|
||||
test_unknown_action_denied if {
|
||||
write_api.decision.reason == "unknown_action" with input as {
|
||||
write_api.decision.reason == "unknown_action" with input as { "tenant": "tenant:friendly:binky",
|
||||
"subject": {"id": "tenant-engine", "type": "service"},
|
||||
"action": "tenant.delete",
|
||||
"resource": {"id": "t-1", "type": "tenant", "system": "tenant-engine"}
|
||||
"resource": {"id": "tenant:friendly:binky", "type": "tenant", "system": "tenant-engine"}
|
||||
}
|
||||
}
|
||||
|
||||
test_wrong_subject_type_denied if {
|
||||
write_api.decision.reason == "wrong_subject_type" with input as {
|
||||
write_api.decision.reason == "wrong_subject_type" with input as { "tenant": "tenant:friendly:binky",
|
||||
"subject": {"id": "tenant-engine", "type": "human"},
|
||||
"action": "tenant.create",
|
||||
"resource": {"id": "t-1", "type": "tenant", "system": "tenant-engine"}
|
||||
"resource": {"id": "tenant:friendly:binky", "type": "tenant", "system": "tenant-engine"}
|
||||
}
|
||||
}
|
||||
|
||||
test_guardrail_read_by_pdp_allowed if {
|
||||
write_api.decision.effect == "allow" with input as {
|
||||
write_api.decision.effect == "allow" with input as { "tenant": "tenant:friendly:binky",
|
||||
"subject": {"id": "flex-auth", "type": "service"},
|
||||
"action": "tenant.guardrail.read",
|
||||
"resource": {"id": "t-1", "type": "guardrail", "system": "tenant-engine"}
|
||||
"resource": {"id": "tenant:friendly:binky", "type": "guardrail", "system": "tenant-engine"}
|
||||
}
|
||||
}
|
||||
|
||||
test_guardrail_read_by_writer_allowed if {
|
||||
write_api.decision.effect == "allow" with input as {
|
||||
write_api.decision.effect == "allow" with input as { "tenant": "tenant:friendly:binky",
|
||||
"subject": {"id": "tenant-engine", "type": "service"},
|
||||
"action": "tenant.guardrail.read",
|
||||
"resource": {"id": "t-1", "type": "guardrail", "system": "tenant-engine"}
|
||||
"resource": {"id": "tenant:friendly:binky", "type": "guardrail", "system": "tenant-engine"}
|
||||
}
|
||||
}
|
||||
|
||||
test_guardrail_set_by_writer_allowed if {
|
||||
write_api.decision.effect == "allow" with input as {
|
||||
write_api.decision.effect == "allow" with input as { "tenant": "tenant:friendly:binky",
|
||||
"subject": {"id": "tenant-engine", "type": "service"},
|
||||
"action": "tenant.guardrail.set",
|
||||
"resource": {"id": "t-1", "type": "guardrail", "system": "tenant-engine"}
|
||||
"resource": {"id": "tenant:friendly:binky", "type": "guardrail", "system": "tenant-engine"}
|
||||
}
|
||||
}
|
||||
|
||||
test_guardrail_set_by_pdp_denied if {
|
||||
write_api.decision.reason == "action_not_granted" with input as {
|
||||
write_api.decision.reason == "action_not_granted" with input as { "tenant": "tenant:friendly:binky",
|
||||
"subject": {"id": "flex-auth", "type": "service"},
|
||||
"action": "tenant.guardrail.set",
|
||||
"resource": {"id": "t-1", "type": "guardrail", "system": "tenant-engine"}
|
||||
"resource": {"id": "tenant:friendly:binky", "type": "guardrail", "system": "tenant-engine"}
|
||||
}
|
||||
}
|
||||
|
||||
test_guardrail_read_unknown_subject_denied if {
|
||||
write_api.decision.reason == "unknown_subject" with input as {
|
||||
write_api.decision.reason == "unknown_subject" with input as { "tenant": "tenant:friendly:binky",
|
||||
"subject": {"id": "ops", "type": "service"},
|
||||
"action": "tenant.guardrail.read",
|
||||
"resource": {"id": "t-1", "type": "guardrail", "system": "tenant-engine"}
|
||||
"resource": {"id": "tenant:friendly:binky", "type": "guardrail", "system": "tenant-engine"}
|
||||
}
|
||||
}
|
||||
|
||||
test_misspelled_guardrail_action_denied if {
|
||||
write_api.decision.reason == "unknown_action" with input as {
|
||||
write_api.decision.reason == "unknown_action" with input as { "tenant": "tenant:friendly:binky",
|
||||
"subject": {"id": "flex-auth", "type": "service"},
|
||||
"action": "tenant.guardrail.get",
|
||||
"resource": {"id": "t-1", "type": "guardrail", "system": "tenant-engine"}
|
||||
"resource": {"id": "tenant:friendly:binky", "type": "guardrail", "system": "tenant-engine"}
|
||||
}
|
||||
}
|
||||
|
||||
test_portal_create if {
|
||||
write_api.decision.effect == "allow" with input as {"subject": {"id": "user-engine", "type": "service"}, "action": "tenant.create", "resource": {"id": "tenant:trial:demo-company", "type": "tenant", "system": "tenant-engine"}}
|
||||
write_api.decision.effect == "allow" with input as { "tenant": "tenant:trial:demo-company","subject": {"id": "user-engine", "type": "service"}, "action": "tenant.create", "resource": {"id": "tenant:trial:demo-company", "type": "tenant", "system": "tenant-engine"}}
|
||||
}
|
||||
|
||||
test_portal_read if {
|
||||
write_api.decision.effect == "allow" with input as {"subject": {"id": "user-engine", "type": "service"}, "action": "tenant.read", "resource": {"id": "tenant:trial:demo-company", "type": "tenant", "system": "tenant-engine"}}
|
||||
write_api.decision.effect == "allow" with input as { "tenant": "tenant:trial:demo-company","subject": {"id": "user-engine", "type": "service"}, "action": "tenant.read", "resource": {"id": "tenant:trial:demo-company", "type": "tenant", "system": "tenant-engine"}}
|
||||
}
|
||||
|
||||
test_owner_read if {
|
||||
write_api.decision.effect == "allow" with input as {"subject": {"id": "tenant-engine", "type": "service"}, "action": "tenant.read", "resource": {"id": "tenant:trial:demo-company", "type": "tenant", "system": "tenant-engine"}}
|
||||
write_api.decision.effect == "allow" with input as { "tenant": "tenant:trial:demo-company","subject": {"id": "tenant-engine", "type": "service"}, "action": "tenant.read", "resource": {"id": "tenant:trial:demo-company", "type": "tenant", "system": "tenant-engine"}}
|
||||
}
|
||||
|
||||
test_portal_platform if {
|
||||
write_api.decision.effect == "deny" with input as {"subject": {"id": "user-engine", "type": "service"}, "action": "tenant.create", "resource": {"id": "tenant:platform", "type": "tenant", "system": "tenant-engine"}}
|
||||
write_api.decision.effect == "deny" with input as { "tenant": "tenant:platform","subject": {"id": "user-engine", "type": "service"}, "action": "tenant.create", "resource": {"id": "tenant:platform", "type": "tenant", "system": "tenant-engine"}}
|
||||
}
|
||||
|
||||
test_portal_wrong_type if {
|
||||
write_api.decision.effect == "deny" with input as {"subject": {"id": "user-engine", "type": "service"}, "action": "tenant.create", "resource": {"id": "tenant:trial:demo-company", "type": "guardrail", "system": "tenant-engine"}}
|
||||
write_api.decision.effect == "deny" with input as { "tenant": "tenant:trial:demo-company","subject": {"id": "user-engine", "type": "service"}, "action": "tenant.create", "resource": {"id": "tenant:trial:demo-company", "type": "guardrail", "system": "tenant-engine"}}
|
||||
}
|
||||
|
||||
test_portal_no_grants if {
|
||||
write_api.decision.effect == "deny" with input as {"subject": {"id": "user-engine", "type": "service"}, "action": "tenant.role.grant", "resource": {"id": "tenant:trial:demo-company", "type": "role-grant", "system": "tenant-engine"}}
|
||||
write_api.decision.effect == "deny" with input as { "tenant": "tenant:trial:demo-company","subject": {"id": "user-engine", "type": "service"}, "action": "tenant.role.grant", "resource": {"id": "tenant:trial:demo-company", "type": "role-grant", "system": "tenant-engine"}}
|
||||
}
|
||||
|
||||
test_portal_no_retirement if {
|
||||
write_api.decision.effect == "deny" with input as {"subject": {"id": "user-engine", "type": "service"}, "action": "tenant.retire", "resource": {"id": "tenant:trial:demo-company", "type": "tenant", "system": "tenant-engine"}}
|
||||
write_api.decision.effect == "deny" with input as { "tenant": "tenant:trial:demo-company","subject": {"id": "user-engine", "type": "service"}, "action": "tenant.retire", "resource": {"id": "tenant:trial:demo-company", "type": "tenant", "system": "tenant-engine"}}
|
||||
}
|
||||
|
||||
test_portal_no_guardrail if {
|
||||
write_api.decision.effect == "deny" with input as {"subject": {"id": "user-engine", "type": "service"}, "action": "tenant.guardrail.set", "resource": {"id": "tenant:trial:demo-company", "type": "guardrail", "system": "tenant-engine"}}
|
||||
write_api.decision.effect == "deny" with input as { "tenant": "tenant:trial:demo-company","subject": {"id": "user-engine", "type": "service"}, "action": "tenant.guardrail.set", "resource": {"id": "tenant:trial:demo-company", "type": "guardrail", "system": "tenant-engine"}}
|
||||
}
|
||||
|
||||
test_pdp_no_tenant_read if {
|
||||
write_api.decision.effect == "deny" with input as {"subject": {"id": "flex-auth", "type": "service"}, "action": "tenant.read", "resource": {"id": "tenant:trial:demo-company", "type": "tenant", "system": "tenant-engine"}}
|
||||
write_api.decision.effect == "deny" with input as { "tenant": "tenant:trial:demo-company","subject": {"id": "flex-auth", "type": "service"}, "action": "tenant.read", "resource": {"id": "tenant:trial:demo-company", "type": "tenant", "system": "tenant-engine"}}
|
||||
}
|
||||
|
||||
test_unknown_no_read if {
|
||||
write_api.decision.effect == "deny" with input as {"subject": {"id": "unknown", "type": "service"}, "action": "tenant.read", "resource": {"id": "tenant:trial:demo-company", "type": "tenant", "system": "tenant-engine"}}
|
||||
write_api.decision.effect == "deny" with input as { "tenant": "tenant:trial:demo-company","subject": {"id": "unknown", "type": "service"}, "action": "tenant.read", "resource": {"id": "tenant:trial:demo-company", "type": "tenant", "system": "tenant-engine"}}
|
||||
}
|
||||
|
||||
# --- Tenant relation (TEN-DEC-2026-002, FLEX-WP-0022-T02) ---
|
||||
|
||||
scope_tenants := {"tenant:platform", "tenant:friendly:binky", "tenant:acme:prod", "tenant:trial:demo-company"}
|
||||
|
||||
resource_type_for := {
|
||||
"tenant.read": "tenant",
|
||||
"tenant.create": "tenant",
|
||||
"tenant.role.grant": "role-grant",
|
||||
"tenant.role.revoke": "role-grant",
|
||||
"tenant.plan.assign": "plan-assignment",
|
||||
"tenant.update": "tenant",
|
||||
"tenant.retire": "tenant",
|
||||
"tenant.reactivate": "tenant",
|
||||
"tenant.guardrail.read": "guardrail",
|
||||
"tenant.guardrail.set": "guardrail",
|
||||
}
|
||||
|
||||
scoped_request(subject, action, t) := {
|
||||
"tenant": t,
|
||||
"subject": {"id": subject, "type": "service"},
|
||||
"action": action,
|
||||
"resource": {"id": t, "type": resource_type_for[action], "system": "tenant-engine"},
|
||||
}
|
||||
|
||||
effects_for(subject, action, tenants) := {e |
|
||||
some t in tenants
|
||||
e := write_api.decision.effect with input as scoped_request(subject, action, t)
|
||||
}
|
||||
|
||||
# Scope: varying the target tenant never changes the effect.
|
||||
test_tenant_never_changes_effect if {
|
||||
every subject in {"tenant-engine", "flex-auth", "some-other-service"} {
|
||||
every action in write_api.valid_actions {
|
||||
count(effects_for(subject, action, scope_tenants)) == 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# The quantified test above must not pass by denying everything.
|
||||
test_cross_tenant_writes_allowed if {
|
||||
every action in write_api.mutate_actions {
|
||||
effects_for("tenant-engine", action, scope_tenants) == {"allow"}
|
||||
}
|
||||
}
|
||||
|
||||
test_pdp_guardrail_read_is_cross_tenant if {
|
||||
effects_for("flex-auth", "tenant.guardrail.read", scope_tenants) == {"allow"}
|
||||
}
|
||||
|
||||
# user-engine's grant excludes one fixed record; that is not a tenant relation.
|
||||
test_portal_tenant_changes_effect_only_on_platform_record if {
|
||||
every action in write_api.valid_actions {
|
||||
count(effects_for("user-engine", action, scope_tenants - {"tenant:platform"})) == 1
|
||||
}
|
||||
effects_for("user-engine", "tenant.create", {"tenant:platform"}) == {"deny"}
|
||||
}
|
||||
|
||||
# Invariant: tenant must equal resource.id.
|
||||
test_tenant_not_target_denied if {
|
||||
write_api.decision.reason == "tenant_not_target" with input as {
|
||||
"tenant": "tenant:friendly:binky",
|
||||
"subject": {"id": "tenant-engine", "type": "service"},
|
||||
"action": "tenant.create",
|
||||
"resource": {"id": "tenant:acme:prod", "type": "tenant", "system": "tenant-engine"}
|
||||
}
|
||||
}
|
||||
|
||||
test_tenant_absent_denied if {
|
||||
write_api.decision.reason == "tenant_not_target" with input as {
|
||||
"subject": {"id": "tenant-engine", "type": "service"},
|
||||
"action": "tenant.create",
|
||||
"resource": {"id": "tenant:acme:prod", "type": "tenant", "system": "tenant-engine"}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue