diff --git a/docs/GatewayConfiguration.md b/docs/GatewayConfiguration.md index 1dc80a9..23bf2a4 100644 --- a/docs/GatewayConfiguration.md +++ b/docs/GatewayConfiguration.md @@ -254,6 +254,7 @@ dev/test GLAuth posture (`glauth.md`), not a production posture. | `MxGateway:Ldap:UserNameAttribute` | `cn` | LDAP attribute holding the login user name. | | `MxGateway:Ldap:DisplayNameAttribute` | `cn` | LDAP attribute holding the display name. | | `MxGateway:Ldap:GroupAttribute` | `memberOf` | LDAP attribute enumerating group membership (mapped to dashboard roles via `MxGateway:Dashboard:GroupToRole`). | +| `MxGateway:Ldap:FallbackServers` | *(empty)* | Ordered backup LDAP endpoints tried when the primary fails with a system-side error (connect/TLS, service-account bind, or search) — **not** when a user's credentials are simply wrong. Each entry is `host` (adopting `Port`) or `host:port`. Empty leaves single-endpoint behaviour exactly as before. Endpoint preference is sticky: the last endpoint that answered keeps being used until it fails. The `Transport` / `AllowInsecure` policy applies to every endpoint — a fallback is not a way to downgrade TLS. Entries are parsed at startup and a malformed one fails the boot, so a typo'd backup DC cannot lie dormant until the outage it exists to survive. Requires ZB.MOM.WW.Auth 0.2.0+. | When LDAP is enabled, `Server`, `SearchBase`, `ServiceAccountDn`, `ServiceAccountPassword`, and the attribute names must be non-blank, and `Port` diff --git a/src/ZB.MOM.WW.MxGateway.IntegrationTests/ZB.MOM.WW.MxGateway.IntegrationTests.csproj b/src/ZB.MOM.WW.MxGateway.IntegrationTests/ZB.MOM.WW.MxGateway.IntegrationTests.csproj index e8be761..a24bce5 100644 --- a/src/ZB.MOM.WW.MxGateway.IntegrationTests/ZB.MOM.WW.MxGateway.IntegrationTests.csproj +++ b/src/ZB.MOM.WW.MxGateway.IntegrationTests/ZB.MOM.WW.MxGateway.IntegrationTests.csproj @@ -22,8 +22,8 @@ (IntegrationTests-028). --> - - + + diff --git a/src/ZB.MOM.WW.MxGateway.Server/Configuration/EffectiveLdapConfiguration.cs b/src/ZB.MOM.WW.MxGateway.Server/Configuration/EffectiveLdapConfiguration.cs index e1e82a4..168a297 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Configuration/EffectiveLdapConfiguration.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Configuration/EffectiveLdapConfiguration.cs @@ -11,4 +11,5 @@ public sealed record EffectiveLdapConfiguration( string ServiceAccountPassword, string UserNameAttribute, string DisplayNameAttribute, - string GroupAttribute); + string GroupAttribute, + IReadOnlyList FallbackServers); diff --git a/src/ZB.MOM.WW.MxGateway.Server/Configuration/GatewayConfigurationProvider.cs b/src/ZB.MOM.WW.MxGateway.Server/Configuration/GatewayConfigurationProvider.cs index 8fb4071..ae66052 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Configuration/GatewayConfigurationProvider.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Configuration/GatewayConfigurationProvider.cs @@ -30,7 +30,8 @@ public sealed class GatewayConfigurationProvider(IOptions option ServiceAccountPassword: RedactedValue, UserNameAttribute: value.Ldap.UserNameAttribute, DisplayNameAttribute: value.Ldap.DisplayNameAttribute, - GroupAttribute: value.Ldap.GroupAttribute), + GroupAttribute: value.Ldap.GroupAttribute, + FallbackServers: value.Ldap.FallbackServers), Worker: new EffectiveWorkerConfiguration( ExecutablePath: value.Worker.ExecutablePath, WorkingDirectory: value.Worker.WorkingDirectory, diff --git a/src/ZB.MOM.WW.MxGateway.Server/Configuration/LdapOptions.cs b/src/ZB.MOM.WW.MxGateway.Server/Configuration/LdapOptions.cs index ebfb9e3..b8f5330 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Configuration/LdapOptions.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Configuration/LdapOptions.cs @@ -68,4 +68,19 @@ public sealed class LdapOptions /// Gets the LDAP attribute name for group membership. public string GroupAttribute { get; init; } = "memberOf"; + + /// + /// Gets the ordered fallback LDAP endpoints ("host" or "host:port") the shared + /// provider walks when the primary fails with a system-side error. Empty (the default) leaves + /// single-endpoint behaviour unchanged. Mirrors + /// , added in + /// ZB.MOM.WW.Auth 0.2.0. + /// + /// Carried here only so the effective-config display does not hide a configured backup DC — + /// nothing on the gateway side reads it. Entry syntax is validated at boot by the shared + /// LdapOptionsValidator, which owns the (internal) parser; re-validating here would + /// mean a second, drifting copy of that grammar. + /// + /// + public IReadOnlyList FallbackServers { get; init; } = []; } diff --git a/src/ZB.MOM.WW.MxGateway.Server/Dashboard/Components/Pages/SettingsPage.razor b/src/ZB.MOM.WW.MxGateway.Server/Dashboard/Components/Pages/SettingsPage.razor index 628be01..2d57f39 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Dashboard/Components/Pages/SettingsPage.razor +++ b/src/ZB.MOM.WW.MxGateway.Server/Dashboard/Components/Pages/SettingsPage.razor @@ -26,6 +26,21 @@ else Run migrations@Snapshot.Configuration.Authentication.RunMigrationsOnStartup LDAP enabled@Snapshot.Configuration.Ldap.Enabled LDAP server@Snapshot.Configuration.Ldap.Server:@Snapshot.Configuration.Ldap.Port + + LDAP fallback servers + @* Rendered even when empty: "none" is the operationally interesting answer + on a host someone believes has a backup DC configured. *@ + + @if (Snapshot.Configuration.Ldap.FallbackServers.Count == 0) + { + none + } + else + { + @string.Join(", ", Snapshot.Configuration.Ldap.FallbackServers) + } + + LDAP transport@Snapshot.Configuration.Ldap.Transport LDAP search base@Snapshot.Configuration.Ldap.SearchBase LDAP service account@Snapshot.Configuration.Ldap.ServiceAccountDn diff --git a/src/ZB.MOM.WW.MxGateway.Server/ZB.MOM.WW.MxGateway.Server.csproj b/src/ZB.MOM.WW.MxGateway.Server/ZB.MOM.WW.MxGateway.Server.csproj index 93162cf..22b0147 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/ZB.MOM.WW.MxGateway.Server.csproj +++ b/src/ZB.MOM.WW.MxGateway.Server/ZB.MOM.WW.MxGateway.Server.csproj @@ -10,20 +10,20 @@ - - - - + + + + - + - - - + + +