refactor(iam): pluggable IAM regime via authenticate/authorise contract (#853)

The gateway no longer holds any policy state — capability sets, role
definitions, workspace scope rules.  Per the IAM contract it asks the
regime "may this identity perform this capability on this resource?"
per request.  That moves the OSS role-based regime entirely into
iam-svc, which can be replaced (SSO, ABAC, ReBAC) without changing
the gateway, the wire protocol, or backend services.

Contract:
- authenticate(credential) -> Identity (handle, workspace,
  principal_id, source).  No roles, claims, or policy state surface
  to the gateway.
- authorise(identity, capability, resource, parameters) -> (allow,
  ttl).  Cached per-decision (regime TTL clamped above; fail-closed
  on regime errors).
- authorise_many available as a fan-out variant.

Operation registry drives every authorisation decision:
- /api/v1/iam -> IamEndpoint, looks up bare op name (create-user,
  list-workspaces, ...).
- /api/v1/{kind} -> RegistryRoutedVariableEndpoint, <kind>:<op>
  (config:get, flow:list-blueprints, librarian:add-document, ...).
- /api/v1/flow/{flow}/service/{kind} -> flow-service:<kind>.
- /api/v1/flow/{flow}/{import,export}/{kind} ->
  flow-{import,export}:<kind>.
- WS Mux per-frame -> flow-service:<kind>; closes a gap where
  authenticated users could hit any service kind.
85 operations registered across the surface.

JWT carries identity only — sub + workspace.  The roles claim is gone;
the gateway never reads policy state from a credential.

The three coarse *_KIND_CAPABILITY maps are removed.  The registry is
the only source of truth for the capability + resource shape of an
operation.  Tests migrated to the new Identity shape and to
authorise()-mocked auth doubles.

Specs updated: docs/tech-specs/iam-contract.md (Identity surface,
caching, registry-naming conventions), iam.md (JWT shape, gateway
flow, role section reframed as OSS-regime detail), iam-protocol.md
(positioned as one implementation of the contract).
This commit is contained in:
cybermaggedon 2026-04-28 16:19:41 +01:00 committed by GitHub
parent 9f2d9adcb1
commit 5e28d3cce0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 2359 additions and 587 deletions

View file

@ -22,8 +22,16 @@ are the boundaries around data, and who owns what?
A workspace is the primary isolation boundary. It represents an
organisation, team, or independent operating unit. All data belongs to
exactly one workspace. Cross-workspace access is never permitted through
the API.
exactly one workspace.
Cross-workspace access through the API is gated by the IAM regime
(see [`iam-contract.md`](iam-contract.md)). In the OSS distribution,
the role table defined in [`capabilities.md`](capabilities.md)
permits cross-workspace operation only to the `admin` role; the
`reader` and `writer` roles are constrained to a single assigned
workspace per credential. Other regimes can model the relationship
between identity and workspace differently — the gateway makes no
assumption.
A workspace owns:
- Source documents
@ -279,9 +287,18 @@ A typical workflow:
The current codebase uses a `user` field in message metadata and storage
partition keys to identify the workspace. The `collection` field
identifies the collection within that workspace. The IAM spec describes
how the gateway maps authenticated credentials to a workspace identity
and sets these fields.
identifies the collection within that workspace.
The gateway is the single point at which workspace gets stamped onto
outbound pub/sub messages. An incoming credential authenticates to a
workspace (the credential's binding, not a user-to-workspace lookup —
see [`iam-contract.md`](iam-contract.md) and the *Identity surface*
section of [`iam.md`](iam.md)); any caller-supplied workspace on the
request is reconciled against the authenticated identity by the IAM
regime; the resolved value is what the gateway writes into outgoing
messages and the storage layers' partition keys. Backend services
trust the workspace they receive — defense-in-depth happens at the
gateway, not at the bus.
For details on how each storage backend implements this scoping, see:
@ -302,7 +319,10 @@ For details on how each storage backend implements this scoping, see:
## References
- [Identity and Access Management](iam.md)
- [IAM Contract](iam-contract.md) — gateway↔IAM regime abstraction.
- [Identity and Access Management](iam.md) — gateway-side framing.
- [Capability Vocabulary](capabilities.md) — capability strings and
the OSS role bundles that decide cross-workspace eligibility.
- [Collection Management](collection-management.md)
- [Entity-Centric Graph](entity-centric-graph.md)
- [Neo4j User Collection Isolation](neo4j-user-collection-isolation.md)