chore(auth): MxGateway unify dev LDAP base DN to dc=zb,dc=local (Task 1.6)

This commit is contained in:
Joseph Doherty
2026-06-02 06:44:38 -04:00
parent 7e1af37eb1
commit 9572045787
6 changed files with 28 additions and 28 deletions
+1 -1
View File
@@ -100,7 +100,7 @@ When source code changes, build and test the affected component before reporting
## Design Sources To Consult Before Non-Trivial Changes ## Design Sources To Consult Before Non-Trivial Changes
- `gateway.md` — top-level architecture, command/event surface, IPC envelope, STA thread model, fault handling. - `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/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/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`. - `docs/GatewayConfiguration.md` — full `MxGateway:*` options bound by `GatewayOptions` and validated at startup by `GatewayOptionsValidator`.
+20 -20
View File
@@ -20,9 +20,9 @@ against them, and what's needed to add a gw-specific role.
| Host | `localhost` | | Host | `localhost` |
| Port | `3893` | | Port | `3893` |
| LDAPS | disabled in dev (set `[ldaps]` block to enable) | | LDAPS | disabled in dev (set `[ldaps]` block to enable) |
| Base DN | `dc=lmxopcua,dc=local` | | Base DN | `dc=zb,dc=local` |
| Bind DN format | `cn={username},dc=lmxopcua,dc=local` | | Bind DN format | `cn={username},dc=zb,dc=local` |
| Group OU | `ou=<groupname>,ou=groups,dc=lmxopcua,dc=local` | | Group OU | `ou=<groupname>,ou=groups,dc=zb,dc=local` |
| Failed-bind throttle | 3 fails → 10-minute IP lockout (per `[behaviors]`) | | Failed-bind throttle | 3 fails → 10-minute IP lockout (per `[behaviors]`) |
## Pre-existing groups (LmxOpcUa role taxonomy) ## 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 | | 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) | | 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=lmxopcua,dc=local` | Write FreeAccess / Operate attrs | `Write` (plain) | | WriteOperate | 5502 | `ou=WriteOperate,ou=groups,dc=zb,dc=local` | Write FreeAccess / Operate attrs | `Write` (plain) |
| WriteTune | 5504 | `ou=WriteTune,ou=groups,dc=lmxopcua,dc=local` | Write Tune attrs | `WriteSecured` (Tune only) | | WriteTune | 5504 | `ou=WriteTune,ou=groups,dc=zb,dc=local` | Write Tune attrs | `WriteSecured` (Tune only) |
| WriteConfigure | 5505 | `ou=WriteConfigure,ou=groups,dc=lmxopcua,dc=local` | Write Configure attrs | `WriteSecured` (Configure) | | WriteConfigure | 5505 | `ou=WriteConfigure,ou=groups,dc=zb,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 | | 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 **A user can be in multiple groups**`othergroups = [...]` in the
config is a list. `admin` is the canonical example (in every role 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) ### 1. Direct bind (simplest)
``` ```
DN: cn=admin,dc=lmxopcua,dc=local DN: cn=admin,dc=zb,dc=local
Password: admin123 Password: admin123
``` ```
@@ -84,9 +84,9 @@ by `sAMAccountName`, not `cn`. Use this only for dev convenience.
### 2. Bind-then-search (production-grade) ### 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). / serviceaccount123).
2. Search under dc=lmxopcua,dc=local with filter 2. Search under dc=zb,dc=local with filter
(uid=<entered-username>) — or any attribute the deployment (uid=<entered-username>) — or any attribute the deployment
identifies users by. GLAuth populates uid + cn. identifies users by. GLAuth populates uid + cn.
3. Read the returned entry's DN + memberOf list (groups). 3. Read the returned entry's DN + memberOf list (groups).
@@ -116,8 +116,8 @@ ldap:
port: 3893 port: 3893
useTls: false useTls: false
allowInsecureLdap: true # dev only allowInsecureLdap: true # dev only
searchBase: "dc=lmxopcua,dc=local" searchBase: "dc=zb,dc=local"
serviceAccountDn: "cn=serviceaccount,dc=lmxopcua,dc=local" serviceAccountDn: "cn=serviceaccount,dc=zb,dc=local"
serviceAccountPassword: "serviceaccount123" serviceAccountPassword: "serviceaccount123"
userNameAttribute: "uid" # GLAuth populates this; AD uses sAMAccountName userNameAttribute: "uid" # GLAuth populates this; AD uses sAMAccountName
displayNameAttribute: "cn" displayNameAttribute: "cn"
@@ -131,7 +131,7 @@ ldap:
``` ```
`groupAttribute` returns full DNs like `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 should strip the leading `ou=` (or `cn=` against AD) RDN value and
look that up in `groupToRole`. look that up in `groupToRole`.
@@ -172,7 +172,7 @@ server:
4. `nssm restart GLAuth` 4. `nssm restart GLAuth`
After the restart, `admin`'s `memberOf` includes 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 strips to `GwAdmin` and matches against `RequiredGroup`. The same
pattern applies to any future permission that doesn't fit the existing pattern applies to any future permission that doesn't fit the existing
five roles. five roles.
@@ -201,7 +201,7 @@ $ldap = New-Object System.DirectoryServices.Protocols.LdapConnection("localhost:
$ldap.AuthType = [System.DirectoryServices.Protocols.AuthType]::Basic $ldap.AuthType = [System.DirectoryServices.Protocols.AuthType]::Basic
$ldap.SessionOptions.ProtocolVersion = 3 $ldap.SessionOptions.ProtocolVersion = 3
$ldap.SessionOptions.SecureSocketLayer = $false $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) $ldap.Bind($cred)
"Bind OK" "Bind OK"
``` ```
@@ -210,8 +210,8 @@ Or via `ldapsearch` if you have OpenLDAP CLI tools:
```bash ```bash
ldapsearch -x -H ldap://localhost:3893 \ ldapsearch -x -H ldap://localhost:3893 \
-D "cn=admin,dc=lmxopcua,dc=local" -w admin123 \ -D "cn=admin,dc=zb,dc=local" -w admin123 \
-b "dc=lmxopcua,dc=local" "(uid=admin)" -b "dc=zb,dc=local" "(uid=admin)"
``` ```
The response should list `admin`'s entry with `memberOf` populated for 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 | | `Port` | `3893` | `636` (LDAPS) — AD increasingly rejects plain bind under LDAP-signing enforcement |
| `UseTls` | `false` | `true` | | `UseTls` | `false` | `true` |
| `AllowInsecureLdap` | `true` | `false` | | `AllowInsecureLdap` | `true` | `false` |
| `SearchBase` | `dc=lmxopcua,dc=local` | `DC=corp,DC=example,DC=com` | | `SearchBase` | `dc=zb,dc=local` | `DC=corp,DC=example,DC=com` |
| `ServiceAccountDn` | `cn=serviceaccount,dc=lmxopcua,dc=local` | `CN=MxGwSvc,OU=Service Accounts,DC=corp,...` | | `ServiceAccountDn` | `cn=serviceaccount,dc=zb,dc=local` | `CN=MxGwSvc,OU=Service Accounts,DC=corp,...` |
| `UserNameAttribute` | `uid` | `sAMAccountName` (or `userPrincipalName`) | | `UserNameAttribute` | `uid` | `sAMAccountName` (or `userPrincipalName`) |
| `GroupAttribute` | `memberOf` (unchanged) | `memberOf` (unchanged) | | `GroupAttribute` | `memberOf` (unchanged) | `memberOf` (unchanged) |
@@ -52,10 +52,10 @@ public sealed class LdapOptions
public bool AllowInsecure { get; init; } = true; public bool AllowInsecure { get; init; } = true;
/// <summary>Gets the LDAP search base distinguished name.</summary> /// <summary>Gets the LDAP search base distinguished name.</summary>
public string SearchBase { get; init; } = "dc=lmxopcua,dc=local"; public string SearchBase { get; init; } = "dc=zb,dc=local";
/// <summary>Gets the service account distinguished name.</summary> /// <summary>Gets the service account distinguished name.</summary>
public string ServiceAccountDn { get; init; } = "cn=serviceaccount,dc=lmxopcua,dc=local"; public string ServiceAccountDn { get; init; } = "cn=serviceaccount,dc=zb,dc=local";
/// <summary>Gets the service account password.</summary> /// <summary>Gets the service account password.</summary>
public string ServiceAccountPassword { get; init; } = "serviceaccount123"; public string ServiceAccountPassword { get; init; } = "serviceaccount123";
@@ -24,8 +24,8 @@
"Port": 3893, "Port": 3893,
"Transport": "None", "Transport": "None",
"AllowInsecure": true, "AllowInsecure": true,
"SearchBase": "dc=lmxopcua,dc=local", "SearchBase": "dc=zb,dc=local",
"ServiceAccountDn": "cn=serviceaccount,dc=lmxopcua,dc=local", "ServiceAccountDn": "cn=serviceaccount,dc=zb,dc=local",
"ServiceAccountPassword": "serviceaccount123", "ServiceAccountPassword": "serviceaccount123",
"UserNameAttribute": "cn", "UserNameAttribute": "cn",
"DisplayNameAttribute": "cn", "DisplayNameAttribute": "cn",
@@ -219,7 +219,7 @@ public sealed class DashboardAuthenticatorTests
[Fact] [Fact]
public async Task AuthenticateAsync_GroupAsDistinguishedNameFromService_ResolvesRoleAndSurfacesServiceValue() 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( FakeLdapAuthService ldap = new(LdapAuthResult.Success(
username: "admin", username: "admin",
displayName: "admin", displayName: "admin",
@@ -38,7 +38,7 @@ public sealed class DashboardGroupRoleMapperTests
[Theory] [Theory]
[InlineData("GwAdmin", DashboardRoles.Admin)] [InlineData("GwAdmin", DashboardRoles.Admin)]
[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)] [InlineData("OtherGroup", null)]
public async Task MapAsync_ResolvesByShortNameAndDistinguishedName( public async Task MapAsync_ResolvesByShortNameAndDistinguishedName(
string ldapGroup, string ldapGroup,
@@ -94,7 +94,7 @@ public sealed class DashboardGroupRoleMapperTests
{ {
Dictionary<string, string> mapping = StandardMapping(); Dictionary<string, string> mapping = StandardMapping();
DashboardGroupRoleMapper mapper = CreateMapper(mapping); 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<string> helperRoles = DashboardGroupRoleMapping.MapGroupsToRoles(groups, mapping); IReadOnlyList<string> helperRoles = DashboardGroupRoleMapping.MapGroupsToRoles(groups, mapping);
GroupRoleMapping<string> mapperResult = await mapper.MapAsync(groups, CancellationToken.None); GroupRoleMapping<string> mapperResult = await mapper.MapAsync(groups, CancellationToken.None);