fix(opcuaclient): never pass checkDomain for a None endpoint; document both SDK check sites (#816 review)
SDK 2.0.1 checks the server host name in TWO places: Session.OpenAsync (after the chain is trusted) and DefaultSessionFactory.CreateChannelAsync, against the certificate in the GetEndpoints reply and before any security-mode test. opc-plc sends a certificate on its None endpoint, so `connect -u opc.tcp://10.100.0.35:50000 --validate-host-name` was refused on an unauthenticated channel, where a host check proves nothing. All three call sites now pass checkDomain only when the selected endpoint's SecurityMode != None: OpcUaEndpointSelection.CheckDomainFor for the driver (OpenSessionOnEndpointAsync, so the failover sweep too) and the AdminUI browser; the same expression in Client.Shared ConnectToEndpointAsync (shared by failover). New live tests: driver None + flag on vs :50000 reads; browser None + flag on vs the NAT fixture browses; the secure + flag refusals still hold. Docs: name both check sites; a zone-qualified IPv6 host never matches (FindDomain compares the zone too), so link-local needs validateServerHostName=false; the match is loose (first label alone, subject DC= parts count, no wildcards), which caveats the CA recommendation.
This commit is contained in:
@@ -56,21 +56,28 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gitea #816: the host-name check reaches the session factory (the SDK's checkDomain) — off by default, the
|
||||
/// pre-#816 behaviour — and survives the per-endpoint settings copy the connect path makes.
|
||||
/// Gitea #816: the host-name check reaches the session factory (the SDK's checkDomain) on a SECURED endpoint — off
|
||||
/// by default, the pre-#816 behaviour — and survives the per-endpoint settings copy the connect path makes. On a
|
||||
/// None endpoint it is never passed: SDK 2.0.1 would check the GetEndpoints certificate at channel creation even
|
||||
/// there (review of #816, reproduced against opc-plc).
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public async Task ConnectAsync_threads_ValidateServerHostName_to_the_session(bool? validate)
|
||||
[InlineData(SecurityMode.Sign, null, false)]
|
||||
[InlineData(SecurityMode.Sign, true, true)]
|
||||
[InlineData(SecurityMode.SignAndEncrypt, true, true)]
|
||||
[InlineData(SecurityMode.Sign, false, false)]
|
||||
[InlineData(SecurityMode.None, true, false)]
|
||||
[InlineData(SecurityMode.None, null, false)]
|
||||
public async Task ConnectAsync_threads_ValidateServerHostName_to_secured_sessions_only(
|
||||
SecurityMode mode, bool? validate, bool expectedCheckDomain)
|
||||
{
|
||||
var settings = ValidSettings();
|
||||
settings.SecurityMode = mode;
|
||||
if (validate is { } v) settings.ValidateServerHostName = v;
|
||||
|
||||
await _service.ConnectAsync(settings, TestContext.Current.CancellationToken);
|
||||
|
||||
_sessionFactory.LastValidateServerHostName.ShouldBe(validate ?? false);
|
||||
_sessionFactory.LastValidateServerHostName.ShouldBe(expectedCheckDomain);
|
||||
_configFactory.LastSettings!.ValidateServerHostName.ShouldBe(validate ?? false);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user