fix: authorize native user portal tenant onboarding
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
This commit is contained in:
parent
88b354377c
commit
dd8dd51743
3 changed files with 378 additions and 4 deletions
|
|
@ -2,10 +2,11 @@
|
|||
id: tenant-engine.write-api.mutate
|
||||
name: tenant-engine Write API authorization
|
||||
namespace: tenant-engine:tenant
|
||||
version: v1
|
||||
version: v2
|
||||
status: ready
|
||||
package: flexauth.tenant_engine.write_api
|
||||
actions:
|
||||
- tenant.read
|
||||
- tenant.create
|
||||
- tenant.role.grant
|
||||
- tenant.role.revoke
|
||||
|
|
@ -157,6 +158,26 @@ Reasoning:
|
|||
**Revisit when** a second human/operator subject is registered, or when
|
||||
flex-auth itself needs to read as a different subject id than `flex-auth`.
|
||||
|
||||
## User portal onboarding (NK-WP-0036, 2026-09-11)
|
||||
|
||||
The native User Engine platform portal is the existing tenant-onboarding PEP.
|
||||
It authenticates the human through KeyCape and checks `tenant:platform` plus
|
||||
`platform-operator` before calling Tenant Engine as `user-engine`. Register
|
||||
that real service subject for `tenant.create` and `tenant.read` on non-platform
|
||||
`tenant` resources only. No role grants, lifecycle mutations, plan assignment
|
||||
or guardrail changes are granted to it. Tenant Engine's own read is also
|
||||
registered because its shipped read endpoint now calls `tenant.read`.
|
||||
|
||||
This repairs the demonstrated native demo-company onboarding refusal; it does
|
||||
not rename the caller to tenant-engine or weaken inbound caller authentication.
|
||||
The PDP's authenticated caller remains the exact Tenant Engine ServiceAccount,
|
||||
bound to resource.system tenant-engine. Tenant Engine's namespace-and-pod ingress
|
||||
continues to admit only User Engine. Human operator entitlement stays with the
|
||||
portal's existing PEP; no client-supplied human assurance is invented here.
|
||||
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.
|
||||
|
||||
## Rules
|
||||
|
||||
```rego
|
||||
|
|
@ -165,6 +186,7 @@ import future.keywords.if
|
|||
import future.keywords.in
|
||||
|
||||
valid_actions := {
|
||||
"tenant.read",
|
||||
"tenant.create",
|
||||
"tenant.role.grant",
|
||||
"tenant.role.revoke",
|
||||
|
|
@ -178,9 +200,9 @@ valid_actions := {
|
|||
|
||||
read_actions := {"tenant.guardrail.read"}
|
||||
|
||||
mutate_actions := valid_actions - read_actions
|
||||
mutate_actions := valid_actions - read_actions - {"tenant.read"}
|
||||
|
||||
known_subjects := {"tenant-engine", "flex-auth"}
|
||||
known_subjects := {"tenant-engine", "flex-auth", "user-engine"}
|
||||
|
||||
read_subjects := {"tenant-engine", "flex-auth"}
|
||||
|
||||
|
|
@ -206,6 +228,23 @@ allowed if {
|
|||
input.subject.id in mutate_subjects
|
||||
}
|
||||
|
||||
allowed if {
|
||||
input.resource.system == "tenant-engine"
|
||||
input.resource.type == "tenant"
|
||||
input.resource.id != "tenant:platform"
|
||||
input.action in {"tenant.create", "tenant.read"}
|
||||
input.subject.type == "service"
|
||||
input.subject.id == "user-engine"
|
||||
}
|
||||
|
||||
allowed if {
|
||||
input.resource.system == "tenant-engine"
|
||||
input.resource.type == "tenant"
|
||||
input.action == "tenant.read"
|
||||
input.subject.type == "service"
|
||||
input.subject.id == "tenant-engine"
|
||||
}
|
||||
|
||||
default first_denial := "no_matching_rule"
|
||||
|
||||
first_denial := "wrong_system" if {
|
||||
|
|
@ -368,4 +407,44 @@ test_misspelled_guardrail_action_denied if {
|
|||
"resource": {"id": "t-1", "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"}}
|
||||
}
|
||||
|
||||
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"}}
|
||||
}
|
||||
|
||||
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"}}
|
||||
}
|
||||
|
||||
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"}}
|
||||
}
|
||||
|
||||
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"}}
|
||||
}
|
||||
|
||||
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"}}
|
||||
}
|
||||
|
||||
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"}}
|
||||
}
|
||||
|
||||
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"}}
|
||||
}
|
||||
|
||||
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"}}
|
||||
}
|
||||
|
||||
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"}}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue