feat(management): Admin-gated actor dispatch for cert-trust commands (arch-review C4)
Made CommunicationService.TrustServerCertAsync/RemoveServerCertAsync/ListServerCertsAsync virtual to support the test seam (mirrors existing virtual WriteTagAsync). Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
This commit is contained in:
@@ -432,7 +432,7 @@ public class CommunicationService
|
||||
/// <param name="command">The trust-server-cert command (connection name + DER + thumbprint).</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The cert-trust result (per-node aggregate success + first error).</returns>
|
||||
public Task<CertTrustResult> TrustServerCertAsync(
|
||||
public virtual Task<CertTrustResult> TrustServerCertAsync(
|
||||
string siteId,
|
||||
TrustServerCertCommand command,
|
||||
CancellationToken cancellationToken = default)
|
||||
@@ -453,7 +453,7 @@ public class CommunicationService
|
||||
/// <param name="command">The list-server-certs command.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The cert-trust result carrying the listed certificates on success.</returns>
|
||||
public Task<CertTrustResult> ListServerCertsAsync(
|
||||
public virtual Task<CertTrustResult> ListServerCertsAsync(
|
||||
string siteId,
|
||||
ListServerCertsCommand command,
|
||||
CancellationToken cancellationToken = default)
|
||||
@@ -474,7 +474,7 @@ public class CommunicationService
|
||||
/// <param name="command">The remove-server-cert command (thumbprint).</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The cert-trust result (per-node aggregate success + first error).</returns>
|
||||
public Task<CertTrustResult> RemoveServerCertAsync(
|
||||
public virtual Task<CertTrustResult> RemoveServerCertAsync(
|
||||
string siteId,
|
||||
RemoveServerCertCommand command,
|
||||
CancellationToken cancellationToken = default)
|
||||
|
||||
@@ -212,7 +212,12 @@ public class ManagementActor : ReceiveActor
|
||||
// gate must match — otherwise a Designer blocked in the UI could still
|
||||
// rotate a production credential via the CLI/Management API.
|
||||
or UpdateSmtpConfigCommand
|
||||
or UpdateSmsConfigCommand => AdminOnly,
|
||||
or UpdateSmsConfigCommand
|
||||
// Server-certificate trust (arch-review C4). These mutate the site's
|
||||
// trusted-peer PKI store — Admin, matching the Admin-gated UI cert
|
||||
// page. Site-side reconcile semantics are owned by the site
|
||||
// CertStoreActor (plan 03); this handler only relays.
|
||||
or TrustServerCertCommand or RemoveServerCertCommand or ListServerCertsCommand => AdminOnly,
|
||||
|
||||
// Area management — any-of [Designer, Deployer] (arch-review C6).
|
||||
// Exposed both in the Designer tooling and inside the Central UI
|
||||
@@ -443,6 +448,9 @@ public class ManagementActor : ReceiveActor
|
||||
BrowseNodeCommand cmd => await HandleBrowseNode(sp, cmd, user),
|
||||
SearchAddressSpaceCommand cmd => await HandleSearchAddressSpace(sp, cmd, user),
|
||||
VerifyEndpointCommand cmd => await HandleVerifyEndpoint(sp, cmd, user),
|
||||
TrustServerCertCommand cmd => await HandleTrustServerCert(sp, cmd, user),
|
||||
RemoveServerCertCommand cmd => await HandleRemoveServerCert(sp, cmd, user),
|
||||
ListServerCertsCommand cmd => await HandleListServerCerts(sp, cmd, user),
|
||||
QueryParkedMessagesCommand cmd => await HandleQueryParkedMessages(sp, cmd, user),
|
||||
RetryParkedMessageCommand cmd => await HandleRetryParkedMessage(sp, cmd, user),
|
||||
DiscardParkedMessageCommand cmd => await HandleDiscardParkedMessage(sp, cmd, user),
|
||||
@@ -2955,6 +2963,39 @@ public class ManagementActor : ReceiveActor
|
||||
return await commService.VerifyEndpointAsync(site, cmd);
|
||||
}
|
||||
|
||||
// Server-certificate trust (arch-review C4). Admin-gated at the role check
|
||||
// above. These mutate the site's trusted-peer PKI store — the handler only
|
||||
// relays; the site-side broadcast/reconcile across both nodes is owned by
|
||||
// the site CertStoreActor (plan 03). SiteIdentifier is REQUIRED so the actor
|
||||
// knows which site to route to.
|
||||
|
||||
private static async Task<object?> HandleTrustServerCert(IServiceProvider sp, TrustServerCertCommand cmd, AuthenticatedUser user)
|
||||
{
|
||||
var site = cmd.SiteIdentifier
|
||||
?? throw new ManagementCommandException("SiteIdentifier is required when trusting a server certificate via the management API.");
|
||||
await EnforceSiteScopeForIdentifier(sp, user, site);
|
||||
var commService = sp.GetRequiredService<CommunicationService>();
|
||||
return await commService.TrustServerCertAsync(site, cmd);
|
||||
}
|
||||
|
||||
private static async Task<object?> HandleRemoveServerCert(IServiceProvider sp, RemoveServerCertCommand cmd, AuthenticatedUser user)
|
||||
{
|
||||
var site = cmd.SiteIdentifier
|
||||
?? throw new ManagementCommandException("SiteIdentifier is required when removing a server certificate via the management API.");
|
||||
await EnforceSiteScopeForIdentifier(sp, user, site);
|
||||
var commService = sp.GetRequiredService<CommunicationService>();
|
||||
return await commService.RemoveServerCertAsync(site, cmd);
|
||||
}
|
||||
|
||||
private static async Task<object?> HandleListServerCerts(IServiceProvider sp, ListServerCertsCommand cmd, AuthenticatedUser user)
|
||||
{
|
||||
var site = cmd.SiteIdentifier
|
||||
?? throw new ManagementCommandException("SiteIdentifier is required when listing server certificates via the management API.");
|
||||
await EnforceSiteScopeForIdentifier(sp, user, site);
|
||||
var commService = sp.GetRequiredService<CommunicationService>();
|
||||
return await commService.ListServerCertsAsync(site, cmd);
|
||||
}
|
||||
|
||||
private static async Task<object?> HandleQueryParkedMessages(IServiceProvider sp, QueryParkedMessagesCommand cmd, AuthenticatedUser user)
|
||||
{
|
||||
await EnforceSiteScopeForIdentifier(sp, user, cmd.SiteIdentifier);
|
||||
|
||||
Reference in New Issue
Block a user