fix(security): validate the API-key pepper at start, finish JWT bearer, circuit-aware audit actor
- ApiKeyOptionsValidator (ValidateOnStart on any driver/admin node): a blank or placeholder Security:ApiKeys:Pepper fails host start instead of rejecting every key after a green boot (R03:2.3). - JWT is finished and opt-in by key (R03:2.4): with Security:Jwt:SigningKey set, a Bearer scheme (JwtBearerAuthenticationHandler over JwtTokenService.TryValidate / BuildValidationParameters, fail closed) accepts the tokens /auth/token mints; the default scheme is a cookie-or-bearer policy scheme and the fallback policy names it, so /api/* and every RequireAuthorization endpoint take a browser session OR a machine token. JwtOptions are validated at start (JwtOptionsValidator). Without a key, /auth/token is not mapped (404, not the former constructor 500) and no bearer scheme exists. A direct AddJwtBearer needs Microsoft.AspNetCore.Authentication.JwtBearer in Directory.Packages.props (not this lane's file) — the handler is a drop-in to swap. - AuthenticationStateAuditActorAccessor is the registered IAuditActorAccessor: the Blazor circuit's principal first, HttpContext as the fallback (R03:2.5). HttpAuditActorAccessor shares its resolver. - CookieAuthenticationStateProvider (unregistered, fire-and-forget ping loop) is deleted (R03:1.5/2.5). - /auth/login logs the LDAP exception at Warning before answering 503 (R03:2.7). - Admin operations reply with a fixed message + correlation id instead of ex.Message (R03:4.1). Refs #733 Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
This commit is contained in:
@@ -89,6 +89,18 @@ public sealed class AdminOperationsActor : ReceiveActor
|
||||
Receive<ShelveAlarmCommand>(HandleShelveAlarm);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The reply text for an unexpected server-side failure (R03:4.1): a fixed sentence plus the request's
|
||||
/// correlation id, never <c>ex.Message</c> — a SqlException or UriFormatException message carries
|
||||
/// connection-string fragments and host names, and the reply reaches any ConfigEditor browser session.
|
||||
/// The detail is in the central log under the same correlation id.
|
||||
/// </summary>
|
||||
/// <param name="operation">The admin operation that failed.</param>
|
||||
/// <param name="correlationId">The request's correlation token, as logged alongside the exception.</param>
|
||||
/// <returns>The operator-facing message.</returns>
|
||||
internal static string ServerFault(string operation, object correlationId) =>
|
||||
$"{operation} failed on the server; see the central node log for correlation {correlationId}.";
|
||||
|
||||
/// <summary>
|
||||
/// Hands a central→node command to the mesh transport router, or publishes it directly when
|
||||
/// no router is wired.
|
||||
@@ -136,8 +148,8 @@ public sealed class AdminOperationsActor : ReceiveActor
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Error(ex, "AdminOps: Acknowledge failed for alarm {AlarmId}", msg.AlarmId);
|
||||
replyTo.Tell(new AcknowledgeAlarmResult(false, ex.Message, msg.CorrelationId));
|
||||
_log.Error(ex, "AdminOps: Acknowledge failed for alarm {AlarmId} (correlation {CorrelationId})", msg.AlarmId, msg.CorrelationId);
|
||||
replyTo.Tell(new AcknowledgeAlarmResult(false, ServerFault("Acknowledge", msg.CorrelationId), msg.CorrelationId));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,8 +195,8 @@ public sealed class AdminOperationsActor : ReceiveActor
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Error(ex, "AdminOps: Shelve ({Kind}) failed for alarm {AlarmId}", msg.Kind, msg.AlarmId);
|
||||
replyTo.Tell(new ShelveAlarmResult(false, ex.Message, msg.CorrelationId));
|
||||
_log.Error(ex, "AdminOps: Shelve ({Kind}) failed for alarm {AlarmId} (correlation {CorrelationId})", msg.Kind, msg.AlarmId, msg.CorrelationId);
|
||||
replyTo.Tell(new ShelveAlarmResult(false, ServerFault("Shelve", msg.CorrelationId), msg.CorrelationId));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,12 +249,12 @@ public sealed class AdminOperationsActor : ReceiveActor
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Error(ex, "StartDeployment failed for {CreatedBy}", msg.CreatedBy);
|
||||
_log.Error(ex, "StartDeployment failed for {CreatedBy} (correlation {CorrelationId})", msg.CreatedBy, msg.CorrelationId);
|
||||
replyTo.Tell(new StartDeploymentResult(
|
||||
StartDeploymentOutcome.Rejected,
|
||||
DeploymentId: null,
|
||||
RevisionHash: null,
|
||||
Message: ex.Message,
|
||||
Message: ServerFault("StartDeployment", msg.CorrelationId),
|
||||
msg.CorrelationId));
|
||||
return;
|
||||
}
|
||||
@@ -269,12 +281,12 @@ public sealed class AdminOperationsActor : ReceiveActor
|
||||
private void HandleDeployComposeFailed(DeployComposeFailed msg)
|
||||
{
|
||||
_composeInFlight = null;
|
||||
_log.Error(msg.Error, "StartDeployment failed for {CreatedBy}", msg.Request.CreatedBy);
|
||||
_log.Error(msg.Error, "StartDeployment failed for {CreatedBy} (correlation {CorrelationId})", msg.Request.CreatedBy, msg.Request.CorrelationId);
|
||||
msg.ReplyTo.Tell(new StartDeploymentResult(
|
||||
StartDeploymentOutcome.Rejected,
|
||||
DeploymentId: null,
|
||||
RevisionHash: null,
|
||||
Message: msg.Error.Message,
|
||||
Message: ServerFault("StartDeployment", msg.Request.CorrelationId),
|
||||
msg.Request.CorrelationId));
|
||||
}
|
||||
|
||||
@@ -513,10 +525,10 @@ public sealed class AdminOperationsActor : ReceiveActor
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Error(ex, "Probe for {DriverType} threw", msg.DriverType);
|
||||
_log.Error(ex, "Probe for {DriverType} threw (correlation {CorrelationId})", msg.DriverType, msg.CorrelationId);
|
||||
return new TestDriverConnectResult(
|
||||
false,
|
||||
ex.Message,
|
||||
ServerFault("Test Connect", msg.CorrelationId),
|
||||
null,
|
||||
msg.CorrelationId);
|
||||
}
|
||||
@@ -548,8 +560,8 @@ public sealed class AdminOperationsActor : ReceiveActor
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Error(ex, "AdminOps: RestartDriver failed for {DriverInstanceId}", msg.DriverInstanceId);
|
||||
replyTo.Tell(new RestartDriverResult(false, ex.Message, msg.CorrelationId));
|
||||
_log.Error(ex, "AdminOps: RestartDriver failed for {DriverInstanceId} (correlation {CorrelationId})", msg.DriverInstanceId, msg.CorrelationId);
|
||||
replyTo.Tell(new RestartDriverResult(false, ServerFault("RestartDriver", msg.CorrelationId), msg.CorrelationId));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -578,8 +590,8 @@ public sealed class AdminOperationsActor : ReceiveActor
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Error(ex, "AdminOps: ReconnectDriver failed for {DriverInstanceId}", msg.DriverInstanceId);
|
||||
replyTo.Tell(new ReconnectDriverResult(false, ex.Message, msg.CorrelationId));
|
||||
_log.Error(ex, "AdminOps: ReconnectDriver failed for {DriverInstanceId} (correlation {CorrelationId})", msg.DriverInstanceId, msg.CorrelationId);
|
||||
replyTo.Tell(new ReconnectDriverResult(false, ServerFault("ReconnectDriver", msg.CorrelationId), msg.CorrelationId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user