fix(auth): OtOpcUa 1.2 review fixes — startup insecure-transport guard + Ldaps in prod overlays, test fidelity, 0.1.1 pin
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using ZB.MOM.WW.Configuration;
|
||||
using ZB.MOM.WW.OtOpcUa.Security.Ldap;
|
||||
using LdapTransport = ZB.MOM.WW.Auth.Abstractions.Ldap.LdapTransport;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Host.Configuration;
|
||||
|
||||
@@ -10,13 +11,19 @@ namespace ZB.MOM.WW.OtOpcUa.Host.Configuration;
|
||||
/// TCP port; when disabled — or when <c>DevStubMode</c> bypasses the real bind — all checks are
|
||||
/// skipped. <c>ServiceAccountDn</c>/<c>Password</c> are
|
||||
/// intentionally not required — an empty pair selects the direct-bind path (see
|
||||
/// <see cref="LdapOptions.ServiceAccountDn"/>). The plaintext-transport-without-AllowInsecure
|
||||
/// guard is enforced at the auth boundary (<see cref="OtOpcUaLdapAuthService"/>) rather than here,
|
||||
/// to preserve the bespoke service's behaviour of booting and failing closed at login (not at
|
||||
/// startup) when a config selects insecure transport. Failure messages use <c>"Ldap:"</c> as a
|
||||
/// <see cref="LdapOptions.ServiceAccountDn"/>). Failure messages use <c>"Ldap:"</c> as a
|
||||
/// human-readable field prefix — not the literal bound section path, which is
|
||||
/// <c>Security:Ldap</c> (see <see cref="LdapOptions.SectionName"/>).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Insecure-transport guard (review fix): a real-LDAP config that selects plaintext transport
|
||||
/// (<see cref="LdapTransport.None"/>) without opting in via <see cref="LdapOptions.AllowInsecure"/>
|
||||
/// now FAILS startup validation, so an insecure-by-accident production overlay never boots.
|
||||
/// This mirrors the login-time fail-closed guard in <see cref="OtOpcUaLdapAuthService"/> and is
|
||||
/// gated on the same conditions (<see cref="LdapOptions.Enabled"/> AND not
|
||||
/// <see cref="LdapOptions.DevStubMode"/>): a disabled or dev-stub config is exempt, exactly as it
|
||||
/// is exempt from the real bind. The login-time guard remains as defence in depth.
|
||||
/// </remarks>
|
||||
public sealed class LdapOptionsValidator : OptionsValidatorBase<LdapOptions>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
@@ -32,5 +39,13 @@ public sealed class LdapOptionsValidator : OptionsValidatorBase<LdapOptions>
|
||||
builder.RequireThat(!string.IsNullOrWhiteSpace(options.SearchBase),
|
||||
"Ldap:SearchBase is required when LDAP login is enabled.");
|
||||
builder.Port(options.Port, "Ldap:Port");
|
||||
|
||||
// Fail closed at startup on a plaintext transport unless explicitly opted in — same
|
||||
// condition the login-time guard in OtOpcUaLdapAuthService enforces, lifted to boot so an
|
||||
// insecure-by-accident production overlay refuses to start rather than silently failing
|
||||
// every bind at login.
|
||||
builder.RequireThat(
|
||||
!(options.Transport == LdapTransport.None && !options.AllowInsecure),
|
||||
"LDAP transport is None (plaintext) but AllowInsecure is false — set Transport to Ldaps/StartTls or set AllowInsecure for dev.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
},
|
||||
"Security": {
|
||||
"Ldap": {
|
||||
"DevStubMode": false
|
||||
"DevStubMode": false,
|
||||
"Transport": "Ldaps"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
},
|
||||
"Security": {
|
||||
"Ldap": {
|
||||
"DevStubMode": false
|
||||
"DevStubMode": false,
|
||||
"Transport": "Ldaps"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
},
|
||||
"Security": {
|
||||
"Ldap": {
|
||||
"DevStubMode": false
|
||||
"DevStubMode": false,
|
||||
"Transport": "Ldaps"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,6 +103,12 @@ public static class AuthEndpoints
|
||||
{
|
||||
// A DB hiccup (or any mapper fault) must never block sign-in — fall back to the
|
||||
// pre-resolved baseline roles (empty on the real path, FleetAdmin under DevStub).
|
||||
// This is intentionally FAIL-CLOSED on the real LDAP path: result.Roles is empty there
|
||||
// (the library returns groups, never roles — the mapper is the sole role source), so a
|
||||
// mapper fault signs the user in AUTHENTICATED but with ZERO role claims. They can prove
|
||||
// identity but are denied every role-gated action until the mapper recovers — strictly
|
||||
// safer than failing open with a stale/guessed role set. (See AuthEndpoints test
|
||||
// Login_when_role_mapper_throws_signs_in_with_no_role_claims.)
|
||||
http.RequestServices.GetService<ILoggerFactory>()?
|
||||
.CreateLogger("ZB.MOM.WW.OtOpcUa.Security.AuthEndpoints")
|
||||
.LogWarning(ex, "Role-map lookup failed for {User}; using pre-resolved baseline roles", username);
|
||||
|
||||
Reference in New Issue
Block a user