Align docs with StyleGuide and add CLAUDE.md

- Rename 16 kebab-case docs to PascalCase per StyleGuide
- Move per-language client design docs from docs/ to clients/<lang>/
  alongside their READMEs
- Add ## Related Documentation sections to 15 docs that lacked one
- Fix sentence-case violations in H3 headings (StyleGuide rule)
- Update cross-references in gateway.md, client READMEs, scripts,
  and generate-proto.ps1 helpers to follow the new paths
- Add CLAUDE.md with build/test commands, the source-update
  verification matrix, the parity-first contract, and pointers
  to MXAccess and Galaxy Repository analysis sources

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-04-30 10:19:22 -04:00
parent 133c83029b
commit 51a9dadf62
45 changed files with 522 additions and 134 deletions
+45 -2
View File
@@ -1,6 +1,8 @@
# Gateway gRPC Authorization
The authorization subsystem enforces per-RPC scope checks against the authenticated `ApiKeyIdentity` produced by the authentication layer, so service implementations never need to repeat permission logic.
The authorization subsystem has two layers. The gRPC interceptor enforces the
verb scope required by the RPC. Service-layer constraint checks then narrow
what an authenticated API key can browse, read, or write inside the Galaxy.
## Overview
@@ -12,6 +14,8 @@ The participating types live under `src/MxGateway.Server/Security/Authorization/
- `GatewayGrpcScopeResolver` maps a request message (and, for `MxCommandRequest`, the inner `MxCommandKind`) to the scope string that must be present on the caller.
- `GatewayScopes` exposes the canonical scope constants used by the resolver and any downstream consumer.
- `GatewayRequestIdentityAccessor` and `IGatewayRequestIdentityAccessor` expose the verified identity to handlers and any service code that runs inside the call.
- `IConstraintEnforcer` applies optional API-key constraints against the
cached Galaxy hierarchy from service bodies.
- `GrpcAuthorizationServiceCollectionExtensions` wires the components into the DI container and the gRPC pipeline.
The `ApiKeyIdentity` consumed here is produced by the authentication layer; see [Authentication](./Authentication.md) for how it is built and how scopes are persisted.
@@ -21,7 +25,9 @@ The `ApiKeyIdentity` consumed here is produced by the authentication layer; see
Centralizing the policy in `GatewayGrpcAuthorizationInterceptor` produces three concrete benefits:
1. Every RPC defined in `MxAccessGatewayService` is covered by construction. A new RPC inherits the check the moment its request type is added to `GatewayGrpcScopeResolver`, instead of relying on each service method to remember to call an authorization helper.
2. The service class stays a thin translator between proto contracts and domain calls. RPC methods do not branch on identity or scope, which keeps the AGENTS.md guideline that gRPC handlers contain no policy.
2. Verb-scope policy stays centralized. Request-specific constraints still run
in service bodies because they need command payloads, item handles, and
Galaxy metadata that the interceptor should not inspect.
3. Authentication and authorization happen in one place, so the gRPC `Status` mapping is consistent. A failed key check always returns `Unauthenticated`, and a missing scope always returns `PermissionDenied` with the offending scope name.
## Interceptor Flow
@@ -131,6 +137,43 @@ private static string ResolveCommandScope(MxCommandKind kind)
Reads (`Register`, `AddItem`, `Advise`, and any other unspecified kind) fall through to `InvokeRead`, which keeps the matrix small while still separating reads from writes, secured writes, metadata lookups, event drains, and worker shutdown.
## Constraint Enforcement
`ApiKeyIdentity.Constraints` is optional. Empty constraints preserve the
previous behavior: the key is authorized only by its verb scopes. Non-empty
constraints are stored as JSON in `api_keys.constraints` and are applied by
`IConstraintEnforcer` after the interceptor succeeds.
Supported constraints are:
| Constraint | Meaning |
|------------|---------|
| `read_subtrees` | Contained-path globs allowed for read/subscription commands. |
| `write_subtrees` | Contained-path globs allowed for write commands. |
| `read_tag_globs` | Tag-address globs allowed for read/subscription commands. |
| `write_tag_globs` | Tag-address globs allowed for write commands. |
| `max_write_classification` | Maximum Galaxy attribute `security_classification` a key may write. |
| `browse_subtrees` | Contained-path globs used to filter Galaxy browse results and deploy-event counts. |
| `read_alarm_only` | Read/subscription commands must target objects with alarm-bearing attributes. |
| `read_historized_only` | Read/subscription commands must target objects with historized attributes. |
Glob matching is anchored, case-insensitive, and supports `*` and `?`.
Subtree and tag glob lists are alternatives: matching either list allows that
scope dimension. Empty lists mean unconstrained for that dimension.
The service checks read constraints for `AddItem`, `AddItem2`, `AddItemBulk`,
`SubscribeBulk`, and `AdviseItemBulk`. It checks write constraints for
`Write`, `Write2`, `WriteSecured`, and `WriteSecured2`. Successful item
registrations are tracked per session so later item-handle commands resolve
back to the original tag address. If a constrained key presents an unknown item
handle, the gateway fails closed.
Non-bulk constraint failures return gRPC `PermissionDenied`. Bulk read
commands preserve input order and return a failed `SubscribeResult` for each
denied item while still forwarding allowed items to the worker. Every denial
adds an `api_key_audit` entry with the key id, command kind, target, and
blocking constraint; secured values and raw credentials are never logged.
## Scope Catalog
`GatewayScopes` is the single source of truth for scope strings. Every entry is currently mapped by either the resolver or another security component: