fix(dcl): verify-endpoint probe logs through a real logger so first-time OPC UA app-cert creation is visible (#121)
The site's DataConnectionManagerActor handed VerifyEndpointAsync a NullLogger. A secured probe is usually what first creates a node's application certificate, so the creation Warning carrying the thumbprint every server must trust was silently discarded (found on the rig live gate). The actor takes an optional probe logger; the Host supplies one. Test pins creation logged once (Warning, thumbprint), not on reuse.
This commit is contained in:
+37
@@ -1,4 +1,5 @@
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.DataConnections;
|
||||
@@ -179,6 +180,42 @@ public sealed class RealOpcUaSecureConnectTests : IDisposable
|
||||
Assert.Contains("80130000", result.Error); // BadSecurityChecksFailed, not BadConfigurationError
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The probe is usually what first creates a node's identity, so creation must reach the
|
||||
/// supplied logger as a Warning carrying the thumbprint servers must trust — once, not on reuse.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task Verify_FirstSecuredProbe_LogsCertificateCreationOnce()
|
||||
{
|
||||
await using var server = await SecureOpcUaTestServer.StartAsync();
|
||||
var log = new CapturingLogger();
|
||||
|
||||
for (var i = 0; i < 2; i++)
|
||||
await RealOpcUaClient.VerifyEndpointAsync(
|
||||
ProbeConfig(server.Url, OpcUaSecurityMode.SignAndEncrypt), ClientOptions(), log,
|
||||
TimeSpan.FromSeconds(30), CancellationToken.None);
|
||||
|
||||
var der = Assert.Single(Directory.GetFiles(OwnCertsDir, "*.der"));
|
||||
using var cert = X509CertificateLoader.LoadCertificate(await File.ReadAllBytesAsync(der));
|
||||
var created = Assert.Single(log.Entries, e => e.Level == LogLevel.Warning && e.Message.Contains("CREATED"));
|
||||
Assert.Contains(cert.Thumbprint, created.Message);
|
||||
}
|
||||
|
||||
private sealed class CapturingLogger : ILogger
|
||||
{
|
||||
public List<(LogLevel Level, string Message)> Entries { get; } = new();
|
||||
|
||||
public IDisposable? BeginScope<TState>(TState state) where TState : notnull => null;
|
||||
|
||||
public bool IsEnabled(LogLevel logLevel) => true;
|
||||
|
||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception,
|
||||
Func<TState, Exception?, string> formatter)
|
||||
{
|
||||
lock (Entries) Entries.Add((logLevel, formatter(state, exception)));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>None is unchanged: it connects without reading or creating the own store.</summary>
|
||||
[Fact]
|
||||
public async Task Connect_None_DoesNotTouchTheOwnStore()
|
||||
|
||||
Reference in New Issue
Block a user