docs(security): credential-reference namespaces + migration note (#815)
docs/security.md gains the canonical table of per-driver namespaces, the five
enforcement seams, the out-of-scope cases (appsettings ${secret:}, Sql
connectionStringRef) and the migration steps for existing configs. Galaxy,
OpcUaClient, Heidenhain, Configuration and driver-specs docs, the Galaxy
options XML docs, the drivers rule file and the docker-dev compose comment
point at it. docker-dev's env:GALAXY_MXGW_API_KEY stays valid (allowed legacy
prefix), so no rig change is needed.
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1144,6 +1144,67 @@ 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` (driver +
|
||||
browser) and `HeidenhainCredentials` all resolve through `CredentialReferences`, which re-checks the scope
|
||||
**before** any secret-store or environment read.
|
||||
|
||||
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).** After the upgrade, a stored config with an out-of-namespace reference fails
|
||||
its next deploy (`CredentialReferenceOutOfScope`), its Test Connect / browse, and — for a running driver — its
|
||||
next (re)initialise, which faults the driver with the same message. To migrate:
|
||||
|
||||
- **Galaxy API key** — `env:GALAXY_MXGW_API_KEY` (the docker-dev rig and docs' name) keeps working. Any other
|
||||
variable: rename it to `OTOPCUA_GALAXY_<NAME>` on every node that runs the driver *and* the admin nodes
|
||||
(Test Connect / browse), and set `env:OTOPCUA_GALAXY_<NAME>`. A store secret already named `galaxy/…` keeps
|
||||
working; otherwise re-create it as `galaxy/<inst>/apikey` (`/admin/secrets` or the `secret` CLI). A
|
||||
`file:<path>` key must move into the store or a namespaced variable.
|
||||
- **Galaxy fallback ack password** — the documented `secret:otopcua/galaxy/…` name keeps working; `env:` / `file:`
|
||||
references move as above.
|
||||
- **OpcUaClient** — re-create `secret:<name>` as `opcuaclient/<name>` (and update the field), or move an
|
||||
environment value to `OTOPCUA_OPCUACLIENT_<NAME>`. Literal passwords are unaffected.
|
||||
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user