feat(secrets): source LDAP bind password via ${secret:ldap/mxgateway/bind}; drop plaintext defaults (G-4)

This commit is contained in:
Joseph Doherty
2026-07-16 10:40:07 -04:00
parent a2538039c0
commit b79e119ada
7 changed files with 113 additions and 11 deletions
@@ -6,17 +6,27 @@ namespace ZB.MOM.WW.MxGateway.Tests.Configuration;
public sealed class GatewayOptionsValidatorTests
{
// A non-blank LDAP bind password for tests. The shipped design default is now blank
// (string.Empty) so the secret-sourced value fails closed if unresolved; enabled-LDAP test
// options must therefore supply an explicit password to pass the required-field check.
private const string TestBindPassword = "test-bind-password";
// Constructs the minimal valid GatewayOptions by relying on each sub-option's
// design-default values; those defaults are validated separately in GatewayOptionsTests.
private static GatewayOptions ValidOptions() => new();
// The one exception is the LDAP bind password (blank by design default), which we supply.
private static GatewayOptions ValidOptions() => new() { Ldap = ValidLdapOptions() };
// Enabled LDAP options with all class defaults plus the non-blank bind password.
private static LdapOptions ValidLdapOptions() => new() { ServiceAccountPassword = TestBindPassword };
// Returns enabled LDAP options that pass all checks except Port.
// The class defaults already satisfy the blank-field checks; we only
// override Enabled (must be true to exercise the port check) and Port.
// The class defaults already satisfy the remaining blank-field checks; we override Enabled
// (must be true to exercise the port check), Port, and the now-blank-by-default bind password.
private static LdapOptions LdapOptionsWithPort(int port) => new()
{
Enabled = true,
Port = port,
ServiceAccountPassword = TestBindPassword,
};
private static GatewayOptions CloneWithLdap(GatewayOptions source, LdapOptions ldap)
@@ -667,12 +677,38 @@ public sealed class GatewayOptionsValidatorTests
{
GatewayOptions options = CloneWithLdap(
ValidOptions(),
new LdapOptions { Enabled = true, Transport = LdapTransport.Ldaps, AllowInsecure = false });
new LdapOptions
{
Enabled = true,
Transport = LdapTransport.Ldaps,
AllowInsecure = false,
ServiceAccountPassword = TestBindPassword,
});
ValidateOptionsResult result = new GatewayOptionsValidator(isProduction: true).Validate(null, options);
Assert.True(result.Succeeded);
}
private static GatewayOptions WithSecurity(SecurityOptions security) => new() { Security = security };
/// <summary>
/// Verifies enabled LDAP with a blank <see cref="LdapOptions.ServiceAccountPassword"/> fails
/// validation. Locks in the fail-closed design default (blank) introduced when the bind password
/// moved to the encrypted secrets store (<c>${secret:ldap/mxgateway/bind}</c>): a blanked/unresolved
/// password must abort startup rather than silently binding with an empty credential.
/// </summary>
[Fact]
public void Validate_Fails_WhenLdapEnabledAndServiceAccountPasswordBlank()
{
GatewayOptions options = CloneWithLdap(
ValidOptions(),
new LdapOptions { Enabled = true, ServiceAccountPassword = string.Empty });
ValidateOptionsResult result = new GatewayOptionsValidator().Validate(null, options);
Assert.True(result.Failed);
Assert.Contains(
result.Failures!,
f => f.Contains("MxGateway:Ldap:ServiceAccountPassword is required when LDAP login is enabled."));
}
private static GatewayOptions WithSecurity(SecurityOptions security)
=> new() { Security = security, Ldap = ValidLdapOptions() };
/// <summary>Verifies the default security options pass validation.</summary>
[Fact]