iam: self-service ops, optional workspace filters, Mux service routing (#855)

Three threads, all reinforcing the contract's system-level vs.
workspace-association distinction.

WS Mux service routing
- tg-show-flows (and any workspace-level service over the WS) was
  failing with "unknown service" because the post-refactor Mux
  unconditionally looked up flow-service:<kind>.  Now branches on
  the envelope's flow field: with flow → flow-service:<kind>;
  without flow → <kind>:<op> from the inner body; with bare op
  lookup for service=iam.  Resource and parameters come from the
  matched op's own extractors — same path the HTTP endpoints take.

Optional workspace on system-level user/key ops
- list-users returns the deployment-wide list when no workspace is
  supplied, filters when one is.  get-user, update-user,
  disable-user, enable-user, delete-user, reset-password,
  create-api-key, list-api-keys, revoke-api-key all treat workspace
  as an optional integrity check rather than a required argument.
- create-user keeps workspace required — there it's the new user's
  home-workspace binding, a parameter rather than an address.
- API keys reclassified as SYSTEM-level resources.  By the same
  reasoning that makes users system-level, an API key is a
  credential record on a deployment-wide registry; the workspace it
  authenticates to is a property, not a containment.

Self-service surface
- whoami: returns the caller's own user record.  AUTHENTICATED-only;
  no users:read capability required.  Foundation for UI affordances
  that depend on the caller's permissions.
- bootstrap-status: POST /api/v1/auth/bootstrap-status, PUBLIC,
  side-effect-free.  Returns {bootstrap_available: bool} so a
  first-run UI can decide whether to render setup without consuming
  the bootstrap op.
- Gateway now injects actor=identity.handle on every authenticated
  forward to iam-svc (IamEndpoint and WS Mux iam path), overwriting
  any caller-supplied value.  Underpins whoami, audit logging, and
  future regime-side decisions that need actor identity.
- tg-whoami and tg-update-user CLIs.

Spec polish
- iam-contract.md: actor-injection rule documented; whoami /
  bootstrap-status added to operations list; permission-scope
  framing tightened (workspace scope is a property of the grant,
  not the user or role).
- iam.md: self-service section; gateway flow gains the actor-
  injection step; role section reframed so iam-svc constraints
  don't leak into contract-level prose.
- iam-protocol.md: ops table updated for whoami, bootstrap-status,
  optional-workspace pattern; bootstrap_available added to the
  IamResponse listing.
This commit is contained in:
cybermaggedon 2026-04-28 22:13:12 +01:00 committed by GitHub
parent 6302eb8c97
commit 9fc1d4527b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 555 additions and 147 deletions

View file

@ -83,17 +83,16 @@ The four arguments separate concerns:
identifier. See *The Resource model* below.
- **`parameters`** — operation-specific data that the regime may
need to consider beyond the resource identifier. Used when a
decision depends on attributes the request supplies — e.g. an
admin scoped to one workspace creating a user *with workspace
association W*: the resource is the system-level user registry,
and W is a parameter the regime checks against the admin's
scope.
decision depends on attributes the request supplies — e.g.
creating a user *with workspace association W*: the resource is
the system-level user registry, and W is a parameter the regime
checks against the caller's permissions for `users:write`.
Different regimes use the four arguments differently — the OSS
regime checks role bundles against the capability and the role's
workspace scope against parameters; an SSO regime might consult an
upstream IdP's group memberships; an ABAC regime evaluates a
policy with all four as inputs. The contract is unchanged.
Different regimes use the four arguments differently — one regime
might evaluate role bundles whose grants carry workspace scope;
another might consult upstream IdP group memberships; an ABAC
regime evaluates a policy with all four as inputs. The contract
is unchanged.
### `authorise_many`
@ -129,14 +128,49 @@ most of them) but the operation set the gateway can forward is:
`revoke-api-key`, `change-password`, `reset-password`
- Workspace management: `create-workspace`, `list-workspaces`,
`get-workspace`, `update-workspace`, `disable-workspace`
- Session management: `login`
- Session management: `login`, `whoami`
- Key management: `get-signing-key-public`, `rotate-signing-key`
- Bootstrap: `bootstrap`
- Bootstrap: `bootstrap`, `bootstrap-status`
`whoami` is the self-read counterpart to `get-user`: any
authenticated caller can read their own identity record without
holding a user-management capability. It is the gating-free probe
a UI uses to render affordances appropriate to the caller's role.
`bootstrap-status` is a side-effect-free probe of whether an
unconsumed `bootstrap` call would currently succeed. It exists so
a first-run UI can decide whether to render setup without invoking
the consuming `bootstrap` op. Public — no authentication.
A regime that does not support one of these (e.g. an SSO regime
where users are managed in the IdP) returns a defined "not
supported" error; the gateway surfaces it as a 501.
### Actor injection
For any management operation forwarded by the gateway after
authentication, the gateway injects the authenticated caller's
`handle` as an `actor` field on the request. Regimes use `actor`
to identify *who is making the request* — distinct from the
operation's target (which lives in `user_id` / `key_id` /
`workspace_record` / etc.) — for purposes such as:
- Self-service operations (`whoami`, `change-password`) that
resolve "the caller" without taking a target argument.
- Audit logging, where the actor is recorded against the change.
- Decisions that depend on the resolved resource state. The
gateway authorises against the parameters on the request, but it
cannot know the resolved resource's actual properties (e.g. the
workspace association of a target user) before the regime loads
it. When that matters, the regime can re-decide using the
actor's permissions and the resolved record — closing a class
of cases the gateway-side check can't see.
Caller-supplied `actor` values on the request body are overwritten
by the gateway — the gateway is the only authority for actor
identity, and a regime that consults `actor` can rely on it being
authentic.
## The `Identity` surface
`Identity` is *mostly* opaque. The gateway holds the value as a
@ -327,13 +361,16 @@ contract via:
- Credentials are API keys (opaque) or JWTs (Ed25519, locally
validated by the gateway against the regime's published public
key).
- `authorise` reduces to a role-and-workspace-scope check against
the role table defined in [`capabilities.md`](capabilities.md).
- `authorise` reduces to a lookup against the role bundles in
[`capabilities.md`](capabilities.md), with each grant's workspace
scope checked against the operation's workspace component.
- Identity, user, and workspace records live in Cassandra.
The OSS regime is deliberately simple — three roles, single
home-workspace per user (a regime data-model decision, not a
contract assertion), no policy language.
The OSS regime is deliberately simple — three roles, a single
workspace association per user (a regime data-model decision, not
a contract assertion), no policy language. Other regimes can
grant the same user different permissions in different workspaces
without changing anything outside the regime.
### Future regimes