feat(wcf): add ConnectViaAddress (WCF Via) for tunneled historian access + wire into C2 spike
When the historian is reached through a port-forward whose local port differs from the server's real service port, WCF's server-side AddressFilter rejects the message (To = tunnel port != server port). ConnectViaAddress lets the channel connect to the tunnel while addressing the SOAP To the real Host/Port endpoint. Applied in HistorianWcfClientCredentialsHelper.Configure (the critical event factories already call it). The C2 spike reads HISTORIAN_WCF_EVENT_VIA. Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
This commit is contained in:
@@ -53,6 +53,17 @@ public sealed class HistorianClientOptions
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string? ServerDnsIdentity { get; init; }
|
public string? ServerDnsIdentity { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Optional WCF "Via" address (e.g. <c>net.tcp://host:42568</c>). When set, the SDK's WCF
|
||||||
|
/// channel factories <b>connect</b> to this address while still addressing the SOAP message
|
||||||
|
/// <c>To</c> the logical endpoint built from <see cref="Host"/>/<see cref="Port"/>. Use this when
|
||||||
|
/// the Historian is reached through a port-forwarding tunnel or proxy whose local port differs
|
||||||
|
/// from the server's real service port: point <see cref="Host"/>/<see cref="Port"/> at the
|
||||||
|
/// server's real endpoint (so the server's WCF AddressFilter matches) and set this to the tunnel
|
||||||
|
/// endpoint. Has no effect on the gRPC transport. Default null (connect == address).
|
||||||
|
/// </summary>
|
||||||
|
public string? ConnectViaAddress { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// For <see cref="HistorianTransport.RemoteGrpc"/>: when true the channel uses TLS
|
/// For <see cref="HistorianTransport.RemoteGrpc"/>: when true the channel uses TLS
|
||||||
/// (<c>https://</c>); when false it uses plaintext (<c>http://</c>). Matches the stock
|
/// (<c>https://</c>); when false it uses plaintext (<c>http://</c>). Matches the stock
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using System.IdentityModel.Selectors;
|
|||||||
using System.IdentityModel.Tokens;
|
using System.IdentityModel.Tokens;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
using System.Security.Cryptography.X509Certificates;
|
||||||
using System.ServiceModel;
|
using System.ServiceModel;
|
||||||
|
using System.ServiceModel.Description;
|
||||||
using System.ServiceModel.Security;
|
using System.ServiceModel.Security;
|
||||||
|
|
||||||
namespace AVEVA.Historian.Client.Wcf;
|
namespace AVEVA.Historian.Client.Wcf;
|
||||||
@@ -28,6 +29,14 @@ internal static class HistorianWcfClientCredentialsHelper
|
|||||||
RevocationMode = X509RevocationMode.NoCheck,
|
RevocationMode = X509RevocationMode.NoCheck,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tunnel/proxy support: connect to the Via address while still addressing the message To the
|
||||||
|
// logical endpoint (Host/Port). Lets a port-forward whose local port differs from the server's
|
||||||
|
// real service port satisfy the server-side WCF AddressFilter (which checks the To header).
|
||||||
|
if (!string.IsNullOrWhiteSpace(options.ConnectViaAddress))
|
||||||
|
{
|
||||||
|
factory.Endpoint.EndpointBehaviors.Add(new ClientViaBehavior(new Uri(options.ConnectViaAddress)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class AcceptAnyCertificateValidator : X509CertificateValidator
|
private sealed class AcceptAnyCertificateValidator : X509CertificateValidator
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ public sealed class WcfEventReadSpikeTests
|
|||||||
string? password = Environment.GetEnvironmentVariable("HISTORIAN_WCF_EVENT_PASSWORD");
|
string? password = Environment.GetEnvironmentVariable("HISTORIAN_WCF_EVENT_PASSWORD");
|
||||||
string? spn = Environment.GetEnvironmentVariable("HISTORIAN_WCF_EVENT_SPN");
|
string? spn = Environment.GetEnvironmentVariable("HISTORIAN_WCF_EVENT_SPN");
|
||||||
string? dnsId = Environment.GetEnvironmentVariable("HISTORIAN_WCF_EVENT_DNSID");
|
string? dnsId = Environment.GetEnvironmentVariable("HISTORIAN_WCF_EVENT_DNSID");
|
||||||
|
string? via = Environment.GetEnvironmentVariable("HISTORIAN_WCF_EVENT_VIA");
|
||||||
bool certificate = string.Equals(
|
bool certificate = string.Equals(
|
||||||
Environment.GetEnvironmentVariable("HISTORIAN_WCF_EVENT_TRANSPORT"), "certificate", StringComparison.OrdinalIgnoreCase);
|
Environment.GetEnvironmentVariable("HISTORIAN_WCF_EVENT_TRANSPORT"), "certificate", StringComparison.OrdinalIgnoreCase);
|
||||||
bool allowUntrusted = string.Equals(
|
bool allowUntrusted = string.Equals(
|
||||||
@@ -69,6 +70,7 @@ public sealed class WcfEventReadSpikeTests
|
|||||||
TargetSpn = string.IsNullOrWhiteSpace(spn) ? "NT SERVICE\\aahClientAccessPoint" : spn,
|
TargetSpn = string.IsNullOrWhiteSpace(spn) ? "NT SERVICE\\aahClientAccessPoint" : spn,
|
||||||
ServerDnsIdentity = string.IsNullOrWhiteSpace(dnsId) ? null : dnsId,
|
ServerDnsIdentity = string.IsNullOrWhiteSpace(dnsId) ? null : dnsId,
|
||||||
AllowUntrustedServerCertificate = allowUntrusted,
|
AllowUntrustedServerCertificate = allowUntrusted,
|
||||||
|
ConnectViaAddress = string.IsNullOrWhiteSpace(via) ? null : via,
|
||||||
};
|
};
|
||||||
|
|
||||||
HistorianWcfEventOrchestrator orchestrator = new(options);
|
HistorianWcfEventOrchestrator orchestrator = new(options);
|
||||||
|
|||||||
Reference in New Issue
Block a user