From 7992e43908375021c5f4bbec2023d30ee9a227df Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Thu, 25 Jun 2026 20:18:23 -0400 Subject: [PATCH] test(c2): make WCF spike transport-selectable (integrated|certificate) + opt-in verbose The first live run used the wrong port (32568 direct vs the 42568 WCF tunnel) and hardcoded RemoteTcpIntegrated; via the tunnel the error advanced from socket-RST to ProtocolException (binding/security mismatch). Add HISTORIAN_WCF_EVENT_TRANSPORT (certificate), _DNSID, _ALLOW_UNTRUSTED, and an opt-in _VERBOSE for live binding diagnosis. Default output stays sanitized; still Windows-only, never fails the suite. Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii --- .../WcfEventReadSpikeTests.cs | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/tests/AVEVA.Historian.Client.Tests/WcfEventReadSpikeTests.cs b/tests/AVEVA.Historian.Client.Tests/WcfEventReadSpikeTests.cs index 3db9017..6a06d31 100644 --- a/tests/AVEVA.Historian.Client.Tests/WcfEventReadSpikeTests.cs +++ b/tests/AVEVA.Historian.Client.Tests/WcfEventReadSpikeTests.cs @@ -48,17 +48,27 @@ public sealed class WcfEventReadSpikeTests string? user = Environment.GetEnvironmentVariable("HISTORIAN_WCF_EVENT_USER"); string? password = Environment.GetEnvironmentVariable("HISTORIAN_WCF_EVENT_PASSWORD"); string? spn = Environment.GetEnvironmentVariable("HISTORIAN_WCF_EVENT_SPN"); - bool integrated = string.IsNullOrEmpty(user); + string? dnsId = Environment.GetEnvironmentVariable("HISTORIAN_WCF_EVENT_DNSID"); + bool certificate = string.Equals( + Environment.GetEnvironmentVariable("HISTORIAN_WCF_EVENT_TRANSPORT"), "certificate", StringComparison.OrdinalIgnoreCase); + bool allowUntrusted = string.Equals( + Environment.GetEnvironmentVariable("HISTORIAN_WCF_EVENT_ALLOW_UNTRUSTED"), "1", StringComparison.Ordinal); + // Certificate transport carries no Windows transport credential (cert-validated TLS channel); + // integrated derives one from the logged-in identity unless an explicit DOMAIN\user is supplied. + // App-level ValidateClientCredential still uses UserName/Password (or the process identity). + bool integrated = !certificate && string.IsNullOrEmpty(user); HistorianClientOptions options = new() { Host = host, Port = port, - Transport = HistorianTransport.RemoteTcpIntegrated, + Transport = certificate ? HistorianTransport.RemoteTcpCertificate : HistorianTransport.RemoteTcpIntegrated, IntegratedSecurity = integrated, UserName = user ?? string.Empty, Password = password ?? string.Empty, TargetSpn = string.IsNullOrWhiteSpace(spn) ? "NT SERVICE\\aahClientAccessPoint" : spn, + ServerDnsIdentity = string.IsNullOrWhiteSpace(dnsId) ? null : dnsId, + AllowUntrustedServerCertificate = allowUntrusted, }; HistorianWcfEventOrchestrator orchestrator = new(options); @@ -79,10 +89,16 @@ public sealed class WcfEventReadSpikeTests } catch (Exception ex) { - outcome = $"threw {ex.GetType().Name}"; // message omitted — may carry host/credential text + // Default: type name only (sanitized). Opt into messages for live binding/protocol debugging + // via HISTORIAN_WCF_EVENT_VERBOSE=1 — binding/protocol errors may carry the endpoint host + // (already known to the operator) but never credentials; still off by default. + outcome = string.Equals(Environment.GetEnvironmentVariable("HISTORIAN_WCF_EVENT_VERBOSE"), "1", StringComparison.Ordinal) + ? $"threw {ex.GetType().FullName} :: {ex.Message} | inner={ex.InnerException?.GetType().FullName} :: {ex.InnerException?.Message}" + : $"threw {ex.GetType().Name}"; } // Sanitized diagnostic dump — counts, native return codes, buffer lengths, sha256 ONLY. + _output.WriteLine($"[C2 WCF spike] transport: {options.Transport} (integratedSec={integrated}, allowUntrusted={allowUntrusted})"); _output.WriteLine($"[C2 WCF spike] outcome: {outcome}"); _output.WriteLine($"[C2 WCF spike] events observed: {observed}"); _output.WriteLine($"[C2 WCF spike] hasFirstEvent: {hasFirstEvent}");