Galaxy: send operator credential in AcknowledgeAlarmRequest (mxaccessgw AckMsg-as-operator) #749

Closed
opened 2026-11-06 15:27:06 -05:00 by dohertj2 · 3 comments
Owner

mxaccessgw main 488649c (PR #162, 2026-10-20) changed native alarm acknowledge: the gateway now acknowledges by writing the comment to <alarm>.AckMsg after AuthenticateUser as the operator, so the Historian records the real operator. AcknowledgeAlarmRequest gained operator_password / operator_domain.

OtOpcUa pins ZB.MOM.WW.MxGateway.Client/Contracts 0.2.1 and nothing under src/ sets OperatorPassword (grep 2026-11-06), so GatewayGalaxyAlarmAcknowledger (src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy/Runtime/GatewayGalaxyAlarmAcknowledger.cs) sends no credential. Galaxy acks will not authenticate once a gateway at or after 488649c is deployed.

Work:

  • Bump the MxGateway client/contracts packages to a version that carries the new fields.
  • Decide where the credential comes from. The OPC UA data-plane principal is an API key or anonymous, so it has no operator password. A likely answer is a configured ack identity resolved through ${secret:}/Secrets, but that needs a decision.
  • Keep the password out of logs, metrics and diagnostics, as the gateway contract requires.
  • Live-gate an ack against windev/DEV.

This blocks mxaccessgw#170 (deploying the operator-authenticated ack to DEV/QA).

Source: scadaproj CLAUDE.md (MxAccessGateway row); mxaccessgw clients/rust/protos/mxaccess_gateway.proto (AcknowledgeAlarmCommand.operator_password). Related: #614 (H6 ack reaches AVEVA event history).

mxaccessgw main `488649c` (PR #162, 2026-10-20) changed native alarm acknowledge: the gateway now acknowledges by writing the comment to `<alarm>.AckMsg` after `AuthenticateUser` as the operator, so the Historian records the real operator. `AcknowledgeAlarmRequest` gained `operator_password` / `operator_domain`. OtOpcUa pins `ZB.MOM.WW.MxGateway.Client`/`Contracts` 0.2.1 and nothing under `src/` sets `OperatorPassword` (grep 2026-11-06), so `GatewayGalaxyAlarmAcknowledger` (`src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy/Runtime/GatewayGalaxyAlarmAcknowledger.cs`) sends no credential. Galaxy acks will not authenticate once a gateway at or after `488649c` is deployed. Work: - Bump the MxGateway client/contracts packages to a version that carries the new fields. - Decide where the credential comes from. The OPC UA data-plane principal is an API key or anonymous, so it has no operator password. A likely answer is a configured ack identity resolved through `${secret:}`/Secrets, but that needs a decision. - Keep the password out of logs, metrics and diagnostics, as the gateway contract requires. - Live-gate an ack against windev/DEV. This blocks mxaccessgw#170 (deploying the operator-authenticated ack to DEV/QA). Source: scadaproj `CLAUDE.md` (MxAccessGateway row); mxaccessgw `clients/rust/protos/mxaccess_gateway.proto` (`AcknowledgeAlarmCommand.operator_password`). Related: #614 (H6 ack reaches AVEVA event history).
dohertj2 added the priority:p1cross-repomigratedtype:followup labels 2026-11-06 15:28:00 -05:00
Author
Owner

Blocked on two things (2026-11-06 investigation, no code changed):

  1. Contracts not published — feed's newest MxGateway.Contracts/Client is 0.2.1 (no OperatorPassword); mxgw main still declares 0.2.1. Tracked as mxaccessgw#214.
  2. Credential source is a design decision — the OPC UA data-plane principal is an API key id or anonymous, with no Galaxy password. Options: (1) one configured ack account per Galaxy driver (${secret:}); (2) per-API-key Galaxy ack identity in AdminUI, with (1) as fallback or fail-closed (recommended); (3) operator Galaxy login over OPC UA (not recommended). Also: the new gateway rejects empty operator_user, and GalaxyDriver.cs:1935 sends string.Empty when no principal resolves, so anonymous acks will fail against 488649c+.
Blocked on two things (2026-11-06 investigation, no code changed): 1. **Contracts not published** — feed's newest MxGateway.Contracts/Client is 0.2.1 (no `OperatorPassword`); mxgw main still declares 0.2.1. Tracked as mxaccessgw#214. 2. **Credential source is a design decision** — the OPC UA data-plane principal is an API key id or anonymous, with no Galaxy password. Options: (1) one configured ack account per Galaxy driver (`${secret:}`); (2) per-API-key Galaxy ack identity in AdminUI, with (1) as fallback or fail-closed (recommended); (3) operator Galaxy login over OPC UA (not recommended). Also: the new gateway rejects empty `operator_user`, and `GalaxyDriver.cs:1935` sends `string.Empty` when no principal resolves, so anonymous acks will fail against 488649c+.
dohertj2 added the blocked label 2026-11-06 15:42:25 -05:00
Author
Owner

Decision (user, 2026-11-06): use a per-API-key Galaxy ack identity, mapped in AdminUI /api-keys with the password held in Secrets. Unmapped keys and anonymous sessions fall back to one configured ack account per Galaxy driver, also via ${secret:}. Never send an empty operator_user. Implementation waits for mxaccessgw#214 (Contracts/Client 0.3.0) to be published.

**Decision (user, 2026-11-06):** use a per-API-key Galaxy ack identity, mapped in AdminUI `/api-keys` with the password held in Secrets. Unmapped keys and anonymous sessions fall back to one configured ack account per Galaxy driver, also via `${secret:}`. Never send an empty `operator_user`. Implementation waits for mxaccessgw#214 (Contracts/Client 0.3.0) to be published.
dohertj2 removed the blocked label 2026-11-06 16:01:25 -05:00
Author
Owner

Landed on master as dc0a24f2 (fast-forward; the commit's Closes #749 closed this).

Design (option 2 + fallback, per the 2026-11-06 decision). For each ack the Galaxy driver resolves:

  1. The session's API key's Galaxy ack identity. It is set on AdminUI /apikeys → "Ack identity" and held only in the encrypted Secrets store, as otopcua/apikeys/<keyId>/galaxy-ack-user (DOMAIN\user) and otopcua/apikeys/<keyId>/galaxy-ack-password. A key that has a user but no password secret is refused, not downgraded to the fallback.
  2. Otherwise, the driver's AlarmAck fallback account (FallbackUser / FallbackDomain / FallbackPasswordSecretRef). The password ref must be secret: / ${secret:} / env: / file: / dev:; a literal is refused.
  3. Otherwise, DriverOperationRefusedException. Nothing is sent, and operator_user is never sent empty. Refusals don't count toward retry or the circuit breaker.

Other changes:

  • The OPC UA principal and key id are appended to the AckMsg comment ([OPC UA: <principal>, key <id>]) and logged.
  • The password is never logged. GalaxyAckCredential.ToString omits it, the acknowledger scrubs it from gateway echoes, and audit rows carry only the key id. Only the key id travels through the actor messages; the password is resolved inside the driver.
  • MxGateway Client/Contracts pins go from 0.2.1 to 0.3.0. The packages are vendored into nuget-packages/.

Tests (all green):

Project Passed Skipped
Driver.Galaxy 432 8
Driver.Galaxy.Browser 21 0
Core 360 1
Core.Abstractions 546 0
AdminUI 1890 0
Runtime 923 3
OpcUaServer 1019 1
Host 219 0

The new tests cover:

  • the request carrying the password;
  • the password being absent from logs and from exception text (mutation-checked: removing the scrub, or the password mapping, makes them fail);
  • the key-mapped, fallback and refused paths;
  • the breaker not tripping on refusals;
  • the key id reaching the driver through the node manager and DriverHostActor.

Not done:

  • No live ack against windev. The :5130 dashboard doesn't show its build without a login, so I couldn't confirm it is at or after mxaccessgw 488649c.
  • When an ack is refused, the OPC UA call still returns Good, because the native ack path is fire-and-forget. Follow-up: #760.

Deploy-time config: give each Galaxy driver an AlarmAck fallback (e.g. FallbackPasswordSecretRef: secret:otopcua/galaxy/ack-fallback-password) and create that secret, and/or map identities per key on /apikeys. Secrets are node-local, so on a split admin/driver topology they must also exist on the driver nodes. With neither in place, every Galaxy ack is refused.

Landed on `master` as **dc0a24f2** (fast-forward; the commit's `Closes #749` closed this). **Design (option 2 + fallback, per the 2026-11-06 decision).** For each ack the Galaxy driver resolves: 1. The session's API key's Galaxy ack identity. It is set on AdminUI `/apikeys` → "Ack identity" and held only in the encrypted Secrets store, as `otopcua/apikeys/<keyId>/galaxy-ack-user` (`DOMAIN\user`) and `otopcua/apikeys/<keyId>/galaxy-ack-password`. A key that has a user but no password secret is **refused**, not downgraded to the fallback. 2. Otherwise, the driver's `AlarmAck` fallback account (`FallbackUser` / `FallbackDomain` / `FallbackPasswordSecretRef`). The password ref must be `secret:` / `${secret:}` / `env:` / `file:` / `dev:`; a literal is refused. 3. Otherwise, `DriverOperationRefusedException`. Nothing is sent, and `operator_user` is never sent empty. Refusals don't count toward retry or the circuit breaker. Other changes: - The OPC UA principal and key id are appended to the AckMsg comment (`[OPC UA: <principal>, key <id>]`) and logged. - The password is never logged. `GalaxyAckCredential.ToString` omits it, the acknowledger scrubs it from gateway echoes, and audit rows carry only the key id. Only the key id travels through the actor messages; the password is resolved inside the driver. - MxGateway Client/Contracts pins go from 0.2.1 to **0.3.0**. The packages are vendored into `nuget-packages/`. **Tests (all green):** | Project | Passed | Skipped | |---|---|---| | Driver.Galaxy | 432 | 8 | | Driver.Galaxy.Browser | 21 | 0 | | Core | 360 | 1 | | Core.Abstractions | 546 | 0 | | AdminUI | 1890 | 0 | | Runtime | 923 | 3 | | OpcUaServer | 1019 | 1 | | Host | 219 | 0 | The new tests cover: - the request carrying the password; - the password being absent from logs and from exception text (mutation-checked: removing the scrub, or the password mapping, makes them fail); - the key-mapped, fallback and refused paths; - the breaker not tripping on refusals; - the key id reaching the driver through the node manager and DriverHostActor. **Not done:** - No live ack against windev. The `:5130` dashboard doesn't show its build without a login, so I couldn't confirm it is at or after mxaccessgw 488649c. - When an ack is refused, the OPC UA call still returns Good, because the native ack path is fire-and-forget. Follow-up: #760. **Deploy-time config:** give each Galaxy driver an `AlarmAck` fallback (e.g. `FallbackPasswordSecretRef: secret:otopcua/galaxy/ack-fallback-password`) and create that secret, and/or map identities per key on `/apikeys`. Secrets are node-local, so on a split admin/driver topology they must also exist on the driver nodes. With neither in place, every Galaxy ack is refused.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: dohertj2/lmxopcua#749