|
|
|
@@ -1144,6 +1144,98 @@ Supporting hygiene, none of which is the control:
|
|
|
|
|
If a deployment needs the browse surface narrower than "anyone who may author config", the lever is the
|
|
|
|
|
page policy (and the LDAP group→role mapping behind it), not the browser.
|
|
|
|
|
|
|
|
|
|
#### Credential references are namespaced per driver (#815)
|
|
|
|
|
|
|
|
|
|
The outbound-connect capability above has a sharper edge whenever the config carries a **credential
|
|
|
|
|
reference**: a Designer authors both the endpoint *and* the reference, and the node resolves the reference
|
|
|
|
|
and sends the value to that endpoint — on Test Connect, on a live browse, and at runtime. Before #815 an
|
|
|
|
|
unscoped reference let anyone who may author config exfiltrate any value the admin node could read, with no
|
|
|
|
|
deploy: point a Galaxy `Gateway.Endpoint` at their own host, set `ApiKeySecretRef` to
|
|
|
|
|
`env:ConnectionStrings__ConfigDb` (or `env:ServerHistorian__ApiKey`, or `file:<any path>`), and click Test
|
|
|
|
|
Connect / browse; or set an OpcUaClient `Password` to `secret:<any store entry>`.
|
|
|
|
|
|
|
|
|
|
Every operator-authored reference is now confined to its driver's namespace — one table,
|
|
|
|
|
`DriverCredentialPolicy` (`Core.Abstractions`), one parser/resolver, `CredentialReferences`:
|
|
|
|
|
|
|
|
|
|
| Driver | Fields | Allowed references | Literal |
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
| `GalaxyMxGateway` | `Gateway.ApiKeySecretRef` | `secret:galaxy/<name>`, `secret:otopcua/galaxy/<name>`, `env:OTOPCUA_GALAXY_<NAME>`, legacy `env:GALAXY_MXGW_<NAME>` | allowed (warns), `dev:<key>` |
|
|
|
|
|
| `GalaxyMxGateway` | `AlarmAck.FallbackPasswordSecretRef` | same | `dev:<value>` only |
|
|
|
|
|
| `OpcUaClient` | `Password`, `UserCertificatePassword` | `secret:opcuaclient/<name>`, `env:OTOPCUA_OPCUACLIENT_<NAME>` | allowed |
|
|
|
|
|
| `Heidenhain` | `InspectPassword`, `DncPassword`, `DataPassword` | `secret:heidenhain/<name>`, `env:OTOPCUA_HEIDENHAIN_<NAME>` (#803) | refused |
|
|
|
|
|
|
|
|
|
|
Rules (all drivers): the prefixes are lower-case and exact (`SECRET:` / `Env:` are refused, never treated
|
|
|
|
|
as a literal); `${secret:<name>}` is accepted as a spelling of `secret:<name>`; secret names are
|
|
|
|
|
`[A-Za-z0-9._-]` segments with no empty, `.` or `..` segment; the variable suffix is `[A-Z0-9_]+`; **`file:` is
|
|
|
|
|
refused everywhere** (Galaxy used to read any file the node could open). A field is matched at any depth of
|
|
|
|
|
the DriverConfig / DeviceConfig, case-insensitively, as the drivers bind it.
|
|
|
|
|
|
|
|
|
|
Enforced, fail-closed, at every seam — the earlier ones exist for a clear message, the last one is the guarantee:
|
|
|
|
|
|
|
|
|
|
1. **AdminUI save** — `DriverConfigSaveGate` (`RawTreeService` driver/device create + update): the config is
|
|
|
|
|
never stored.
|
|
|
|
|
2. **Deploy gate** — `CredentialReferenceDeployChecks`, code **`CredentialReferenceOutOfScope`** (Heidenhain keeps
|
|
|
|
|
`HeidenhainCredentialPersisted`).
|
|
|
|
|
3. **Test Connect** — `AdminOperationsActor` refuses before any probe runs (`"Test Connect refused: …"`).
|
|
|
|
|
4. **Live browse** — `BrowserSessionService.OpenAsync` refuses before any bespoke or universal browser opens.
|
|
|
|
|
5. **Resolve time** — `GalaxySecretRef`, `GalaxyAckCredentialResolver`, `OpcUaUserIdentityFactory` and
|
|
|
|
|
`HeidenhainCredentials` all resolve through `CredentialReferences`, which re-checks the scope **before** any
|
|
|
|
|
secret-store or environment read. This is hard-fail on every admin-node path (the Galaxy and OpcUaClient
|
|
|
|
|
AdminUI browsers, every Test Connect probe) and everywhere for Heidenhain. The one exception is the deprecated
|
|
|
|
|
warn-only compatibility path for already-deployed drivers, below.
|
|
|
|
|
|
|
|
|
|
Messages name the field and the allowed forms, never the value. Out of scope by design: `${secret:}` tokens in
|
|
|
|
|
`appsettings.json` (host config, not operator-authored), and the `Sql` driver's `connectionStringRef`, which is
|
|
|
|
|
already a fixed prefix (`Sql__ConnectionStrings__<ref>`) whose value carries its own server — a Designer can
|
|
|
|
|
choose *which* provisioned database a ref names, but cannot redirect its credentials to another host.
|
|
|
|
|
|
|
|
|
|
**Migration (existing configs).** A stored config with an out-of-namespace reference is listed by the upgrade
|
|
|
|
|
preflight (below), refused by its next deploy (`CredentialReferenceOutOfScope` — the message names the
|
|
|
|
|
migration), and refused by Test Connect / live browse. Values most likely to be in real config databases, with
|
|
|
|
|
the exact replacement (create the new secret / variable first — on every node that runs the driver **and** on
|
|
|
|
|
the admin nodes, which run Test Connect and browse — then edit the field):
|
|
|
|
|
|
|
|
|
|
| Old value | Why it is refused | Replace with |
|
|
|
|
|
|---|---|---|
|
|
|
|
|
| Galaxy `env:MX_API_KEY` — the **old AdminUI Galaxy form default** (pre-#815 `GalaxyDriverForm.razor`), so the likeliest value in a real DB | variable outside the namespace | rename the variable to `OTOPCUA_GALAXY_API_KEY` and set `env:OTOPCUA_GALAXY_API_KEY` (or store it and use `secret:galaxy/<inst>/apikey`) |
|
|
|
|
|
| Galaxy `env:<ANY_OTHER_NAME>` (e.g. `env:MY_API_KEY`) | same | variable `OTOPCUA_GALAXY_<NAME>` → `env:OTOPCUA_GALAXY_<NAME>`. `env:GALAXY_MXGW_API_KEY` (docker-dev, parity docs) needs **no** change |
|
|
|
|
|
| Galaxy `file:<path>` — `docs/Configuration.md` used to recommend it for production | `file:` is refused everywhere | put the file's contents in the store as `galaxy/<inst>/apikey` → `secret:galaxy/<inst>/apikey` (or a variable `OTOPCUA_GALAXY_<NAME>`) |
|
|
|
|
|
| Galaxy `secret:<name>` not under `galaxy/` (e.g. the old `docs/v2/driver-specs.md` example `secret:galaxy-gw-api-key`) | secret outside the namespace | re-create it as `galaxy/<name>` (e.g. `galaxy/gw/apikey`) → `secret:galaxy/gw/apikey`. Names already under `galaxy/` or `otopcua/galaxy/` need no change |
|
|
|
|
|
| Galaxy `Env:X`, `SECRET:x`, `File:x`, … (any non-lower-case prefix) — **pre-#815 resolved these case-insensitively** | prefixes are now exact | the lower-case namespaced form from the rows above |
|
|
|
|
|
| Galaxy `AlarmAck.FallbackPasswordSecretRef` = `env:` / `file:` / `secret:` outside the namespace | same | as above; the documented `secret:otopcua/galaxy/<name>` needs no change |
|
|
|
|
|
| OpcUaClient `Password` / `UserCertificatePassword` = `secret:<name>` not under `opcuaclient/` (e.g. `secret:opcua/plant/pw`) | secret outside the namespace | re-create the secret as `opcuaclient/<name>` → `secret:opcuaclient/<name>` |
|
|
|
|
|
| OpcUaClient `SECRET:x` / `Secret:x` — pre-#815 resolved case-insensitively | prefixes are now exact | `secret:opcuaclient/<name>` |
|
|
|
|
|
| OpcUaClient **literal** password that happens to start with `env:`, `file:`, `secret:`/`${secret:` in any casing (e.g. a real password `Env:Prod1`) — pre-#815 sent it verbatim | it now parses as a reference | store it: `secret:opcuaclient/<name>` (a literal that does not start with one of those prefixes is unaffected) |
|
|
|
|
|
|
|
|
|
|
**Warn-only compatibility for already-deployed drivers (⚠️ deprecated — a later release fails closed).** Refusing
|
|
|
|
|
at resolve time would fault running plant drivers at the upgrade restart — and, for Galaxy, at **every gateway
|
|
|
|
|
reconnect**: `GalaxyDriver.ReopenAsync` (the `ReconnectSupervisor` callback) rebuilds the client options through
|
|
|
|
|
`BuildClientOptionsAsync`, which re-resolves `Gateway.ApiKeySecretRef` each time, so a running driver would fault
|
|
|
|
|
at its next reconnect, not at a deploy. So, for one compatibility window, the running-driver call sites — Galaxy
|
|
|
|
|
`InitializeAsync` / `ReopenAsync` / the ack-fallback resolver, and OpcUaClient session open — resolve an
|
|
|
|
|
out-of-namespace reference with the **old** semantics (Galaxy: case-insensitive `env:`/`file:`/`dev:`/`secret:`,
|
|
|
|
|
literal otherwise; OpcUaClient: case-insensitive `secret:`, literal otherwise) and log a Warning naming the
|
|
|
|
|
driver, field and allowed form (never the value), at most once per field per hour. The use also rides the
|
|
|
|
|
driver's diagnostics bag (`credential-compat:<field>`) to `/hosts` and to the `/raw` credential banner. This is
|
|
|
|
|
chosen **by call site**, not by a flag (`LegacyCredentialReferenceTracker`): the AdminUI browsers and every probe
|
|
|
|
|
never get the tracker and stay hard-fail, and Heidenhain (new driver, no legacy configs) is hard-fail everywhere.
|
|
|
|
|
It cannot be used to exfiltrate: save, Test Connect, browse and the deploy gate all refuse such a reference, so it
|
|
|
|
|
can only be the reference an administrator deployed **before** the upgrade, and it lives only until that driver's
|
|
|
|
|
next successful deploy (which requires migrating it). The hard-fail release deletes the tracker, the per-driver
|
|
|
|
|
`Legacy…` resolve methods and their `legacy` parameters — nothing else changes.
|
|
|
|
|
|
|
|
|
|
**Upgrade preflight.** On an admin node, `CredentialReferencePreflightService` runs once, 15 s after
|
|
|
|
|
`ApplicationStarted` (never inside an Akka starter, and it resolves nothing — ConfigDb reads only). It scans
|
|
|
|
|
every credential-resolving driver's stored `DriverConfig` / `DeviceConfig` rows (**Draft**) and the latest
|
|
|
|
|
deployment artifact (**Deployed** — what the running drivers were given) and logs one Warning per finding:
|
|
|
|
|
driver, device, field, allowed form, never the value. The same list, plus running drivers that reported a
|
|
|
|
|
warn-only resolution (**Running**, from driver-health diagnostics, fleet-wide), is shown as a banner on `/raw`,
|
|
|
|
|
with a Re-check button.
|
|
|
|
|
|
|
|
|
|
Residual (accepted): within one namespace, a Designer can still point one driver instance at another
|
|
|
|
|
instance's secret (e.g. `secret:galaxy/<other-inst>/apikey`) — the value then reaches the endpoint the Designer
|
|
|
|
|
chose. Per-instance scoping is a possible follow-up.
|
|
|
|
|
|
|
|
|
|
### Role grant source
|
|
|
|
|
|
|
|
|
|
Admin reads `LdapGroupRoleMapping` rows from the Config DB (`src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/LdapGroupRoleMapping.cs`) — the same pattern as the data-plane `NodeAcl` but scoped to Admin roles + (optionally) one cluster for multi-site fleets (a system-wide row, `IsSystemWide = true`, stacks additively with cluster-scoped rows). The `RoleGrants.razor` page lets `Administrator`s edit these mappings without leaving the UI.
|
|
|
|
|