From e912ef960c834b85f42cf2a88072742dbc9df2cc Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Mon, 1 Jun 2026 07:08:12 -0400 Subject: [PATCH] feat(gateway): detect HTTPS endpoints missing a certificate --- .../Security/Tls/KestrelTlsInspector.cs | 37 +++++++++++++++++++ .../Security/Tls/KestrelTlsInspectorTests.cs | 34 +++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 src/ZB.MOM.WW.MxGateway.Server/Security/Tls/KestrelTlsInspector.cs create mode 100644 src/ZB.MOM.WW.MxGateway.Tests/Security/Tls/KestrelTlsInspectorTests.cs diff --git a/src/ZB.MOM.WW.MxGateway.Server/Security/Tls/KestrelTlsInspector.cs b/src/ZB.MOM.WW.MxGateway.Server/Security/Tls/KestrelTlsInspector.cs new file mode 100644 index 0000000..290822c --- /dev/null +++ b/src/ZB.MOM.WW.MxGateway.Server/Security/Tls/KestrelTlsInspector.cs @@ -0,0 +1,37 @@ +using Microsoft.Extensions.Configuration; + +namespace ZB.MOM.WW.MxGateway.Server.Security.Tls; + +/// +/// Inspects the Kestrel configuration to decide whether the gateway must supply +/// a generated default certificate (an HTTPS endpoint exists with no certificate +/// of its own). +/// +public static class KestrelTlsInspector +{ + public static bool RequiresGeneratedCertificate(IConfiguration configuration) + { + IConfigurationSection endpoints = configuration.GetSection("Kestrel:Endpoints"); + foreach (IConfigurationSection endpoint in endpoints.GetChildren()) + { + string? url = endpoint["Url"]; + if (string.IsNullOrWhiteSpace(url) || + !url.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + IConfigurationSection certificate = endpoint.GetSection("Certificate"); + bool hasOwnCertificate = + !string.IsNullOrWhiteSpace(certificate["Path"]) || + !string.IsNullOrWhiteSpace(certificate["Subject"]); + + if (!hasOwnCertificate) + { + return true; + } + } + + return false; + } +} diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Security/Tls/KestrelTlsInspectorTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Security/Tls/KestrelTlsInspectorTests.cs new file mode 100644 index 0000000..2905d29 --- /dev/null +++ b/src/ZB.MOM.WW.MxGateway.Tests/Security/Tls/KestrelTlsInspectorTests.cs @@ -0,0 +1,34 @@ +using Microsoft.Extensions.Configuration; +using ZB.MOM.WW.MxGateway.Server.Security.Tls; +using Xunit; + +namespace ZB.MOM.WW.MxGateway.Tests.Security.Tls; + +public sealed class KestrelTlsInspectorTests +{ + private static IConfiguration Config(params (string Key, string Value)[] entries) + => new ConfigurationBuilder() + .AddInMemoryCollection(entries.ToDictionary(e => e.Key, e => (string?)e.Value)) + .Build(); + + [Fact] + public void RequiresGeneratedCertificate_True_WhenHttpsEndpointHasNoCertificate() + => Assert.True(KestrelTlsInspector.RequiresGeneratedCertificate( + Config(("Kestrel:Endpoints:Http:Url", "https://0.0.0.0:5120")))); + + [Fact] + public void RequiresGeneratedCertificate_False_WhenAllEndpointsPlaintext() + => Assert.False(KestrelTlsInspector.RequiresGeneratedCertificate( + Config(("Kestrel:Endpoints:Http:Url", "http://0.0.0.0:5120")))); + + [Fact] + public void RequiresGeneratedCertificate_False_WhenHttpsEndpointHasOwnCertificate() + => Assert.False(KestrelTlsInspector.RequiresGeneratedCertificate( + Config( + ("Kestrel:Endpoints:Http:Url", "https://0.0.0.0:5120"), + ("Kestrel:Endpoints:Http:Certificate:Path", @"C:\certs\real.pfx")))); + + [Fact] + public void RequiresGeneratedCertificate_False_WhenNoEndpointsConfigured() + => Assert.False(KestrelTlsInspector.RequiresGeneratedCertificate(Config())); +}