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
|
## 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,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);
|
||||||
|
|||||||
Reference in New Issue
Block a user