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:
@@ -11,6 +11,18 @@ namespace ZB.MOM.WW.MxGateway.Server.Dashboard;
|
||||
/// (b) authentication is fully disabled, or (c) the request is from loopback
|
||||
/// and <c>MxGateway:Dashboard:AllowAnonymousLocalhost</c> is on.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The environment bypasses in (b) and (c) grant <em>read-only</em> access only: they
|
||||
/// satisfy a requirement that includes <see cref="DashboardRoles.Viewer"/> (i.e.
|
||||
/// <see cref="DashboardAuthorizationRequirement.AnyDashboardRole"/>) but never the
|
||||
/// <see cref="DashboardAuthorizationRequirement.AdminOnly"/> requirement. Destructive/admin
|
||||
/// surfaces still require a real Admin role claim, preserving the documented
|
||||
/// "anonymous localhost is read-only" contract at the policy layer rather than by accident
|
||||
/// in downstream service re-checks. The loopback test trusts
|
||||
/// <c>Connection.RemoteIpAddress</c>; if forwarded-headers middleware is ever added upstream,
|
||||
/// <see cref="IsLoopbackRequest"/> must be revisited so a spoofed <c>X-Forwarded-For</c>
|
||||
/// cannot masquerade as loopback.
|
||||
/// </remarks>
|
||||
public sealed class DashboardAuthorizationHandler(
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
IOptions<GatewayOptions> options) : AuthorizationHandler<DashboardAuthorizationRequirement>
|
||||
@@ -22,14 +34,18 @@ public sealed class DashboardAuthorizationHandler(
|
||||
{
|
||||
GatewayOptions gatewayOptions = options.Value;
|
||||
|
||||
if (gatewayOptions.Authentication.Mode == AuthenticationMode.Disabled)
|
||||
// Environment bypasses are read-only: they satisfy the Viewer requirement
|
||||
// (AnyDashboardRole) but never AdminOnly, which lacks Viewer in RequiredRoles.
|
||||
bool grantsReadOnly = requirement.RequiredRoles.Contains(DashboardRoles.Viewer);
|
||||
|
||||
if (grantsReadOnly && gatewayOptions.Authentication.Mode == AuthenticationMode.Disabled)
|
||||
{
|
||||
context.Succeed(requirement);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
if (gatewayOptions.Dashboard.AllowAnonymousLocalhost && IsLoopbackRequest())
|
||||
if (grantsReadOnly && gatewayOptions.Dashboard.AllowAnonymousLocalhost && IsLoopbackRequest())
|
||||
{
|
||||
context.Succeed(requirement);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user