fix(TST-31): reconcile glauth.md with the shared GLAuth directory, fix propagated live-test credential
glauth.md's OPC-perm group table and pre-provisioned-users table described a directory that no longer exists: all five OPC-perm gids were off by 100 (5501-5505 vs actual 5601-5605), and the user table listed readonly/writeop/writetune/writeconfig/alarmack accounts that scadaproj's shared GLAuth (config.toml) never provisioned. The real per-role testers are opc-readonly/opc-writeop/opc-writetune/opc-writeconfig/opc-alarmack (renamed to avoid a cn= case-collision with their group), and every dev user's password is "password" per config.toml's own comment and passsha256 (not admin123). admin itself is no longer a member of any OPC-perm group in the shared directory (only GwAdmin + OtOpcUa-Admins) - multi-role is the actual "covers every gw capability" account now. The wrong admin123 default had propagated into MXGATEWAY_LIVE_MXACCESS_WRITE_SECURED_PASSWORD (docs/GatewayTesting.md) and the WorkerLiveMxAccessSmokeTests.cs literal/comment attributing the rig's GLAuth-bridged ArchestrA identity - both corrected to password. Also made the WriteSecured parity test log (never assert) whether AuthenticateUser succeeded or was rejected: MXAccess parity means a bad credential is a legitimate failure reply, so it must not fail the test, but a stale rig credential was silently degrading the nightly live smoke's WriteSecured leg from an authenticated round-trip to a bare failure-reply-shape check with no visible signal. Verified: dotnet build src/ZB.MOM.WW.MxGateway.NonWindows.slnx succeeds (IntegrationTests builds cleanly on macOS, net10.0, no COM/x86 dependency). Flips Batch B (TST-31) to Done in the cycle-3 remediation tracker.
This commit is contained in:
@@ -14,7 +14,7 @@ This plan is executed as **seven parallel batches**, each by one subagent workin
|
|||||||
| Batch | Branch | Findings | Model | Why this model | Status |
|
| Batch | Branch | Findings | Model | Why this model | Status |
|
||||||
|---|---|---|---|---|---|
|
|---|---|---|---|---|---|
|
||||||
| A | `fix/c3-client-versions` | CLI-46 | sonnet | Mechanical five-client bump + README note + one script guard; recipe is fully specified | Not started |
|
| A | `fix/c3-client-versions` | CLI-46 | sonnet | Mechanical five-client bump + README note + one script guard; recipe is fully specified | Not started |
|
||||||
| B | `fix/c3-glauth-doc` | TST-31 | sonnet | Reconcile a table against one TOML source of truth, re-derive one default, change an assert to a log | Not started |
|
| B | `fix/c3-glauth-doc` | TST-31 | sonnet | Reconcile a table against one TOML source of truth, re-derive one default, change an assert to a log | Done |
|
||||||
| C | `fix/c3-tracker-hygiene` | GWC-31 + housekeeping (close GWC-16/IPC-08/IPC-31, refresh TST-06/TST-17, register WRK-31) | sonnet | Cross-file comment/ID sweep needs care about what each ID *means*, but no logic changes | Not started |
|
| C | `fix/c3-tracker-hygiene` | GWC-31 + housekeeping (close GWC-16/IPC-08/IPC-31, refresh TST-06/TST-17, register WRK-31) | sonnet | Cross-file comment/ID sweep needs care about what each ID *means*, but no logic changes | Not started |
|
||||||
| D | `fix/c3-dashboard-acl` | SEC-40, SEC-41, GWC-38 | opus | Authorization semantics, fail-closed decisions, claims re-resolution; each needs a regression test | Not started |
|
| D | `fix/c3-dashboard-acl` | SEC-40, SEC-41, GWC-38 | opus | Authorization semantics, fail-closed decisions, claims re-resolution; each needs a regression test | Not started |
|
||||||
| E | `fix/c3-ipc-boundaries` | IPC-33, IPC-39, IPC-34, IPC-35 | opus | Changes the WorkerClient command lifecycle, the options validator, and the worker drain budget; race-sensitive | Not started |
|
| E | `fix/c3-ipc-boundaries` | IPC-33, IPC-39, IPC-34, IPC-35 | opus | Changes the WorkerClient command lifecycle, the options validator, and the worker drain budget; race-sensitive | Not started |
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ Optional live smoke variables:
|
|||||||
| `MXGATEWAY_LIVE_MXACCESS_CLIENT_NAME` | `ZB.MOM.WW.MxGateway.IntegrationTests` | Client name passed to `Register`. |
|
| `MXGATEWAY_LIVE_MXACCESS_CLIENT_NAME` | `ZB.MOM.WW.MxGateway.IntegrationTests` | Client name passed to `Register`. |
|
||||||
| `MXGATEWAY_LIVE_MXACCESS_EVENT_TIMEOUT_SECONDS` | `15` | Maximum wait for the first `OnDataChange` (also used for the `OnWriteComplete` round-trip and the abnormal-exit fault transition). |
|
| `MXGATEWAY_LIVE_MXACCESS_EVENT_TIMEOUT_SECONDS` | `15` | Maximum wait for the first `OnDataChange` (also used for the `OnWriteComplete` round-trip and the abnormal-exit fault transition). |
|
||||||
| `MXGATEWAY_LIVE_MXACCESS_WRITE_SECURED_USER` | `admin` | ArchestrA user name passed to `AuthenticateUser` before the `WriteSecured` parity step. |
|
| `MXGATEWAY_LIVE_MXACCESS_WRITE_SECURED_USER` | `admin` | ArchestrA user name passed to `AuthenticateUser` before the `WriteSecured` parity step. |
|
||||||
| `MXGATEWAY_LIVE_MXACCESS_WRITE_SECURED_PASSWORD` | `admin123` | Password paired with the user above. Never logged; the test asserts the value does not appear in the WriteSecured diagnostic message. |
|
| `MXGATEWAY_LIVE_MXACCESS_WRITE_SECURED_PASSWORD` | `password` | Password paired with the user above (matches the shared GLAuth `admin` user's dev password — see `glauth.md`). Never logged; the test asserts the value does not appear in the WriteSecured diagnostic message. |
|
||||||
|
|
||||||
The test output includes session id, worker process id, command status,
|
The test output includes session id, worker process id, command status,
|
||||||
HRESULT/status diagnostics, event sequence and handles, close status, and worker
|
HRESULT/status diagnostics, event sequence and handles, close status, and worker
|
||||||
|
|||||||
@@ -52,31 +52,46 @@ 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=zb,dc=local` | Browse + read OPC UA nodes | `Browse` + `Subscribe` (read paths only) |
|
| ReadOnly | 5601 | `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) |
|
| WriteOperate | 5602 | `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) |
|
| WriteTune | 5603 | `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) |
|
| WriteConfigure | 5604 | `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 |
|
| AlarmAck | 5605 | `ou=AlarmAck,ou=groups,dc=zb,dc=local` | Acknowledge alarms | gw alarm-ack RPC, when added |
|
||||||
|
|
||||||
|
These are the shared config's OPC-perm (`560x`) family — see
|
||||||
|
`scadaproj/infra/glauth/README.md`'s directory-layout table, which
|
||||||
|
partitions groups into non-overlapping gid ranges per sister app
|
||||||
|
(`SCADA-*` 55xx, OPC-perm 560x, `Gw*` 561x, `OtOpcUa-*` 57xx) so the
|
||||||
|
three apps coexist in one directory.
|
||||||
|
|
||||||
**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. `multi-role` is the canonical example (in every
|
||||||
group below).
|
group in the file, not just the ones below).
|
||||||
|
|
||||||
## Pre-provisioned users
|
## Pre-provisioned users
|
||||||
|
|
||||||
|
Per-role OPC-perm testers are named `opc-*` rather than the bare role name
|
||||||
|
(`readonly`, `writeop`, …) because a tester username must not
|
||||||
|
case-collide with a group name — see the naming rule in
|
||||||
|
`scadaproj/infra/glauth/README.md`.
|
||||||
|
|
||||||
| Username | Password | UID | Primary group | Other groups | Capabilities |
|
| Username | Password | UID | Primary group | Other groups | Capabilities |
|
||||||
|---|---|---|---|---|---|
|
|---|---|---|---|---|---|
|
||||||
| `readonly` | `readonly123` | 5001 | ReadOnly | — | Browse, read |
|
| `opc-readonly` | `password` | 5101 | ReadOnly | — | Browse, read |
|
||||||
| `writeop` | `writeop123` | 5002 | WriteOperate | — | + plain Write |
|
| `opc-writeop` | `password` | 5102 | WriteOperate | — | + plain Write |
|
||||||
| `writetune` | `writetune123` | 5005 | WriteTune | — | + WriteSecured (Tune) |
|
| `opc-writetune` | `password` | 5103 | WriteTune | — | + WriteSecured (Tune) |
|
||||||
| `writeconfig` | `writeconfig123` | 5006 | WriteConfigure | — | + WriteSecured (Configure) |
|
| `opc-writeconfig` | `password` | 5104 | WriteConfigure | — | + WriteSecured (Configure) |
|
||||||
| `alarmack` | `alarmack123` | 5003 | AlarmAck | — | Alarm acknowledgment |
|
| `opc-alarmack` | `password` | 5105 | AlarmAck | — | Alarm acknowledgment |
|
||||||
| `admin` | `admin123` | 5004 | ReadOnly | WriteOperate, AlarmAck, WriteTune, WriteConfigure | All roles |
|
| `multi-role` | `password` | 5005 | SCADA-Admins | every other group in the file, incl. all five OPC-perm groups + `GwAdmin`/`GwReader` | All roles, all apps (cross-app canonical login) |
|
||||||
|
| `admin` | `password` | 5001 | SCADA-Admins | `GwAdmin`, `OtOpcUa-Admins` | Dashboard Administrator + ScadaBridge/OtOpcUa admin — **not** a member of any OPC-perm group, so it has no gw-side Write/WriteSecured/AlarmAck capability |
|
||||||
| `serviceaccount` | `<service-account-password>` | 5999 | ReadOnly | — | LDAP search capability (for bind-then-search) |
|
| `serviceaccount` | `<service-account-password>` | 5999 | ReadOnly | — | LDAP search capability (for bind-then-search) |
|
||||||
|
|
||||||
For mxaccessgw dev, `admin` covers every gw-side capability test;
|
For mxaccessgw dev, `multi-role` covers every gw-side capability test
|
||||||
`readonly` is the right "negative" case for proving Browse-OK /
|
(it is a member of all five OPC-perm groups as well as `GwAdmin`);
|
||||||
Write-denied.
|
`opc-readonly` is the right "negative" case for proving Browse-OK /
|
||||||
|
Write-denied. `admin` is useful for dashboard-Administrator and
|
||||||
|
ScadaBridge/OtOpcUa-admin testing but, per the table above, carries no
|
||||||
|
OPC-perm group membership of its own.
|
||||||
|
|
||||||
The gateway dashboard uses two gateway-specific groups beyond the LmxOpcUa taxonomy:
|
The gateway dashboard uses two gateway-specific groups beyond the LmxOpcUa taxonomy:
|
||||||
`GwAdmin` (gid 5610 → role `Administrator`) and `GwReader` (gid 5611 → role `Viewer`).
|
`GwAdmin` (gid 5610 → role `Administrator`) and `GwReader` (gid 5611 → role `Viewer`).
|
||||||
@@ -121,7 +136,7 @@ principal cannot show that.
|
|||||||
|
|
||||||
```
|
```
|
||||||
DN: cn=admin,dc=zb,dc=local
|
DN: cn=admin,dc=zb,dc=local
|
||||||
Password: admin123
|
Password: password
|
||||||
```
|
```
|
||||||
|
|
||||||
Construct the DN from the username; bind. Works on GLAuth because
|
Construct the DN from the username; bind. Works on GLAuth because
|
||||||
|
|||||||
@@ -514,21 +514,37 @@ public sealed class WorkerLiveMxAccessSmokeTests(ITestOutputHelper output)
|
|||||||
// AuthenticateUser resolves an ArchestrA user id for the WriteSecured call.
|
// AuthenticateUser resolves an ArchestrA user id for the WriteSecured call.
|
||||||
// Credentials are env-overridable so the test honors the gateway's "do not
|
// Credentials are env-overridable so the test honors the gateway's "do not
|
||||||
// log secrets" rule and works against either MXAccess's own user store or
|
// log secrets" rule and works against either MXAccess's own user store or
|
||||||
// the LmxOpcUa-baseline GLAuth-bridged ArchestrA identity (admin/admin123).
|
// the LmxOpcUa-baseline GLAuth-bridged ArchestrA identity (admin/password —
|
||||||
|
// see glauth.md; the shared GLAuth's dev-user password is "password", not
|
||||||
|
// the old per-box "admin123").
|
||||||
MxCommandReply authReply = await fixture.Service.Invoke(
|
MxCommandReply authReply = await fixture.Service.Invoke(
|
||||||
CreateAuthenticateUserRequest(sessionId, serverHandle, verifyUser, verifyPassword),
|
CreateAuthenticateUserRequest(sessionId, serverHandle, verifyUser, verifyPassword),
|
||||||
new TestServerCallContext()).ConfigureAwait(false);
|
new TestServerCallContext()).ConfigureAwait(false);
|
||||||
recordedOutput.WriteLine(
|
|
||||||
$"AuthenticateUser status={authReply.ProtocolStatus.Code} hresult={authReply.Hresult} user_id={authReply.AuthenticateUser?.UserId}");
|
|
||||||
|
|
||||||
// AuthenticateUser is allowed to fail (the underlying provider may reject
|
// AuthenticateUser is allowed to fail — MXAccess parity means a rejected
|
||||||
// the credential pair); we use the returned user id if non-zero and fall
|
// credential is a legitimate reply, not a bug, so this must never be an
|
||||||
// back to 0 ("operator only" / no verifier) so the parity assertion holds.
|
// Assert. But a silent fall-through to "operator only" (currentUserId 0)
|
||||||
int currentUserId = authReply.ProtocolStatus.Code == ProtocolStatusCode.Ok
|
// quietly demotes the rest of this test from an authenticated round-trip
|
||||||
|
// to a bare failure-reply-shape check, which a stale rig credential (e.g.
|
||||||
|
// TST-31) would otherwise hide indefinitely in nightly runs. Log the
|
||||||
|
// outcome loudly — never the password (verifyUser only) — so that
|
||||||
|
// degradation is visible in the test output even though it never fails
|
||||||
|
// the test by itself.
|
||||||
|
bool authenticateUserSucceeded = authReply.ProtocolStatus.Code == ProtocolStatusCode.Ok
|
||||||
&& authReply.AuthenticateUser is not null
|
&& authReply.AuthenticateUser is not null
|
||||||
&& authReply.AuthenticateUser.UserId != 0
|
&& authReply.AuthenticateUser.UserId != 0;
|
||||||
? authReply.AuthenticateUser.UserId
|
recordedOutput.WriteLine(
|
||||||
: 0;
|
authenticateUserSucceeded
|
||||||
|
? $"AuthenticateUser SUCCEEDED for user={verifyUser} user_id={authReply.AuthenticateUser!.UserId}"
|
||||||
|
: $"AuthenticateUser FAILED (credential rejected or not recognized by the provider) "
|
||||||
|
+ $"for user={verifyUser} status={authReply.ProtocolStatus.Code} hresult={authReply.Hresult} "
|
||||||
|
+ $"diagnostic={authReply.DiagnosticMessage} -- WriteSecured below will run as an "
|
||||||
|
+ $"unauthenticated (currentUserId=0) failure-reply-shape check only, not an "
|
||||||
|
+ $"authenticated round-trip. If this is unexpected, verify "
|
||||||
|
+ $"MXGATEWAY_LIVE_MXACCESS_WRITE_SECURED_USER/_PASSWORD against the rig's ArchestrA "
|
||||||
|
+ $"identity (see glauth.md).");
|
||||||
|
|
||||||
|
int currentUserId = authenticateUserSucceeded ? authReply.AuthenticateUser!.UserId : 0;
|
||||||
|
|
||||||
MxCommandReply writeSecuredReply = await fixture.Service.Invoke(
|
MxCommandReply writeSecuredReply = await fixture.Service.Invoke(
|
||||||
CreateWriteSecuredRequest(
|
CreateWriteSecuredRequest(
|
||||||
@@ -1429,7 +1445,7 @@ public sealed class WorkerLiveMxAccessSmokeTests(ITestOutputHelper output)
|
|||||||
string verifyUser = Environment.GetEnvironmentVariable("MXGATEWAY_LIVE_MXACCESS_WRITE_SECURED_USER")
|
string verifyUser = Environment.GetEnvironmentVariable("MXGATEWAY_LIVE_MXACCESS_WRITE_SECURED_USER")
|
||||||
?? "admin";
|
?? "admin";
|
||||||
string verifyPassword = Environment.GetEnvironmentVariable("MXGATEWAY_LIVE_MXACCESS_WRITE_SECURED_PASSWORD")
|
string verifyPassword = Environment.GetEnvironmentVariable("MXGATEWAY_LIVE_MXACCESS_WRITE_SECURED_PASSWORD")
|
||||||
?? "admin123";
|
?? "password";
|
||||||
return (verifyUser, verifyPassword);
|
return (verifyUser, verifyPassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user