chore(auth): MxGateway unify dev LDAP base DN to dc=zb,dc=local (Task 1.6)
This commit is contained in:
@@ -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`.
|
||||
|
||||
@@ -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=<groupname>,ou=groups,dc=lmxopcua,dc=local` |
|
||||
| Base DN | `dc=zb,dc=local` |
|
||||
| Bind DN format | `cn={username},dc=zb,dc=local` |
|
||||
| Group OU | `ou=<groupname>,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=<entered-username>) — 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) |
|
||||
|
||||
|
||||
@@ -52,10 +52,10 @@ public sealed class LdapOptions
|
||||
public bool AllowInsecure { get; init; } = true;
|
||||
|
||||
/// <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>
|
||||
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>
|
||||
public string ServiceAccountPassword { get; init; } = "serviceaccount123";
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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<string, string> 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<string> helperRoles = DashboardGroupRoleMapping.MapGroupsToRoles(groups, mapping);
|
||||
GroupRoleMapping<string> mapperResult = await mapper.MapAsync(groups, CancellationToken.None);
|
||||
|
||||
Reference in New Issue
Block a user