9138f3edf8
WithSiteCredentials always composed the per-site PSK over
ChannelCredentials.Insecure. Grpc.Net.Client refuses that on an https
address ("insecure channel credentials and can't use a HttpClient with a
'https' scheme"), so every https control-plane endpoint threw at channel
construction - in SitePairChannelProvider the throw was caught and the
site address logged as malformed, i.e. a TLS site was silently
undialable. The old comment claiming TLS needed no change here was wrong.
WithSiteCredentials now takes the endpoint: https composes over
ChannelCredentials.SecureSsl (PSK retained inside TLS, no insecure
opt-in); anything else, including callers that omit it, keeps the
previous composition unchanged. SitePairChannelProvider,
SiteStreamGrpcClient and CentralChannelProvider pass their endpoint. The
three AuditLog pull clients are deliberately left for after #42.
Tests: real Kestrel HTTP/2-over-TLS (ALPN) with the real
ControlPlaneAuthInterceptor - correct PSK accepted, wrong PSK still
PermissionDenied, untrusted server cert fails with an
AuthenticationException cause, the pre-fix composition throws on https,
http composition unchanged; plus each production builder accepts https
(mutation check: disabling the TLS branch turns those red).
Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
89 lines
3.5 KiB
C#
89 lines
3.5 KiB
C#
using Grpc.Net.Client;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using Microsoft.Extensions.Options;
|
|
using ZB.MOM.WW.ScadaBridge.Communication.Grpc;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.Communication.Tests.Grpc;
|
|
|
|
/// <summary>
|
|
/// Every production builder of a PSK-credentialed control-plane channel must accept an
|
|
/// <c>https://</c> endpoint (ScadaBridge#64). Before the endpoint was threaded into
|
|
/// <see cref="ControlPlaneCredentials.WithSiteCredentials"/>, each composed its credentials over
|
|
/// insecure channel credentials and Grpc.Net.Client threw at construction for any https address —
|
|
/// in <see cref="SitePairChannelProvider"/> that throw was caught and the site address logged as
|
|
/// malformed, so a TLS site was silently undialable.
|
|
/// </summary>
|
|
public class HttpsEndpointChannelTests
|
|
{
|
|
private const string Site = "site-a";
|
|
|
|
/// <summary>Verifies central's per-site pair provider builds a channel for an https site address.</summary>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
[Fact]
|
|
public async Task SitePairChannelProvider_builds_an_https_site_channel()
|
|
{
|
|
using var provider = new SitePairChannelProvider(
|
|
new FixedKeyProvider(),
|
|
Options.Create(new CommunicationOptions()),
|
|
NullLogger<SitePairChannelProvider>.Instance,
|
|
handlerFactory: null,
|
|
reachabilityProbe: (_, _) => Task.FromResult(false));
|
|
|
|
provider.UpdateSite(Site, "https://site-a-node-a:8083", nodeBEndpoint: null);
|
|
|
|
var target = await provider.ExecuteAsync(
|
|
Site, (channel, _) => Task.FromResult(channel.Target), CancellationToken.None);
|
|
Assert.Equal("site-a-node-a:8083", target);
|
|
}
|
|
|
|
/// <summary>Verifies a site's central channel provider builds https channels for both centrals.</summary>
|
|
[Fact]
|
|
public void CentralChannelProvider_builds_https_central_channels()
|
|
{
|
|
using var provider = new CentralChannelProvider(
|
|
new[] { "https://central-a:8083", "https://central-b:8083" },
|
|
new FixedKeyProvider(),
|
|
Site,
|
|
new CommunicationOptions(),
|
|
NullLogger.Instance);
|
|
}
|
|
|
|
/// <summary>Verifies the site stream client builds an https channel with credentials.</summary>
|
|
[Fact]
|
|
public void SiteStreamGrpcClient_builds_an_https_channel()
|
|
{
|
|
using var client = new SiteStreamGrpcClient(
|
|
"https://site-a-node-a:8083",
|
|
NullLogger<SiteStreamGrpcClient>.Instance,
|
|
new CommunicationOptions(),
|
|
new FixedKeyProvider(),
|
|
Site);
|
|
|
|
Assert.Equal("https://site-a-node-a:8083", client.Endpoint);
|
|
}
|
|
|
|
/// <summary>Verifies the scheme check behind the composition choice.</summary>
|
|
/// <param name="endpoint">The endpoint.</param>
|
|
/// <param name="expected">Whether it is TLS.</param>
|
|
[Theory]
|
|
[InlineData("https://host:8083", true)]
|
|
[InlineData("HTTPS://host:8083", true)]
|
|
[InlineData("http://host:8083", false)]
|
|
[InlineData(null, false)]
|
|
[InlineData("", false)]
|
|
[InlineData("not a url", false)]
|
|
public void IsTlsEndpoint_matches_only_absolute_https(string? endpoint, bool expected)
|
|
{
|
|
Assert.Equal(expected, ControlPlaneCredentials.IsTlsEndpoint(endpoint));
|
|
}
|
|
|
|
private sealed class FixedKeyProvider : ISitePskProvider
|
|
{
|
|
/// <inheritdoc />
|
|
public ValueTask<string> GetAsync(string siteId, CancellationToken ct) => new("k");
|
|
|
|
/// <inheritdoc />
|
|
public void Invalidate(string siteId) { }
|
|
}
|
|
}
|