feat(auth): cut ScadaBridge over to ZB.MOM.WW.Auth.Ldap; nest+rename Ldap config; roles+sitescope via IGroupRoleMapper (Task 1.2/1.4)
This commit is contained in:
@@ -38,17 +38,19 @@ public class ScadaBridgeWebApplicationFactory : WebApplicationFactory<Program>
|
||||
["ScadaBridge__Database__MachineDataDb"] = "Server=localhost;Database=ScadaBridge_MachineData_Test;TrustServerCertificate=True",
|
||||
["ScadaBridge__Database__SkipMigrations"] = "true",
|
||||
["ScadaBridge__Security__JwtSigningKey"] = "integration-test-signing-key-must-be-at-least-32-chars-long",
|
||||
["ScadaBridge__Security__LdapServer"] = "localhost",
|
||||
["ScadaBridge__Security__LdapPort"] = "3893",
|
||||
["ScadaBridge__Security__LdapUseTls"] = "false",
|
||||
["ScadaBridge__Security__AllowInsecureLdap"] = "true",
|
||||
["ScadaBridge__Security__LdapSearchBase"] = "dc=scadabridge,dc=local",
|
||||
// GLAuth places users at cn=<name>,ou=<group>,ou=users,dc=... — the
|
||||
// no-service-account fallback DN (uid=<name>,dc=...) does not match,
|
||||
// so a service account is configured to enable search-then-bind:
|
||||
// resolve the user's real DN by (uid=<name>) lookup, then bind it.
|
||||
["ScadaBridge__Security__LdapServiceAccountDn"] = "cn=admin,ou=SCADA-Admins,ou=users,dc=scadabridge,dc=local",
|
||||
["ScadaBridge__Security__LdapServiceAccountPassword"] = "password",
|
||||
// Task 1.4: LDAP settings nest under Security:Ldap (shared LdapOptions) and use
|
||||
// the renamed keys (Transport replaces LdapUseTls; None == plaintext for the
|
||||
// GLAuth dev directory, paired with AllowInsecure=true).
|
||||
["ScadaBridge__Security__Ldap__Server"] = "localhost",
|
||||
["ScadaBridge__Security__Ldap__Port"] = "3893",
|
||||
["ScadaBridge__Security__Ldap__Transport"] = "None",
|
||||
["ScadaBridge__Security__Ldap__AllowInsecure"] = "true",
|
||||
["ScadaBridge__Security__Ldap__SearchBase"] = "dc=scadabridge,dc=local",
|
||||
// GLAuth places users at cn=<name>,ou=<group>,ou=users,dc=... — a service
|
||||
// account is configured to enable the shared service's search-then-bind:
|
||||
// resolve the user's real DN by (UserNameAttribute=<name>) lookup, then bind it.
|
||||
["ScadaBridge__Security__Ldap__ServiceAccountDn"] = "cn=admin,ou=SCADA-Admins,ou=users,dc=scadabridge,dc=local",
|
||||
["ScadaBridge__Security__Ldap__ServiceAccountPassword"] = "password",
|
||||
};
|
||||
|
||||
foreach (var (key, value) in envVars)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using ZB.MOM.WW.Auth.Abstractions.Ldap;
|
||||
using ZB.MOM.WW.ScadaBridge.Security;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.IntegrationTests;
|
||||
@@ -23,18 +24,19 @@ public class SecurityHardeningTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SecurityOptions_LdapUseTls_DefaultsToTrue()
|
||||
public void LdapOptions_Transport_DefaultsToLdaps()
|
||||
{
|
||||
// Production requires LDAPS. The default must be true.
|
||||
var options = new SecurityOptions();
|
||||
Assert.True(options.LdapUseTls);
|
||||
// Production requires encrypted transport. The shared LdapOptions defaults to
|
||||
// LDAPS (secure-by-default), preserving the donor's LdapUseTls=true default.
|
||||
var options = new LdapOptions();
|
||||
Assert.Equal(LdapTransport.Ldaps, options.Transport);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SecurityOptions_AllowInsecureLdap_DefaultsToFalse()
|
||||
public void LdapOptions_AllowInsecure_DefaultsToFalse()
|
||||
{
|
||||
var options = new SecurityOptions();
|
||||
Assert.False(options.AllowInsecureLdap);
|
||||
var options = new LdapOptions();
|
||||
Assert.False(options.AllowInsecure);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -172,10 +174,21 @@ public class SecurityHardeningTests
|
||||
[Fact]
|
||||
public void StartupValidator_RejectsInsecureLdapInProduction()
|
||||
{
|
||||
// The SecurityOptions.AllowInsecureLdap defaults to false.
|
||||
// Only when explicitly set to true (for dev/test) is insecure LDAP allowed.
|
||||
var prodOptions = new SecurityOptions { LdapUseTls = true, AllowInsecureLdap = false };
|
||||
Assert.True(prodOptions.LdapUseTls);
|
||||
Assert.False(prodOptions.AllowInsecureLdap);
|
||||
// The shared LdapOptionsValidator (registered with ValidateOnStart by AddZbLdapAuth)
|
||||
// rejects plaintext transport (Transport=None) unless AllowInsecure is explicitly set,
|
||||
// preserving the donor's production LDAPS-enforcement guarantee.
|
||||
var insecure = new LdapOptions
|
||||
{
|
||||
Server = "ldap.example.com",
|
||||
SearchBase = "dc=example,dc=com",
|
||||
ServiceAccountDn = "cn=admin,dc=example,dc=com",
|
||||
Transport = LdapTransport.None,
|
||||
AllowInsecure = false,
|
||||
};
|
||||
|
||||
var result = new ZB.MOM.WW.Auth.Ldap.LdapOptionsValidator().Validate(name: null, insecure);
|
||||
|
||||
Assert.True(result.Failed);
|
||||
Assert.Contains(nameof(LdapOptions.AllowInsecure), result.FailureMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ public class StartupValidationTests
|
||||
["ScadaBridge__Cluster__SeedNodes__1"] = "akka.tcp://scadabridge@localhost:8082",
|
||||
["ScadaBridge__Database__ConfigurationDb"] = "Server=x;Database=x",
|
||||
["ScadaBridge__Database__MachineDataDb"] = "Server=x;Database=x",
|
||||
["ScadaBridge__Security__LdapServer"] = "localhost",
|
||||
["ScadaBridge__Security__Ldap__Server"] = "localhost",
|
||||
// Deliberately missing JwtSigningKey
|
||||
});
|
||||
|
||||
@@ -92,11 +92,13 @@ public class StartupValidationTests
|
||||
"ScadaBridge__Database__MachineDataDb",
|
||||
"ScadaBridge__Database__SkipMigrations",
|
||||
"ScadaBridge__Security__JwtSigningKey",
|
||||
"ScadaBridge__Security__LdapServer",
|
||||
"ScadaBridge__Security__LdapPort",
|
||||
"ScadaBridge__Security__LdapUseTls",
|
||||
"ScadaBridge__Security__AllowInsecureLdap",
|
||||
"ScadaBridge__Security__LdapSearchBase",
|
||||
"ScadaBridge__Security__Ldap__Server",
|
||||
"ScadaBridge__Security__Ldap__Port",
|
||||
"ScadaBridge__Security__Ldap__Transport",
|
||||
"ScadaBridge__Security__Ldap__AllowInsecure",
|
||||
"ScadaBridge__Security__Ldap__SearchBase",
|
||||
"ScadaBridge__Security__Ldap__ServiceAccountDn",
|
||||
"ScadaBridge__Security__Ldap__ServiceAccountPassword",
|
||||
};
|
||||
|
||||
public TempEnvironment(Dictionary<string, string> varsToSet)
|
||||
|
||||
Reference in New Issue
Block a user