2026-08-16 02:46:25 +02:00
|
|
|
|
---
|
|
|
|
|
|
id: FLEX-WP-0014
|
|
|
|
|
|
type: workplan
|
|
|
|
|
|
title: "Authorize tenant-engine guardrail actions"
|
|
|
|
|
|
domain: infotech
|
|
|
|
|
|
repo: flex-auth
|
|
|
|
|
|
status: finished
|
|
|
|
|
|
owner: grok
|
|
|
|
|
|
topic_slug: netkingdom
|
|
|
|
|
|
planning_priority: P1
|
|
|
|
|
|
planning_order: 140
|
|
|
|
|
|
depends_on_workplans:
|
|
|
|
|
|
- FLEX-WP-0010
|
|
|
|
|
|
- FLEX-WP-0013
|
|
|
|
|
|
related_workplans:
|
|
|
|
|
|
- TEN-WP-0006
|
|
|
|
|
|
created: "2026-08-16"
|
|
|
|
|
|
updated: "2026-08-16"
|
2026-08-25 20:10:35 +02:00
|
|
|
|
state_hub_workstream_id: "7de17bd2-5e7c-5355-ba1d-40a051a67746"
|
2026-08-16 02:46:25 +02:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
# FLEX-WP-0014 - Authorize tenant-engine guardrail actions
|
|
|
|
|
|
|
|
|
|
|
|
Extend package `tenant-engine.write-api.mutate` from seven actions to nine
|
|
|
|
|
|
with the TEN-WP-0006 guardrail surface. Until they exist here, every
|
|
|
|
|
|
guardrail check resolves to `deny` `unknown_action` — correct fail-closed
|
|
|
|
|
|
behaviour, but the surface is inert.
|
|
|
|
|
|
|
|
|
|
|
|
**Requested by:** `tenant-engine` (TEN-WP-0006, message `5572d198`).
|
|
|
|
|
|
**Blocked on:** FLEX-WP-0013 (four-action production pin). That restore is
|
|
|
|
|
|
done; this workplan starts on a current seven-action package, not a
|
|
|
|
|
|
rolled-back one.
|
|
|
|
|
|
|
|
|
|
|
|
## Requirements from tenant-engine
|
|
|
|
|
|
|
|
|
|
|
|
Two new actions, both on resource type `guardrail`, both against
|
|
|
|
|
|
`system: "tenant-engine"`:
|
|
|
|
|
|
|
|
|
|
|
|
| Action | Resource type | HTTP surface |
|
|
|
|
|
|
|---|---|---|
|
|
|
|
|
|
| `tenant.guardrail.read` | `guardrail` | `GET /tenants/{id}/guardrails?actor=` |
|
|
|
|
|
|
| `tenant.guardrail.set` | `guardrail` | `PUT` / `DELETE /tenants/{id}/guardrails/{limit_key}` |
|
|
|
|
|
|
|
|
|
|
|
|
Strings are live in `tenant-engine/src/tenant_engine/authz.py`
|
|
|
|
|
|
`_RESOURCE_TYPES`. Match them verbatim. `CheckRequest` shape is unchanged:
|
|
|
|
|
|
`subject_type: "service"`, `resource_id` = `tenant_id`.
|
|
|
|
|
|
|
|
|
|
|
|
They are two actions so policy can grant a PDP the read without granting
|
|
|
|
|
|
the write. tenant-engine's own tests call the read as `actor=flex-auth`.
|
|
|
|
|
|
|
|
|
|
|
|
`v1` stays the activated revision; the identifying fact is that `v1` as of
|
|
|
|
|
|
this commit contains the two guardrail actions.
|
|
|
|
|
|
|
|
|
|
|
|
Do **not** deploy a new production image in this workplan until the package
|
|
|
|
|
|
is verified locally. Image + pin is a follow-on, named in T04.
|
|
|
|
|
|
|
|
|
|
|
|
## T01 - Extend the policy package with the guardrail actions
|
|
|
|
|
|
|
|
|
|
|
|
```task
|
|
|
|
|
|
id: FLEX-WP-0014-T01
|
|
|
|
|
|
status: done
|
|
|
|
|
|
priority: high
|
2026-08-25 20:10:35 +02:00
|
|
|
|
state_hub_task_id: "34fb239a-7f06-5e96-9f10-a6bbb85bf4c3"
|
2026-08-16 02:46:25 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Add `tenant.guardrail.read` and `tenant.guardrail.set` to
|
|
|
|
|
|
`valid_actions`, the frontmatter `actions:` list, and
|
|
|
|
|
|
`protected_system_manifest.yaml`. Add resource type `guardrail`.
|
|
|
|
|
|
|
|
|
|
|
|
CARING: `View` for the read (already used by qonto-assistant), `EditAny`
|
|
|
|
|
|
for the set (already used by `tenant.update`). Extend
|
|
|
|
|
|
`caring.capabilities` with `View`.
|
|
|
|
|
|
|
|
|
|
|
|
Register `flex-auth` as a second service subject that is allowed
|
|
|
|
|
|
`tenant.guardrail.read` and nothing else. Update `subject_manifest.yaml`
|
|
|
|
|
|
and rebuild `examples/tenant-engine/registry_snapshot.json`.
|
|
|
|
|
|
|
|
|
|
|
|
Update `docs/tenant-engine-action-vocabulary.md` and
|
|
|
|
|
|
`docs/tenant-engine-resource-namespace.md`. Keep the existing note that
|
|
|
|
|
|
this policy governs who may call tenant-engine's admin API, not a tenant's
|
|
|
|
|
|
own capability roles.
|
|
|
|
|
|
|
|
|
|
|
|
Done when `valid_actions` carries all nine actions, the new resource type
|
|
|
|
|
|
is declared, and `flex-auth` is a registered read-only subject.
|
|
|
|
|
|
|
|
|
|
|
|
Done 2026-08-16: nine actions in frontmatter, `valid_actions`, and the
|
|
|
|
|
|
protected-system manifest. Resource type `guardrail` added. CARING
|
|
|
|
|
|
`View` on the read, `EditAny` on the set. `flex-auth` registered as a
|
|
|
|
|
|
read-only subject. Registry snapshot rebuilt (2 subjects, 2 groups).
|
|
|
|
|
|
|
|
|
|
|
|
## T02 - Decide how the read/write split is used today
|
|
|
|
|
|
|
|
|
|
|
|
```task
|
|
|
|
|
|
id: FLEX-WP-0014-T02
|
|
|
|
|
|
status: done
|
|
|
|
|
|
priority: medium
|
2026-08-25 20:10:35 +02:00
|
|
|
|
state_hub_task_id: "8ea87d5f-883a-5d63-89bb-33a3d0963472"
|
2026-08-16 02:46:25 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
tenant-engine split the actions so a PDP can read ceilings without being
|
|
|
|
|
|
able to change them. Decide whether that split is used now or only reserved.
|
|
|
|
|
|
|
|
|
|
|
|
Record the decision in `policy_package.md`. Using it now — `flex-auth` may
|
|
|
|
|
|
read, `tenant-engine` may read and set, nobody else — is the expected
|
|
|
|
|
|
outcome unless a concrete reason says otherwise. Inventing an `ops` write
|
|
|
|
|
|
subject is out of scope; writes stay on the existing service identity.
|
|
|
|
|
|
|
|
|
|
|
|
Done when the decision and its reasoning are recorded and implemented.
|
|
|
|
|
|
|
|
|
|
|
|
Done 2026-08-16: **yes — use the split now.** Recorded under "Decision:
|
|
|
|
|
|
use the read/write split now" in
|
|
|
|
|
|
`examples/tenant-engine/policy_package.md`. `flex-auth` may
|
|
|
|
|
|
`tenant.guardrail.read` and is denied `action_not_granted` on
|
|
|
|
|
|
`tenant.guardrail.set`. `tenant-engine` may do both. No `ops` write
|
|
|
|
|
|
subject.
|
|
|
|
|
|
|
|
|
|
|
|
## T03 - Fixtures and verification
|
|
|
|
|
|
|
|
|
|
|
|
```task
|
|
|
|
|
|
id: FLEX-WP-0014-T03
|
|
|
|
|
|
status: done
|
|
|
|
|
|
priority: high
|
2026-08-25 20:10:35 +02:00
|
|
|
|
state_hub_task_id: "1029d2ce-bb37-530e-91ef-ded86f6f5be8"
|
2026-08-16 02:46:25 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Add fixture pairs for: authorized `tenant-engine` read and set → allow;
|
|
|
|
|
|
authorized `flex-auth` read → allow; `flex-auth` set → deny
|
|
|
|
|
|
`action_not_granted`; unknown subject → deny `unknown_subject`; misspelled
|
|
|
|
|
|
guardrail action → deny `unknown_action`.
|
|
|
|
|
|
|
|
|
|
|
|
Verify against the real binary: `test-policy`, `load-registry`, `check` on
|
|
|
|
|
|
standalone request files. Then close the loop with a live `flex-auth serve`
|
|
|
|
|
|
and a real `tenant-engine` on `TENANT_ENGINE_FLEX_AUTH_URL`, through
|
|
|
|
|
|
unmodified `FlexAuthWriteAuthorizer`:
|
|
|
|
|
|
|
|
|
|
|
|
- `GET /tenants/{id}/guardrails?actor=flex-auth` → 200
|
|
|
|
|
|
- `GET ...?actor=ops` → 403
|
|
|
|
|
|
- `PUT ...` as `actor=tenant-engine` → 200
|
|
|
|
|
|
- `PUT ...` as `actor=flex-auth` → 403
|
|
|
|
|
|
- `PUT ...` as `actor=ops` → 403
|
|
|
|
|
|
|
|
|
|
|
|
Done when those five calls match, `go test ./...` is green, and
|
|
|
|
|
|
`gofmt`/`go vet` are clean.
|
|
|
|
|
|
|
|
|
|
|
|
Done 2026-08-16: 17/17 Rego tests, 23/23 fixtures. `load-registry` loads
|
|
|
|
|
|
clean. Standalone `check` files return the expected allow/deny.
|
|
|
|
|
|
|
|
|
|
|
|
Live `flex-auth serve` on `127.0.0.1:9098` + real `tenant-engine` on
|
|
|
|
|
|
`TENANT_ENGINE_FLEX_AUTH_URL`:
|
|
|
|
|
|
|
|
|
|
|
|
| Call | tenant-engine | flex-auth |
|
|
|
|
|
|
|---|---|---|
|
|
|
|
|
|
| `POST /tenants` actor=`tenant-engine` | 201 | allow |
|
|
|
|
|
|
| `GET .../guardrails?actor=flex-auth` | 200 `spend.monthly=25000` grouping | allow |
|
|
|
|
|
|
| `GET ...?actor=ops` | 403 `write_denied` | deny `unknown_subject` |
|
|
|
|
|
|
| `PUT .../spend.monthly` actor=`tenant-engine` | 200 override `9000` v2 | allow |
|
|
|
|
|
|
| `PUT ...` actor=`flex-auth` | 403 `write_denied` | deny `action_not_granted` |
|
|
|
|
|
|
| `PUT ...` actor=`ops` | 403 `write_denied` | deny `unknown_subject` |
|
|
|
|
|
|
|
|
|
|
|
|
`go test ./...` green; `gofmt -l` empty; `go vet ./...` clean.
|
|
|
|
|
|
|
|
|
|
|
|
## T04 - Closure and handoff
|
|
|
|
|
|
|
|
|
|
|
|
```task
|
|
|
|
|
|
id: FLEX-WP-0014-T04
|
|
|
|
|
|
status: done
|
|
|
|
|
|
priority: low
|
2026-08-25 20:10:35 +02:00
|
|
|
|
state_hub_task_id: "b2b44215-ef08-5c50-b466-190156a88ef3"
|
2026-08-16 02:46:25 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Confirm T01–T03. Notify `tenant-engine` naming the policy revision. State
|
|
|
|
|
|
plainly whether the nine-action package is **in source** or **deployed** —
|
|
|
|
|
|
FLEX-WP-0010 taught that distinction the hard way. Production rollout of a
|
|
|
|
|
|
new image is not implied by this workplan; it is a separate pin after CI
|
|
|
|
|
|
publishes a digest.
|
|
|
|
|
|
|
|
|
|
|
|
Done when the handoff is sent and this workplan is `finished`.
|
|
|
|
|
|
|
|
|
|
|
|
Done 2026-08-16: T01–T03 confirmed. Policy revision for the handoff is
|
|
|
|
|
|
package `tenant-engine.write-api.mutate` **version `v1`**, now carrying
|
|
|
|
|
|
all nine actions. The version string is unchanged because the package is
|
|
|
|
|
|
additive.
|
|
|
|
|
|
|
|
|
|
|
|
**In source, not in the cluster.** Production `flex-auth-tenant-engine`
|
|
|
|
|
|
still serves `sha256:9320df39` (seven actions). A CI-built image from
|
|
|
|
|
|
this commit plus a pin of `flex-auth-tenant-engine` is the remaining
|
|
|
|
|
|
step before a live guardrail check succeeds against the cluster Service.
|
|
|
|
|
|
tenant-engine's own TEN-WP-0006 surface is also not deployed, so the
|
|
|
|
|
|
cluster path is not yet the binding one.
|
2026-08-16 09:57:07 +02:00
|
|
|
|
|
|
|
|
|
|
Deployed 2026-08-16 after operator "sync and commit / deploy flex-auth":
|
|
|
|
|
|
CI image `main-f304688` /
|
|
|
|
|
|
`sha256:1bf060e61122693ce98359c167cc5fe8bdafc84e097e090eaa71af94d0f27cbc`
|
|
|
|
|
|
is live on `flex-auth-tenant-engine`. Approval
|
|
|
|
|
|
`8d1efcea-93c7-436b-a749-e5d464e94a33`. Rollback target remains
|
|
|
|
|
|
`sha256:9320df39`. `flex-auth-user-engine` stayed on `sha256:1f529037`.
|
|
|
|
|
|
|
|
|
|
|
|
Live `/v1/check` against the Service:
|
|
|
|
|
|
|
|
|
|
|
|
| Request | Effect | Decision |
|
|
|
|
|
|
| --- | --- | --- |
|
|
|
|
|
|
| `tenant.create` | allow `write_api_policy_matched` | `decision:ef18e8bbd2dead6a` |
|
|
|
|
|
|
| `tenant.retire` | allow `write_api_policy_matched` | `decision:996663f6902d0762` |
|
|
|
|
|
|
| `tenant.guardrail.read` as flex-auth | allow `write_api_policy_matched` | `decision:9b9e540de13cf7cf` |
|
|
|
|
|
|
| `tenant.guardrail.set` as tenant-engine | allow `write_api_policy_matched` | `decision:20c82bc38501a343` |
|
|
|
|
|
|
| `tenant.guardrail.set` as flex-auth | deny `action_not_granted` | `decision:a057a7030df65208` |
|
|
|
|
|
|
| misspelled guardrail | deny `unknown_action` | `decision:18ae70a79c6d47c6` |
|
|
|
|
|
|
| unknown subject | deny `unknown_subject` | `decision:a98c59c6a34f84ad` |
|
|
|
|
|
|
| misspelled lifecycle | deny `unknown_action` | `decision:1b3012abb4e2b27d` |
|
|
|
|
|
|
|
|
|
|
|
|
TEN-WP-0007 can pin the tenant-engine guardrail image. The seven
|
|
|
|
|
|
pre-existing actions still allow.
|