feat(adminui): audit cert Trust/Untrust/Delete to ConfigAuditLog
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using ZB.MOM.WW.Audit;
|
||||
using ZB.MOM.WW.OtOpcUa.AdminUI.Audit;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Certificates;
|
||||
|
||||
@@ -25,31 +27,61 @@ public sealed class CertificateStoreManager
|
||||
new(StringComparer.OrdinalIgnoreCase) { "trusted", "rejected" };
|
||||
|
||||
private readonly string _pkiRoot;
|
||||
private readonly IAuditWriter _audit;
|
||||
|
||||
/// <summary>Production ctor — reads <c>OpcUa:PkiStoreRoot</c> (default <c>pki</c>).</summary>
|
||||
/// <param name="config">App configuration.</param>
|
||||
public CertificateStoreManager(IConfiguration config)
|
||||
=> _pkiRoot = config.GetValue<string?>("OpcUa:PkiStoreRoot") ?? "pki";
|
||||
/// <param name="audit">The audit writer that persists Trust/Untrust/Delete actions to <c>ConfigAuditLog</c>.</param>
|
||||
public CertificateStoreManager(IConfiguration config, IAuditWriter audit)
|
||||
{
|
||||
_pkiRoot = config.GetValue<string?>("OpcUa:PkiStoreRoot") ?? "pki";
|
||||
_audit = audit;
|
||||
}
|
||||
|
||||
/// <summary>Test ctor — explicit PKI root.</summary>
|
||||
/// <param name="pkiRoot">The PKI store root directory.</param>
|
||||
internal CertificateStoreManager(string pkiRoot) => _pkiRoot = pkiRoot;
|
||||
/// <param name="audit">Optional audit writer; defaults to a no-op writer when omitted.</param>
|
||||
internal CertificateStoreManager(string pkiRoot, IAuditWriter? audit = null)
|
||||
{
|
||||
_pkiRoot = pkiRoot;
|
||||
_audit = audit ?? NoOpAuditWriter.Instance;
|
||||
}
|
||||
|
||||
/// <summary>Moves a rejected peer cert into the trusted store.</summary>
|
||||
/// <param name="thumbprint">The cert thumbprint.</param>
|
||||
/// <param name="actor">The identity performing the action; recorded in the audit log.</param>
|
||||
/// <returns>The action result.</returns>
|
||||
public CertActionResult Trust(string thumbprint) => Move("rejected", "trusted", thumbprint);
|
||||
public CertActionResult Trust(string thumbprint, string actor)
|
||||
{
|
||||
var r = Move("rejected", "trusted", thumbprint);
|
||||
Audit("Trust", "rejected→trusted", thumbprint, actor, r);
|
||||
return r;
|
||||
}
|
||||
|
||||
/// <summary>Moves a trusted peer cert back into the rejected store.</summary>
|
||||
/// <param name="thumbprint">The cert thumbprint.</param>
|
||||
/// <param name="actor">The identity performing the action; recorded in the audit log.</param>
|
||||
/// <returns>The action result.</returns>
|
||||
public CertActionResult Untrust(string thumbprint) => Move("trusted", "rejected", thumbprint);
|
||||
public CertActionResult Untrust(string thumbprint, string actor)
|
||||
{
|
||||
var r = Move("trusted", "rejected", thumbprint);
|
||||
Audit("Untrust", "trusted→rejected", thumbprint, actor, r);
|
||||
return r;
|
||||
}
|
||||
|
||||
/// <summary>Deletes a cert from the named mutable store (<c>trusted</c> or <c>rejected</c>).</summary>
|
||||
/// <param name="store">The store name.</param>
|
||||
/// <param name="thumbprint">The cert thumbprint.</param>
|
||||
/// <param name="actor">The identity performing the action; recorded in the audit log.</param>
|
||||
/// <returns>The action result.</returns>
|
||||
public CertActionResult Delete(string store, string thumbprint)
|
||||
public CertActionResult Delete(string store, string thumbprint, string actor)
|
||||
{
|
||||
var r = DeleteCore(store, thumbprint);
|
||||
Audit("Delete", store, thumbprint, actor, r);
|
||||
return r;
|
||||
}
|
||||
|
||||
private CertActionResult DeleteCore(string store, string thumbprint)
|
||||
{
|
||||
if (!MutableStores.Contains(store)) return CertActionResult.Fail($"unknown store '{store}'");
|
||||
if (!IsValidThumbprint(thumbprint)) return CertActionResult.Fail("invalid thumbprint");
|
||||
@@ -66,6 +98,12 @@ public sealed class CertificateStoreManager
|
||||
}
|
||||
}
|
||||
|
||||
// Fire-and-forget: the writer enqueues synchronously and returns a completed task, so the
|
||||
// store mutation stays synchronous. Every return path (success + every failure) is audited
|
||||
// because Trust/Untrust/Delete each route their single result through here.
|
||||
private void Audit(string action, string store, string thumbprint, string actor, CertActionResult r)
|
||||
=> _audit.WriteAsync(CertAuditEvents.Build(action, store, thumbprint, actor, r.Success, r.Error));
|
||||
|
||||
private CertActionResult Move(string fromSub, string toSub, string thumbprint)
|
||||
{
|
||||
if (!IsValidThumbprint(thumbprint)) return CertActionResult.Fail("invalid thumbprint");
|
||||
|
||||
@@ -191,14 +191,15 @@ else
|
||||
return;
|
||||
}
|
||||
|
||||
var actor = authState.User.Identity?.Name ?? "system";
|
||||
var result = p.Verb switch
|
||||
{
|
||||
"trust" => CertManager.Trust(p.Thumbprint),
|
||||
"untrust" => CertManager.Untrust(p.Thumbprint),
|
||||
"trust" => CertManager.Trust(p.Thumbprint, actor),
|
||||
"untrust" => CertManager.Untrust(p.Thumbprint, actor),
|
||||
"delete" => p.Kind switch
|
||||
{
|
||||
StoreKind.Trusted => CertManager.Delete("trusted", p.Thumbprint),
|
||||
StoreKind.Rejected => CertManager.Delete("rejected", p.Thumbprint),
|
||||
StoreKind.Trusted => CertManager.Delete("trusted", p.Thumbprint, actor),
|
||||
StoreKind.Rejected => CertManager.Delete("rejected", p.Thumbprint, actor),
|
||||
_ => CertActionResult.Fail($"cannot delete from {p.Kind}"),
|
||||
},
|
||||
_ => CertActionResult.Fail("unknown action"),
|
||||
|
||||
Reference in New Issue
Block a user