fix(archreview-p1): SEC-02/12/20 dashboard + observability hardening
- SEC-02: DashboardAuthorizationHandler restricts the loopback and Authentication:Mode=Disabled bypasses to read-only. They now satisfy only a Viewer-bearing requirement (AnyDashboardRole), never AdminOnly, so anonymous localhost can view the dashboard but cannot reach API-key CRUD or session Close/Kill at the policy layer (previously guarded only by service re-checks). - SEC-12: DashboardSessionAdminService emits canonical AuditEvents through IAuditWriter (actions dashboard-close-session / dashboard-kill-worker, category SessionAdmin) on Success/Failure/Denied, mirroring the API-key audit path so destructive session actions leave durable, queryable rows. - SEC-20: drop the unbounded session_id tag from the exported mxgateway.heartbeats.failed counter (per-session detail stays in the snapshot/log). Docs updated same-change: CLAUDE.md (read-only loopback + 5-min bearer), GatewayDashboardDesign.md (bypass scoping + session-admin audit), Metrics.md. Server build clean; 30/30 targeted + 295/295 Dashboard/Security/App/Metrics sweep.
This commit is contained in:
@@ -257,6 +257,15 @@ gated by a confirmation dialog before it reaches `ISessionManager`.
|
||||
is killed immediately with no graceful-shutdown attempt. The session is
|
||||
removed from the registry and the open-session slot is released either way.
|
||||
|
||||
Both actions write a canonical `AuditEvent` through `IAuditWriter` into the
|
||||
`audit_event` store (category `SessionAdmin`, actions `dashboard-close-session`
|
||||
and `dashboard-kill-worker`) in addition to the operational `ILogger` line — so a
|
||||
worker killed mid-production leaves a durable, queryable row rather than only a
|
||||
rotatable log entry. The event records the LDAP actor, the session id (`Target`),
|
||||
the remote address, and an outcome of `Success`, `Failure`, or `Denied`
|
||||
(an unauthorized attempt is still audited as `Denied`). This mirrors the API-key
|
||||
management audit path.
|
||||
|
||||
### Workers page
|
||||
|
||||
Show:
|
||||
@@ -436,11 +445,16 @@ Three authorization policies are registered:
|
||||
cookie OR a `MxGateway.Dashboard.HubToken` bearer (used by WebSocket upgrades
|
||||
where the cookie can't be forwarded).
|
||||
|
||||
Two environmental bypasses still apply: `MxGateway:Authentication:Mode = Disabled`
|
||||
authorizes every request, and `MxGateway:Dashboard:AllowAnonymousLocalhost`
|
||||
(default `true`) authorizes any loopback request without a role check. Remote
|
||||
requests always require an authenticated principal carrying at least the
|
||||
Viewer role.
|
||||
Two environmental bypasses still apply, both scoped to **read-only** access:
|
||||
`MxGateway:Authentication:Mode = Disabled` and `MxGateway:Dashboard:AllowAnonymousLocalhost`
|
||||
(default `true`, loopback only) each satisfy a requirement that includes the Viewer
|
||||
role (`AnyDashboardRole`) but **never** the `AdminOnly` requirement. Destructive/admin
|
||||
surfaces (API-key CRUD, session Close, worker Kill) still demand a real `Administrator`
|
||||
role claim, so the "anonymous localhost is read-only" contract holds at the policy layer
|
||||
rather than only in downstream service re-checks. Remote requests (outside the `Disabled`
|
||||
bypass) always require an authenticated principal carrying at least the Viewer role. The
|
||||
loopback test trusts `Connection.RemoteIpAddress`; if forwarded-headers middleware is ever
|
||||
added upstream, `DashboardAuthorizationHandler.IsLoopbackRequest()` must be revisited.
|
||||
|
||||
### DisableLogin dev bypass
|
||||
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ All counters are `Counter<long>`. Tag values come from the call sites listed und
|
||||
| `mxgateway.faults` | `category` | Faults reported by session, event, or worker code paths. The category is a `SessionManagerErrorCode` or `WorkerClientErrorCode` name. |
|
||||
| `mxgateway.workers.killed` | `reason` | Forced terminations of worker processes. |
|
||||
| `mxgateway.workers.exited` | `reason` | Clean or fault-driven worker exits. |
|
||||
| `mxgateway.heartbeats.failed` | `session_id` | Worker heartbeat misses tracked per session. |
|
||||
| `mxgateway.heartbeats.failed` | *(none)* | Aggregate worker heartbeat misses. The exported counter carries **no** `session_id` tag — per-session attribution is unbounded cardinality (every session mints a new time series), so it is kept out of the exporter; use the dashboard snapshot or the log scope for per-session detail. |
|
||||
| `mxgateway.grpc.streams.disconnected` | `reason` | Detachments of the dashboard or client gRPC event stream. |
|
||||
| `mxgateway.retries.attempted` | `area` | Resilience retries executed by gateway components. |
|
||||
|
||||
|
||||
Reference in New Issue
Block a user