feat(siteruntime): per-node CertStore actor + trust broadcast to both site nodes (T17)

This commit is contained in:
Joseph Doherty
2026-06-18 03:13:48 -04:00
parent 303385fd98
commit c8d9303031
6 changed files with 526 additions and 0 deletions
@@ -0,0 +1,92 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
// ─────────────────────────────────────────────────────────────────────────────
// T17 / D6 — OPC UA server-certificate trust management.
//
// Cert trust is SITE-LOCAL: there is no central persistence of trusted server
// certificates. The trusted-peer PKI store lives on each site node's file
// system, so a trust/remove decision must reach BOTH site nodes (node-a and
// node-b) or the two PKI stores diverge across failover (Decision D6).
//
// The public commands below are handled by the site Deployment Manager
// singleton (active node only). For trust/remove it broadcasts the
// corresponding per-node internal message to the per-node CertStoreActor on
// EVERY site cluster node; for list it answers from the local node.
// ─────────────────────────────────────────────────────────────────────────────
/// <summary>
/// Trust an OPC UA server certificate at every site node. Carries the raw
/// DER bytes (base64-encoded) so the certificate can be written into each
/// node's trusted-peer PKI store. The thumbprint is the store filename key.
/// </summary>
/// <param name="ConnectionName">The data-connection the certificate was captured from (diagnostics / correlation only).</param>
/// <param name="DerBase64">The server certificate's DER encoding, base64-encoded.</param>
/// <param name="Thumbprint">The certificate thumbprint — used as the store filename key.</param>
public record TrustServerCertCommand(string ConnectionName, string DerBase64, string Thumbprint);
/// <summary>
/// Remove a previously-trusted OPC UA server certificate from every site
/// node's trusted-peer PKI store, identified by thumbprint.
/// </summary>
/// <param name="Thumbprint">The thumbprint of the certificate to remove.</param>
public record RemoveServerCertCommand(string Thumbprint);
/// <summary>
/// List the certificates currently present in this site's trusted-peer and
/// rejected PKI stores. Answered from the local node (the Deployment Manager
/// singleton's own node).
/// </summary>
public record ListServerCertsCommand();
/// <summary>
/// Read-only projection of a certificate found in a site PKI store.
/// </summary>
/// <param name="Thumbprint">The certificate thumbprint.</param>
/// <param name="Subject">The certificate subject distinguished name.</param>
/// <param name="Issuer">The certificate issuer distinguished name.</param>
/// <param name="NotBeforeUtc">Validity start (UTC).</param>
/// <param name="NotAfterUtc">Validity end (UTC).</param>
/// <param name="Rejected">True if the certificate is in the rejected store; false if trusted.</param>
public record TrustedCertInfo(
string Thumbprint,
string Subject,
string Issuer,
DateTime NotBeforeUtc,
DateTime NotAfterUtc,
bool Rejected);
/// <summary>
/// Aggregate result of a cert-trust command. For trust/remove, <paramref name="Success"/>
/// reflects whether every reachable site node acked; <paramref name="Error"/> carries the
/// first node error (or a partial-failure note when a node did not ack in time).
/// For list, <paramref name="Certs"/> carries the local node's store contents.
/// </summary>
/// <param name="Success">True when the operation succeeded on all targeted nodes.</param>
/// <param name="Error">First error encountered, or null on success.</param>
/// <param name="Certs">Listed certificates (list command only), otherwise null.</param>
public record CertTrustResult(bool Success, string? Error, IReadOnlyList<TrustedCertInfo>? Certs);
// ── Per-node internal messages (CertStoreActor wire protocol) ──
// Sent by the Deployment Manager singleton to each site node's CertStoreActor,
// or used directly in tests. Not part of the public management surface.
/// <summary>Per-node: decode <paramref name="DerBase64"/> and write it into the local trusted-peer store as <c>&lt;Thumbprint&gt;.der</c>.</summary>
/// <param name="DerBase64">The certificate's DER encoding, base64-encoded.</param>
/// <param name="Thumbprint">The thumbprint used as the store filename key.</param>
public record WriteCertToLocalStore(string DerBase64, string Thumbprint);
/// <summary>Per-node: delete the trusted-peer store file matching <paramref name="Thumbprint"/>.</summary>
/// <param name="Thumbprint">The thumbprint of the certificate to remove.</param>
public record RemoveCertFromLocalStore(string Thumbprint);
/// <summary>Per-node: enumerate the local trusted-peer and rejected stores.</summary>
public record ListLocalCerts();
/// <summary>
/// Per-node ack for a <see cref="WriteCertToLocalStore"/>, <see cref="RemoveCertFromLocalStore"/>
/// or <see cref="ListLocalCerts"/> operation.
/// </summary>
/// <param name="Success">True if the local store operation succeeded.</param>
/// <param name="Error">The error message on failure, otherwise null.</param>
/// <param name="Certs">Listed certificates (list only), otherwise null.</param>
public record LocalCertOpAck(bool Success, string? Error, IReadOnlyList<TrustedCertInfo>? Certs);