From 957204578714f48083663bf2711bca8c27adc8c6 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Tue, 2 Jun 2026 06:44:38 -0400 Subject: [PATCH] chore(auth): MxGateway unify dev LDAP base DN to dc=zb,dc=local (Task 1.6) --- CLAUDE.md | 2 +- glauth.md | 40 +++++++++---------- .../Configuration/LdapOptions.cs | 4 +- .../appsettings.json | 4 +- .../Dashboard/DashboardAuthenticatorTests.cs | 2 +- .../DashboardGroupRoleMapperTests.cs | 4 +- 6 files changed, 28 insertions(+), 28 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 726a48a..8cf6ea1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -100,7 +100,7 @@ When source code changes, build and test the affected component before reporting ## Design Sources To Consult Before Non-Trivial Changes - `gateway.md` — top-level architecture, command/event surface, IPC envelope, STA thread model, fault handling. -- `glauth.md` — local LDAP server (GLAuth on `localhost:3893`, base DN `dc=lmxopcua,dc=local`) used for dev authn. Pre-provisioned users (`admin/admin123`, `readonly/readonly123`, etc.) and the role→capability mapping live there. +- `glauth.md` — local LDAP server (GLAuth on `localhost:3893`, base DN `dc=zb,dc=local`) used for dev authn. Pre-provisioned users (`admin/admin123`, `readonly/readonly123`, etc.) and the role→capability mapping live there. - `docs/DesignDecisions.md` — v1 choices (MXAccess COM target `LMXProxyServerClass` from `C:\Program Files (x86)\ArchestrA\Framework\Bin\ArchestrA.MXAccess.dll`, API-key-in-SQLite auth, fail-fast event backpressure, etc.). - `docs/GatewayProcessDesign.md`, `docs/MxAccessWorkerInstanceDesign.md`, `docs/WorkerFrameProtocol.md`, `docs/WorkerProcessLauncher.md` — detailed component designs. - `docs/GatewayConfiguration.md` — full `MxGateway:*` options bound by `GatewayOptions` and validated at startup by `GatewayOptionsValidator`. diff --git a/glauth.md b/glauth.md index 9ade93a..4c3659f 100644 --- a/glauth.md +++ b/glauth.md @@ -20,9 +20,9 @@ against them, and what's needed to add a gw-specific role. | Host | `localhost` | | Port | `3893` | | LDAPS | disabled in dev (set `[ldaps]` block to enable) | -| Base DN | `dc=lmxopcua,dc=local` | -| Bind DN format | `cn={username},dc=lmxopcua,dc=local` | -| Group OU | `ou=,ou=groups,dc=lmxopcua,dc=local` | +| Base DN | `dc=zb,dc=local` | +| Bind DN format | `cn={username},dc=zb,dc=local` | +| Group OU | `ou=,ou=groups,dc=zb,dc=local` | | Failed-bind throttle | 3 fails → 10-minute IP lockout (per `[behaviors]`) | ## Pre-existing groups (LmxOpcUa role taxonomy) @@ -33,11 +33,11 @@ LmxOpcUa write rights doesn't need a second account for the gw. | Group | GID | DN | LmxOpcUa meaning | Suggested mxgw mapping | |---|---|---|---|---| -| ReadOnly | 5501 | `ou=ReadOnly,ou=groups,dc=lmxopcua,dc=local` | Browse + read OPC UA nodes | `Browse` + `Subscribe` (read paths only) | -| WriteOperate | 5502 | `ou=WriteOperate,ou=groups,dc=lmxopcua,dc=local` | Write FreeAccess / Operate attrs | `Write` (plain) | -| WriteTune | 5504 | `ou=WriteTune,ou=groups,dc=lmxopcua,dc=local` | Write Tune attrs | `WriteSecured` (Tune only) | -| WriteConfigure | 5505 | `ou=WriteConfigure,ou=groups,dc=lmxopcua,dc=local` | Write Configure attrs | `WriteSecured` (Configure) | -| AlarmAck | 5503 | `ou=AlarmAck,ou=groups,dc=lmxopcua,dc=local` | Acknowledge alarms | gw alarm-ack RPC, when added | +| ReadOnly | 5501 | `ou=ReadOnly,ou=groups,dc=zb,dc=local` | Browse + read OPC UA nodes | `Browse` + `Subscribe` (read paths only) | +| WriteOperate | 5502 | `ou=WriteOperate,ou=groups,dc=zb,dc=local` | Write FreeAccess / Operate attrs | `Write` (plain) | +| WriteTune | 5504 | `ou=WriteTune,ou=groups,dc=zb,dc=local` | Write Tune attrs | `WriteSecured` (Tune only) | +| WriteConfigure | 5505 | `ou=WriteConfigure,ou=groups,dc=zb,dc=local` | Write Configure attrs | `WriteSecured` (Configure) | +| AlarmAck | 5503 | `ou=AlarmAck,ou=groups,dc=zb,dc=local` | Acknowledge alarms | gw alarm-ack RPC, when added | **A user can be in multiple groups** — `othergroups = [...]` in the config is a list. `admin` is the canonical example (in every role @@ -72,7 +72,7 @@ group](#provisioning-the-gwadmin-group) below. ### 1. Direct bind (simplest) ``` -DN: cn=admin,dc=lmxopcua,dc=local +DN: cn=admin,dc=zb,dc=local Password: admin123 ``` @@ -84,9 +84,9 @@ by `sAMAccountName`, not `cn`. Use this only for dev convenience. ### 2. Bind-then-search (production-grade) ``` -1. Bind as the service account (cn=serviceaccount,dc=lmxopcua,dc=local +1. Bind as the service account (cn=serviceaccount,dc=zb,dc=local / serviceaccount123). -2. Search under dc=lmxopcua,dc=local with filter +2. Search under dc=zb,dc=local with filter (uid=) — or any attribute the deployment identifies users by. GLAuth populates uid + cn. 3. Read the returned entry's DN + memberOf list (groups). @@ -116,8 +116,8 @@ ldap: port: 3893 useTls: false allowInsecureLdap: true # dev only - searchBase: "dc=lmxopcua,dc=local" - serviceAccountDn: "cn=serviceaccount,dc=lmxopcua,dc=local" + searchBase: "dc=zb,dc=local" + serviceAccountDn: "cn=serviceaccount,dc=zb,dc=local" serviceAccountPassword: "serviceaccount123" userNameAttribute: "uid" # GLAuth populates this; AD uses sAMAccountName displayNameAttribute: "cn" @@ -131,7 +131,7 @@ ldap: ``` `groupAttribute` returns full DNs like -`ou=ReadOnly,ou=groups,dc=lmxopcua,dc=local` — the authenticator +`ou=ReadOnly,ou=groups,dc=zb,dc=local` — the authenticator should strip the leading `ou=` (or `cn=` against AD) RDN value and look that up in `groupToRole`. @@ -172,7 +172,7 @@ server: 4. `nssm restart GLAuth` After the restart, `admin`'s `memberOf` includes -`ou=GwAdmin,ou=groups,dc=lmxopcua,dc=local`, which the authenticator +`ou=GwAdmin,ou=groups,dc=zb,dc=local`, which the authenticator strips to `GwAdmin` and matches against `RequiredGroup`. The same pattern applies to any future permission that doesn't fit the existing five roles. @@ -201,7 +201,7 @@ $ldap = New-Object System.DirectoryServices.Protocols.LdapConnection("localhost: $ldap.AuthType = [System.DirectoryServices.Protocols.AuthType]::Basic $ldap.SessionOptions.ProtocolVersion = 3 $ldap.SessionOptions.SecureSocketLayer = $false -$cred = New-Object System.Net.NetworkCredential("cn=admin,dc=lmxopcua,dc=local","admin123") +$cred = New-Object System.Net.NetworkCredential("cn=admin,dc=zb,dc=local","admin123") $ldap.Bind($cred) "Bind OK" ``` @@ -210,8 +210,8 @@ Or via `ldapsearch` if you have OpenLDAP CLI tools: ```bash ldapsearch -x -H ldap://localhost:3893 \ - -D "cn=admin,dc=lmxopcua,dc=local" -w admin123 \ - -b "dc=lmxopcua,dc=local" "(uid=admin)" + -D "cn=admin,dc=zb,dc=local" -w admin123 \ + -b "dc=zb,dc=local" "(uid=admin)" ``` The response should list `admin`'s entry with `memberOf` populated for @@ -257,8 +257,8 @@ applies to mxaccessgw verbatim. Keys that change: | `Port` | `3893` | `636` (LDAPS) — AD increasingly rejects plain bind under LDAP-signing enforcement | | `UseTls` | `false` | `true` | | `AllowInsecureLdap` | `true` | `false` | -| `SearchBase` | `dc=lmxopcua,dc=local` | `DC=corp,DC=example,DC=com` | -| `ServiceAccountDn` | `cn=serviceaccount,dc=lmxopcua,dc=local` | `CN=MxGwSvc,OU=Service Accounts,DC=corp,...` | +| `SearchBase` | `dc=zb,dc=local` | `DC=corp,DC=example,DC=com` | +| `ServiceAccountDn` | `cn=serviceaccount,dc=zb,dc=local` | `CN=MxGwSvc,OU=Service Accounts,DC=corp,...` | | `UserNameAttribute` | `uid` | `sAMAccountName` (or `userPrincipalName`) | | `GroupAttribute` | `memberOf` (unchanged) | `memberOf` (unchanged) | diff --git a/src/ZB.MOM.WW.MxGateway.Server/Configuration/LdapOptions.cs b/src/ZB.MOM.WW.MxGateway.Server/Configuration/LdapOptions.cs index e86d26c..a367bb4 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Configuration/LdapOptions.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Configuration/LdapOptions.cs @@ -52,10 +52,10 @@ public sealed class LdapOptions public bool AllowInsecure { get; init; } = true; /// Gets the LDAP search base distinguished name. - public string SearchBase { get; init; } = "dc=lmxopcua,dc=local"; + public string SearchBase { get; init; } = "dc=zb,dc=local"; /// Gets the service account distinguished name. - public string ServiceAccountDn { get; init; } = "cn=serviceaccount,dc=lmxopcua,dc=local"; + public string ServiceAccountDn { get; init; } = "cn=serviceaccount,dc=zb,dc=local"; /// Gets the service account password. public string ServiceAccountPassword { get; init; } = "serviceaccount123"; diff --git a/src/ZB.MOM.WW.MxGateway.Server/appsettings.json b/src/ZB.MOM.WW.MxGateway.Server/appsettings.json index a5fa095..f14bc39 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/appsettings.json +++ b/src/ZB.MOM.WW.MxGateway.Server/appsettings.json @@ -24,8 +24,8 @@ "Port": 3893, "Transport": "None", "AllowInsecure": true, - "SearchBase": "dc=lmxopcua,dc=local", - "ServiceAccountDn": "cn=serviceaccount,dc=lmxopcua,dc=local", + "SearchBase": "dc=zb,dc=local", + "ServiceAccountDn": "cn=serviceaccount,dc=zb,dc=local", "ServiceAccountPassword": "serviceaccount123", "UserNameAttribute": "cn", "DisplayNameAttribute": "cn", diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardAuthenticatorTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardAuthenticatorTests.cs index 48fd21c..7cd03a6 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardAuthenticatorTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardAuthenticatorTests.cs @@ -219,7 +219,7 @@ public sealed class DashboardAuthenticatorTests [Fact] public async Task AuthenticateAsync_GroupAsDistinguishedNameFromService_ResolvesRoleAndSurfacesServiceValue() { - const string groupDn = "ou=GwAdmin,ou=groups,dc=lmxopcua,dc=local"; + const string groupDn = "ou=GwAdmin,ou=groups,dc=zb,dc=local"; FakeLdapAuthService ldap = new(LdapAuthResult.Success( username: "admin", displayName: "admin", diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardGroupRoleMapperTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardGroupRoleMapperTests.cs index 727e297..7d02fed 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardGroupRoleMapperTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardGroupRoleMapperTests.cs @@ -38,7 +38,7 @@ public sealed class DashboardGroupRoleMapperTests [Theory] [InlineData("GwAdmin", DashboardRoles.Admin)] [InlineData("gwadmin", DashboardRoles.Admin)] - [InlineData("ou=GwAdmin,ou=groups,dc=lmxopcua,dc=local", DashboardRoles.Admin)] + [InlineData("ou=GwAdmin,ou=groups,dc=zb,dc=local", DashboardRoles.Admin)] [InlineData("OtherGroup", null)] public async Task MapAsync_ResolvesByShortNameAndDistinguishedName( string ldapGroup, @@ -94,7 +94,7 @@ public sealed class DashboardGroupRoleMapperTests { Dictionary mapping = StandardMapping(); DashboardGroupRoleMapper mapper = CreateMapper(mapping); - string[] groups = ["ou=GwAdmin,ou=groups,dc=lmxopcua,dc=local", "gwreader"]; + string[] groups = ["ou=GwAdmin,ou=groups,dc=zb,dc=local", "gwreader"]; IReadOnlyList helperRoles = DashboardGroupRoleMapping.MapGroupsToRoles(groups, mapping); GroupRoleMapping mapperResult = await mapper.MapAsync(groups, CancellationToken.None);