fix: preserve SDK 2 driver value and credential lifetimes
This commit is contained in:
+28
-32
@@ -69,9 +69,10 @@ public sealed class OpcUaClientDriverBrowser : IDriverBrowser
|
||||
// R06:O-5 — Certificate auth is supported: the identity is built (and any secret: credential resolved) by the
|
||||
// same factory the runtime driver uses, BEFORE any network I/O, so a bad certificate path / password fails fast
|
||||
// with its own message.
|
||||
var (identity, ownedCertificate) = await BuildBrowseCredentialAsync(opts, _secretResolver, cancellationToken)
|
||||
var identity = await BuildBrowseUserIdentityAsync(opts, _secretResolver, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
ApplicationInstance? ownedApplication = null;
|
||||
try
|
||||
{
|
||||
if (opts.AutoAcceptCertificates)
|
||||
@@ -81,7 +82,9 @@ public sealed class OpcUaClientDriverBrowser : IDriverBrowser
|
||||
"driver's PKI store instead.",
|
||||
endpoint);
|
||||
|
||||
var appConfig = await BuildBrowseAppConfigurationAsync(cancellationToken).ConfigureAwait(false);
|
||||
var app = await BuildBrowseAppConfigurationAsync(cancellationToken).ConfigureAwait(false);
|
||||
ownedApplication = app;
|
||||
var appConfig = app.ApplicationConfiguration!;
|
||||
|
||||
var perEndpointBudget = TimeSpan.FromSeconds(
|
||||
Math.Clamp(opts.PerEndpointConnectTimeout.TotalSeconds, 5, 30));
|
||||
@@ -106,6 +109,7 @@ public sealed class OpcUaClientDriverBrowser : IDriverBrowser
|
||||
|
||||
try
|
||||
{
|
||||
connectCts.Token.ThrowIfCancellationRequested();
|
||||
var nsMap = NamespaceMap.FromSession(session);
|
||||
var rootNodeId = string.IsNullOrEmpty(opts.BrowseRoot)
|
||||
? ObjectIds.ObjectsFolder
|
||||
@@ -115,7 +119,9 @@ public sealed class OpcUaClientDriverBrowser : IDriverBrowser
|
||||
"AdminUI OPC UA browse session opened against {Endpoint} (policy {Policy}, mode {Mode})",
|
||||
endpoint, opts.SecurityPolicy, opts.SecurityMode);
|
||||
|
||||
return new OpcUaClientBrowseSession(session, nsMap, rootNodeId, ownedCertificate);
|
||||
var browse = new OpcUaClientBrowseSession(session, nsMap, rootNodeId, ownedApplication);
|
||||
ownedApplication = null;
|
||||
return browse;
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -126,9 +132,8 @@ public sealed class OpcUaClientDriverBrowser : IDriverBrowser
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Review m9: the certificate (and its private-key handle) is owned by the browse session once handed
|
||||
// over; any failure before that releases it here instead of leaving it to the GC.
|
||||
ownedCertificate?.Dispose();
|
||||
// The SDK application owns its certificate manager until the browse session takes it.
|
||||
if (ownedApplication is not null) await ownedApplication.DisposeAsync().ConfigureAwait(false);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@@ -139,7 +144,7 @@ public sealed class OpcUaClientDriverBrowser : IDriverBrowser
|
||||
/// trust decisions don't leak into the deployed driver's cert store.
|
||||
/// </summary>
|
||||
/// <param name="ct">Cancellation token for the configuration build.</param>
|
||||
private static async Task<ApplicationConfiguration> BuildBrowseAppConfigurationAsync(CancellationToken ct)
|
||||
private static async Task<ApplicationInstance> BuildBrowseAppConfigurationAsync(CancellationToken ct)
|
||||
{
|
||||
// The ITelemetryContext overload is the non-obsolete ctor; an empty builder keeps SDK
|
||||
// internals off any logging provider.
|
||||
@@ -153,7 +158,7 @@ public sealed class OpcUaClientDriverBrowser : IDriverBrowser
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
"OtOpcUa", "adminui-browse-pki");
|
||||
|
||||
var config = new ApplicationConfiguration
|
||||
var config = new ApplicationConfiguration(BrowseTelemetry.Context)
|
||||
{
|
||||
ApplicationName = "OtOpcUa AdminUI Browse",
|
||||
ApplicationType = ApplicationType.Client,
|
||||
@@ -187,11 +192,19 @@ public sealed class OpcUaClientDriverBrowser : IDriverBrowser
|
||||
ClientConfiguration = new ClientConfiguration { DefaultSessionTimeout = 60_000 },
|
||||
DisableHiResClock = true,
|
||||
};
|
||||
await config.ValidateAsync(ApplicationType.Client, ct).ConfigureAwait(false);
|
||||
app.ApplicationConfiguration = config;
|
||||
await app.CheckApplicationInstanceCertificatesAsync(silent: true, lifeTimeInMonths: null, ct)
|
||||
.ConfigureAwait(false);
|
||||
return config;
|
||||
try
|
||||
{
|
||||
await config.ValidateAsync(ApplicationType.Client, ct).ConfigureAwait(false);
|
||||
await app.CheckApplicationInstanceCertificatesAsync(silent: true, lifeTimeInMonths: null, ct)
|
||||
.ConfigureAwait(false);
|
||||
return app;
|
||||
}
|
||||
catch
|
||||
{
|
||||
await app.DisposeAsync().ConfigureAwait(false);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -203,28 +216,11 @@ public sealed class OpcUaClientDriverBrowser : IDriverBrowser
|
||||
/// <param name="ct">Cancellation token for the secret resolution.</param>
|
||||
/// <returns>The identity the browse session activates with.</returns>
|
||||
internal static async Task<UserIdentity> BuildBrowseUserIdentityAsync(
|
||||
OpcUaClientDriverOptions opts, ISecretResolver? secretResolver, CancellationToken ct) =>
|
||||
(await BuildBrowseCredentialAsync(opts, secretResolver, ct).ConfigureAwait(false)).Identity;
|
||||
|
||||
/// <summary>
|
||||
/// As <see cref="BuildBrowseUserIdentityAsync"/>, also handing back the user certificate a Certificate-auth
|
||||
/// identity holds, so the browse session that uses it can dispose it (review m9: the browse opens one per picker
|
||||
/// session, and an undisposed <c>X509Certificate2</c> pins a private-key handle until GC).
|
||||
/// </summary>
|
||||
/// <param name="opts">Driver options carrying the form's auth fields.</param>
|
||||
/// <param name="secretResolver">The secret store, or null when none is available.</param>
|
||||
/// <param name="ct">Cancellation token for the secret resolution.</param>
|
||||
/// <returns>The identity, and the certificate it owns (null for Anonymous / Username).</returns>
|
||||
internal static async Task<(UserIdentity Identity, System.Security.Cryptography.X509Certificates.X509Certificate2? Certificate)>
|
||||
BuildBrowseCredentialAsync(OpcUaClientDriverOptions opts, ISecretResolver? secretResolver, CancellationToken ct)
|
||||
OpcUaClientDriverOptions opts, ISecretResolver? secretResolver, CancellationToken ct)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
var resolved = await OpcUaUserIdentityFactory.ResolveSecretRefsAsync(opts, secretResolver, ct).ConfigureAwait(false);
|
||||
if (resolved.AuthType != OpcUaAuthType.Certificate)
|
||||
{
|
||||
return (OpcUaUserIdentityFactory.Build(resolved), null);
|
||||
}
|
||||
|
||||
return (OpcUaUserIdentityFactory.FromCertificate(resolved), null);
|
||||
return OpcUaUserIdentityFactory.Build(resolved);
|
||||
}
|
||||
|
||||
/// <summary>Select the endpoint matching the requested SecurityPolicy + SecurityMode pair.</summary>
|
||||
|
||||
Reference in New Issue
Block a user