feat(dcl): OPC UA verify-endpoint probe with untrusted-cert capture (T17)
This commit is contained in:
@@ -1,8 +1,12 @@
|
||||
using Akka.Actor;
|
||||
using Akka.Event;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Protocol;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Messages.DataConnection;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Serialization;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.DataConnections;
|
||||
using ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Adapters;
|
||||
using ZB.MOM.WW.ScadaBridge.HealthMonitoring;
|
||||
using ZB.MOM.WW.ScadaBridge.SiteEventLogging;
|
||||
|
||||
@@ -20,6 +24,11 @@ public class DataConnectionManagerActor : ReceiveActor
|
||||
private readonly DataConnectionOptions _options;
|
||||
private readonly ISiteHealthCollector _healthCollector;
|
||||
private readonly ISiteEventLogger? _siteEventLogger;
|
||||
// T17: deployment-wide OPC UA application identity / cert-store paths — the same
|
||||
// global options the DataConnectionFactory feeds to RealOpcUaClient when creating OPC
|
||||
// UA connections. Needed by the verify-endpoint probe (VerifyEndpointCommand), which
|
||||
// builds an ApplicationConfiguration directly rather than through a connection actor.
|
||||
private readonly OpcUaGlobalOptions _opcUaGlobalOptions;
|
||||
private readonly Dictionary<string, IActorRef> _connectionActors = new();
|
||||
|
||||
/// <summary>
|
||||
@@ -29,16 +38,23 @@ public class DataConnectionManagerActor : ReceiveActor
|
||||
/// <param name="options">Configuration options for data connections.</param>
|
||||
/// <param name="healthCollector">Collector for site health metrics reported by connection actors.</param>
|
||||
/// <param name="siteEventLogger">Optional logger for site event entries; null disables site event logging.</param>
|
||||
/// <param name="opcUaGlobalOptions">
|
||||
/// Deployment-wide OPC UA application identity / cert-store paths used by the
|
||||
/// verify-endpoint probe; null falls back to defaults (mirrors
|
||||
/// <see cref="DataConnectionFactory"/>'s default-options constructor).
|
||||
/// </param>
|
||||
public DataConnectionManagerActor(
|
||||
IDataConnectionFactory factory,
|
||||
DataConnectionOptions options,
|
||||
ISiteHealthCollector healthCollector,
|
||||
ISiteEventLogger? siteEventLogger = null)
|
||||
ISiteEventLogger? siteEventLogger = null,
|
||||
OpcUaGlobalOptions? opcUaGlobalOptions = null)
|
||||
{
|
||||
_factory = factory;
|
||||
_options = options;
|
||||
_healthCollector = healthCollector;
|
||||
_siteEventLogger = siteEventLogger;
|
||||
_opcUaGlobalOptions = opcUaGlobalOptions ?? new OpcUaGlobalOptions();
|
||||
|
||||
Receive<CreateConnectionCommand>(HandleCreateConnection);
|
||||
Receive<SubscribeTagsRequest>(HandleRoute);
|
||||
@@ -52,6 +68,7 @@ public class DataConnectionManagerActor : ReceiveActor
|
||||
Receive<BrowseNodeCommand>(HandleBrowse);
|
||||
Receive<SearchAddressSpaceCommand>(HandleSearch);
|
||||
Receive<ReadTagValuesCommand>(HandleReadTagValues);
|
||||
Receive<VerifyEndpointCommand>(HandleVerifyEndpoint);
|
||||
}
|
||||
|
||||
private void HandleCreateConnection(CreateConnectionCommand command)
|
||||
@@ -243,6 +260,46 @@ public class DataConnectionManagerActor : ReceiveActor
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// T17: Handles a <see cref="VerifyEndpointCommand"/> from the Central UI's "Verify"
|
||||
/// action — probes the endpoint config WITHOUT persisting it (connect → capture an
|
||||
/// untrusted cert → disconnect) and pipes a structured <see cref="VerifyEndpointResult"/>
|
||||
/// back to the sender. Verify does NOT require an existing connection (the config may be
|
||||
/// brand-new and unsaved), so — unlike the routed browse/read handlers — it does not look
|
||||
/// up a connection actor; it runs the probe directly. Only OPC UA is supported today.
|
||||
/// </summary>
|
||||
private void HandleVerifyEndpoint(VerifyEndpointCommand cmd)
|
||||
{
|
||||
if (!string.Equals(cmd.Protocol, "OpcUa", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Sender.Tell(new VerifyEndpointResult(
|
||||
false, VerifyFailureKind.ServerError,
|
||||
"Verify is only supported for OPC UA connections.", null));
|
||||
return;
|
||||
}
|
||||
|
||||
OpcUaEndpointConfig config;
|
||||
try
|
||||
{
|
||||
(config, _) = OpcUaEndpointConfigSerializer.Deserialize(cmd.ConfigJson);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Defensive: Deserialize is designed not to throw (it classifies Malformed), but
|
||||
// a verify must never crash the manager — surface the parse failure as ServerError.
|
||||
_log.Warning(ex, "Verify config for {0} could not be parsed", cmd.ConnectionName);
|
||||
Sender.Tell(new VerifyEndpointResult(
|
||||
false, VerifyFailureKind.ServerError,
|
||||
"The endpoint configuration could not be parsed.", null));
|
||||
return;
|
||||
}
|
||||
|
||||
var probeLogger = NullLogger.Instance;
|
||||
RealOpcUaClient
|
||||
.VerifyEndpointAsync(config, _opcUaGlobalOptions, probeLogger, TimeSpan.FromSeconds(6), CancellationToken.None)
|
||||
.PipeTo(Sender);
|
||||
}
|
||||
|
||||
private void HandleRemoveConnection(RemoveConnectionCommand command)
|
||||
{
|
||||
if (_connectionActors.TryGetValue(command.ConnectionName, out var actor))
|
||||
|
||||
Reference in New Issue
Block a user