feat(galaxy): operator-authenticated alarm ack — send Galaxy credential to mxaccessgw (#749)
v2-ci / unit-tests (push) Failing after 1h8m43s
v2-ci / unit-tests (push) Failing after 1h8m43s
mxaccessgw 488649c+ acknowledges by AuthenticateUser as the operator and
writing <alarm>.AckMsg, so AcknowledgeAlarmRequest now needs a real Galaxy
operator_user + operator_domain + operator_password. The OPC UA data plane is
API-key/anonymous and carries no Galaxy password, so the driver resolves the
account per ack:
1. the session's API key's Galaxy ack identity, set on AdminUI /apikeys and
held ONLY in the encrypted Secrets store
(otopcua/apikeys/<keyId>/galaxy-ack-{user,password}); a key with a user but
no password secret is refused, never silently downgraded;
2. else the driver instance's AlarmAck fallback account (DriverConfig
FallbackUser/FallbackDomain/FallbackPasswordSecretRef; secret:/${secret:}/
env:/file:/dev: only — a literal is refused);
3. else DriverOperationRefusedException: nothing is sent, never an empty
operator_user. Refusals are excluded from the resilience retry/breaker.
- Pins ZB.MOM.WW.MxGateway.Client/Contracts 0.2.1 -> 0.3.0 (vendored into
nuget-packages/ from the Gitea feed, per the local-mxgw source mapping).
- The session ApiKeyId (not a secret) rides NativeAlarmAck ->
RouteNativeAlarmAck -> RouteAlarmAck -> AlarmAcknowledgeRequest; the password
is resolved in the driver and never travels through actor messages.
- The OPC UA principal + key id are appended to the AckMsg comment
("[OPC UA: <principal>, key <id>]") and logged, so a shared fallback
account does not lose who acknowledged.
- Password never logged: GalaxyAckCredential.ToString omits it, the
acknowledger scrubs it from every gateway message it echoes and withholds
any exception that echoes it; audit rows carry only the key id.
- AdminUI: /apikeys "Ack identity" editor (set/remove; delete-key clears it),
Galaxy driver form gains the fallback section.
- Synchronous OPC UA status for a refusal is follow-up #760 (the native ack
path is fire-and-forget after the Part 9 method returns).
Tests: request mapping (password populated), password absent from logs and
exception text (mutation-checked), key-mapped / fallback / refused paths at
resolver and driver level, breaker non-trip, key-id propagation through node
manager and DriverHostActor, AdminUI service + form round-trip.
Closes #749
This commit is contained in:
@@ -85,6 +85,42 @@ Project root files:
|
||||
- **`MxAccess`** — `ClientName` (must be unique per OtOpcUa instance — redundancy pairs enforce uniqueness at install time), `PublishingIntervalMs` (forwarded as `buffered_update_interval_ms` on subscribe), `WriteUserId` for ArchestrA secured-write, `EventPumpChannelCapacity` (default 50_000 — one second of headroom at 50k tags / 1Hz; tune via the `galaxy.events.dropped` metric).
|
||||
- **`Repository`** — `DiscoverPageSize`, `WatchDeployEvents`.
|
||||
- **`Reconnect`** — `InitialBackoffMs`, `MaxBackoffMs`, `ReplayOnSessionLost` (calls the gateway's `ReplaySubscriptions` RPC after reconnect rather than re-issuing subscribe-bulk for every tag).
|
||||
- **`AlarmAck`** (optional, lmxopcua#749) — `FallbackUser`, `FallbackDomain`, `FallbackPasswordSecretRef`. See [Alarm acknowledge credential](#alarm-acknowledge-credential).
|
||||
|
||||
### Alarm acknowledge credential
|
||||
|
||||
mxaccessgw `488649c`+ (client/contracts `0.3.0`) acknowledges a native alarm by running
|
||||
`AuthenticateUser` as the operator and writing the comment to `<alarm>.AckMsg`, so the Historian
|
||||
`ACK_ALM` row records a real Galaxy account. `AcknowledgeAlarmRequest` therefore carries
|
||||
`operator_user` + `operator_domain` + `operator_password`, and the gateway rejects an empty
|
||||
`operator_user`. The OPC UA data plane is machine-to-machine (API key or anonymous) and carries no
|
||||
Galaxy password, so `GalaxyAckCredentialResolver` picks the account per acknowledge:
|
||||
|
||||
1. **The API key's own Galaxy ack identity** — set on AdminUI `/apikeys` ("Ack identity"). Stored only
|
||||
in the encrypted Secrets store as `otopcua/apikeys/<keyId>/galaxy-ack-user` (`DOMAIN\user` or
|
||||
`user`) and `otopcua/apikeys/<keyId>/galaxy-ack-password` (`GalaxyAckIdentitySecrets`) — never in
|
||||
the config DB or the deployment artifact. A key with a user but no password secret is **refused**,
|
||||
not silently downgraded to the fallback.
|
||||
2. **The driver's fallback account** — `AlarmAck.FallbackUser` / `FallbackDomain` /
|
||||
`FallbackPasswordSecretRef` in `DriverConfig`, used by keys without an identity and by anonymous
|
||||
sessions. The password ref must be `secret:NAME` / `${secret:NAME}` (production), `env:NAME`,
|
||||
`file:PATH` or `dev:VALUE`; a bare literal is refused so a password never sits in DriverConfig.
|
||||
3. **Neither** → the driver throws `DriverOperationRefusedException`; **nothing is sent to the
|
||||
gateway**. The refusal is excluded from the resilience breaker (it says nothing about gateway
|
||||
health). ⚠️ The native ack path is fire-and-forget after the Part 9 method returns, so the OPC UA
|
||||
call itself still returns `Good` and the refusal surfaces as a `DriverInstance` warning naming the
|
||||
fix — a synchronous status / revert is #760.
|
||||
|
||||
The comment written to `AckMsg` is suffixed with the OPC UA identity —
|
||||
`"<comment> [OPC UA: <principal>, key <keyId>]"` or `"[OPC UA: anonymous]"` — so the client is not
|
||||
lost when the Historian's Operator is a shared fallback account. The password is never logged: the
|
||||
acknowledger scrubs it from every gateway message it echoes and `GalaxyAckCredential.ToString()`
|
||||
omits it.
|
||||
|
||||
⚠️ **Secrets are node-local** (optionally Akka-replicated within one mesh). The per-key secrets and
|
||||
the fallback secret must exist on the node that serves the OPC UA session and hosts the Galaxy
|
||||
driver; on a split admin/driver topology create them on the driver nodes too (`/admin/secrets` or
|
||||
the `secret` CLI) under exactly those names.
|
||||
|
||||
Full per-field descriptions live in `Config/GalaxyDriverOptions.cs`. The full JSON skeleton is reproduced in [docs/v2/driver-specs.md §1](../v2/driver-specs.md).
|
||||
|
||||
|
||||
@@ -345,6 +345,14 @@ of a credential was wrong.
|
||||
ConfigDb, so keys reach them inside the deployment artifact exactly as `NodeAcl` rows do. Clearing
|
||||
`Enabled` takes effect on a node only once a new artifact reaches it.
|
||||
|
||||
**Galaxy alarm-ack identity per key (lmxopcua#749).** mxaccessgw acknowledges Galaxy alarms as a real
|
||||
Galaxy account, so a key can carry an optional Galaxy ack identity (AdminUI `/apikeys` → "Ack
|
||||
identity"). It is held only in the encrypted Secrets store
|
||||
(`otopcua/apikeys/<keyId>/galaxy-ack-{user,password}`), never in the ConfigDb or the artifact; keys
|
||||
without one and anonymous sessions use the Galaxy driver's `AlarmAck` fallback account, and with
|
||||
neither the ack is refused before it reaches the gateway. Deleting a key on `/apikeys` removes its
|
||||
identity. See [docs/drivers/Galaxy.md § Alarm acknowledge credential](drivers/Galaxy.md#alarm-acknowledge-credential).
|
||||
|
||||
### Anonymous is a normal principal
|
||||
|
||||
Anonymous sessions are **admitted by default** (`OpcUa:AllowAnonymous`, default `true` — flipping the
|
||||
|
||||
Reference in New Issue
Block a user