From 3397e997833e8c1a7ee7d96e69cf6c03a22fd797 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Sun, 17 May 2026 01:07:35 -0400 Subject: [PATCH] Document the dashboard API Keys management page The dashboard's API Keys page (list plus Create/Rotate/Revoke and the create dialog) had no design-doc coverage even though Authorization.md already documents the constraint model it exposes. Add an "API keys page" section to GatewayDashboardDesign.md describing the table columns, the LDAP-group-gated management actions, the one-time secret reveal, and audit logging. Cross-link it from the constraint-enforcement section of Authorization.md and the CLI section of Authentication.md so the two key-management surfaces reference each other. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/Authentication.md | 5 ++++ docs/Authorization.md | 7 +++++ docs/GatewayDashboardDesign.md | 51 ++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+) diff --git a/docs/Authentication.md b/docs/Authentication.md index 79d524f..e51b35a 100644 --- a/docs/Authentication.md +++ b/docs/Authentication.md @@ -223,6 +223,10 @@ constraints remain fully unconstrained after migration. Key ids are restricted by the parser to ASCII letters, digits, periods, and hyphens so they remain safe to embed in the token format and in URL paths used by administrative tooling. +The CLI is not the only management surface: the dashboard API Keys page +creates, rotates, and revokes keys through the same `IApiKeyAdminStore`. See +[Gateway Dashboard Design](./GatewayDashboardDesign.md#api-keys-page). + ## Scope Serialization Scopes are persisted as a single TEXT column rather than a join table because the set is small, never queried by membership at the database level, and changes atomically with the owning row. `ApiKeyScopeSerializer.Serialize` writes a JSON array sorted with `StringComparer.Ordinal` so equivalent scope sets produce byte-identical column values, which makes audit diffing and database comparisons deterministic: @@ -276,4 +280,5 @@ Singletons are safe because each operation opens its own short-lived `SqliteConn - [Gateway Configuration](./GatewayConfiguration.md) - [Authorization](./Authorization.md) +- [Gateway Dashboard Design](./GatewayDashboardDesign.md) - [Diagnostics](./Diagnostics.md) diff --git a/docs/Authorization.md b/docs/Authorization.md index 97eff1a..9fda4eb 100644 --- a/docs/Authorization.md +++ b/docs/Authorization.md @@ -161,6 +161,12 @@ 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. +Constraints are set when a key is created — through the `apikey create-key` +flags (see [Authentication](./Authentication.md)) or the dashboard API Keys +page create dialog (see +[Gateway Dashboard Design](./GatewayDashboardDesign.md#api-keys-page)). The +dashboard API Keys page also renders each key's effective constraints. + The service checks read constraints for `AddItem`, `AddItem2`, `AddItemBulk`, `SubscribeBulk`, and `AdviseItemBulk`. It checks write constraints for `Write`, `Write2`, `WriteSecured`, and `WriteSecured2`. Successful item @@ -252,6 +258,7 @@ Singleton lifetimes are appropriate because none of the three classes hold per-r ## Related Documentation - [Authentication](./Authentication.md) +- [Gateway Dashboard Design](./GatewayDashboardDesign.md) - [Grpc](./Grpc.md) - [GatewayConfiguration](./GatewayConfiguration.md) - [Galaxy Repository Browse](./GalaxyRepository.md) diff --git a/docs/GatewayDashboardDesign.md b/docs/GatewayDashboardDesign.md index d57f4ed..8ffeea7 100644 --- a/docs/GatewayDashboardDesign.md +++ b/docs/GatewayDashboardDesign.md @@ -49,6 +49,7 @@ Endpoint layout: /dashboard/workers /dashboard/events /dashboard/galaxy +/dashboard/apikeys /dashboard/settings /dashboard/_blazor ``` @@ -83,6 +84,7 @@ MxGateway.Server SessionDetailsPage.razor WorkersPage.razor EventsPage.razor + ApiKeysPage.razor SettingsPage.razor Shared/ MetricCard.razor @@ -91,6 +93,9 @@ MxGateway.Server DashboardSnapshotService.cs DashboardAuthorizationHandler.cs DashboardAuthenticator.cs + DashboardApiKeyAuthorization.cs + DashboardApiKeyManagementService.cs + DashboardApiKeySummary.cs DashboardSnapshot.cs DashboardSessionSummary.cs DashboardWorkerSummary.cs @@ -249,6 +254,52 @@ Show aggregate event diagnostics: Do not display full tag values by default. If value display is later added, make it opt-in and redacted. +### API keys page + +`/dashboard/apikeys` lists the gateway's API keys and, for authorized +operators, manages them. It reads key metadata through the same +`IApiKeyAdminStore` the `apikey` CLI uses, so the dashboard and the CLI act +on one source of truth. + +The table shows one row per key: + +- key id, +- status (`Active` or `Revoked`), +- display name, +- scopes, +- constraints (rendered as `unconstrained` when none are set), +- created timestamp, +- last-used timestamp. + +Key secrets are never listed. Only the peppered hash is stored, and the page +never reconstructs a key. See [Authorization](./Authorization.md#constraint-enforcement) +for what each constraint means and how it is enforced on the gRPC path. + +#### Management actions + +Create, Rotate, and Revoke controls render only when the signed-in user is +authorized. `DashboardApiKeyAuthorization.CanManage` requires an authenticated +principal that is a member of the LDAP `MxGateway:Ldap:RequiredGroup` — the +same group the dashboard login enforces. An anonymous localhost viewer can read +the table but sees no action controls. + +- **Create** opens a dialog for the key id, display name, scope checkboxes + (the `GatewayScopes` catalog), and the optional constraint fields: read and + write subtrees, read and write tag globs, browse subtrees, max write + classification, and the read-alarm-only / read-historized-only flags. +- **Rotate** issues a new secret for an existing key id and invalidates the + old one. +- **Revoke** marks a key revoked; a revoked key cannot be un-revoked. + +Create and Rotate return the assembled `mxgw__` token **once**, +in a one-time banner. It is never shown again, so the operator must copy it +immediately. This mirrors the `apikey create-key` / `rotate-key` CLI. + +Every management action appends an `api_key_audit` entry +(`dashboard-create-key`, `dashboard-rotate-key`, `dashboard-revoke-key`) with +the key id and the caller's remote address. Secrets and pepper values are never +logged. + ### Settings page Show read-only effective configuration: