Shared design (two resolution layers, wiring template, master-key/clustering fork) + per-repo executable plans with code-verified anchors and co-located .tasks.json: - mxaccessgw (G-4/G-5/G-6, 8 tasks) - otopcua (G-2/G-4/G-5/G-6, 10 tasks, 2 slices) - scadabridge (G-3/G-4/G-5/G-6, 10 tasks, 2 slices) Linked from components/secrets/GAPS.md.
5.4 KiB
Secrets — GAPS (adoption backlog)
The delta between each project's current-state and the
SPEC, as prioritized work. The library itself is built + published +
reference-consumer-proven; everything here is per-app adoption of an existing lib (not lib
construction).
Design + implementation plans (2026-07-16)
G-2 … G-6 are now planned. Shared design + three per-repo executable plans (task-metadata'd,
code-verified anchors, co-located .tasks.json):
docs/plans/2026-07-16-secrets-adoption-design.md— shared design: library API, the two resolution layers (pre-host${secret:}config expander vs runtimeISecretResolver), wiring template, master-key/clustering fork.docs/plans/2026-07-16-secrets-adoption-otopcua.md— G-2 + G-4 + G-5 + G-6 (10 tasks, 2 slices; Layer-A config + Layer-B driver secrets).docs/plans/2026-07-16-secrets-adoption-scadabridge.md— G-3 + G-4 + G-5 + G-6 (10 tasks; incl. claim-type verification + committed-plaintext cleanup).docs/plans/2026-07-16-secrets-adoption-mxaccessgw.md— G-4 + G-5 + G-6 (8 tasks; single-box, no Layer B).
G-7/G-8 remain design-only/deferred (below).
Cross-cutting observations
- No app has
${secret:}resolution today. All three assemble config with the stockAddJsonFile+AddEnvironmentVariableschain and read secrets verbatim. Two already lean on the pattern the expander formalizes: ScadaBridge's non-functional${SCADABRIDGE_*}placeholders (whole-key env override) and OtOpcUa's driver-levelGalaxySecretRef(env:/file:/dev:/literal). These are the natural first swaps. - The common high-value gap is plaintext credentials in appsettings/env: LDAP service-account passwords (all three), SQL connection-string passwords (OtOpcUa ConfigDb, ScadaBridge ConfigDb + MachineDataDb), JWT/HS256 signing keys (OtOpcUa, ScadaBridge), and API-key peppers (all three, runtime-supplied but plaintext).
- Secrets already stored in a DB in plaintext are the sharpest edge: OtOpcUa's OpcUaClient
Password/UserCertificatePasswordand thedev:/literal Galaxy API key live cleartext in the central config DB; ScadaBridge's MxGateway per-endpointApiKeylives plaintext in the ConfigDbDataConnectionsJSON. These leak at rest to anyone with DB read. - Existing at-rest crypto is Data Protection, and only ScadaBridge applies it to secrets
(4 encrypted ConfigDb columns via
EncryptedStringConverter). OtOpcUa/mxaccessgw use Data Protection only for cookies/tokens. None use DPAPI orProtectKeysWith*; key rings are unencrypted-at-rest.ZB.MOM.WW.Secretsadds envelope encryption with a KEK held outside the store — a stronger boundary than "the DB is the only protection." - Peppered-HMAC API-key stores stay bespoke (mxaccessgw + ScadaBridge): they are hashed, not reversibly stored — already correct. Secrets only takes over the pepper value supply.
Backlog (prioritized)
G-1 — HistorianGateway reference consumer ✅ DONE
Adopted + live-proven 2026-07-16 (historian password via ${secret:}, authenticated read
against the real wonder historian). This is the wiring template for the three apps.
G-2 — OtOpcUa: cleartext-in-DB driver secrets (highest value)
Add a secret: arm to GalaxySecretRef (its own comment anticipates this) and to the
OpcUaClient driver options, resolving through ISecretResolver — retire the dev:/literal
and plaintext Password/UserCertificatePassword DB paths.
G-3 — ScadaBridge: MxGateway ApiKey plaintext-in-ConfigDb
Resolve via ISecretResolver or extend the existing EncryptedStringConverter to that JSON
column.
G-4 — Pre-host ${secret:} for plaintext config secrets (all three)
LDAP passwords, SQL connstr passwords, JWT signing keys, deploy API key (OtOpcUa),
peppers — swap to ${secret:…} tokens, expander running before each app's existing
validator (ConfigPreflight / Ldap+OpcUa validators / GatewayOptionsValidator).
Delete committed dev plaintext and the loose *_login.txt files (ScadaBridge).
G-5 — Master-key provider per deployment
Env (ZB_SECRETS_MASTER_KEY) for containers; DPAPI for the Windows boxes; File (shared
mounted key) for clustered pairs (hard requirement — same KEK on every node).
G-6 — Mount /admin/secrets UI (all three)
Add the RCL page to each dashboard (OtOpcUa AdminUI, MxGateway Server, ScadaBridge CentralUI)
and map each app's admin role onto secrets:manage / secrets:reveal.
G-7 — Clustered replication (ScadaBridge, OtOpcUa) — build ZB.MOM.WW.Secrets.Akka
Deferred until G-2…G-6 land for a clustered app. Options per SPEC: shared SQL-Server
ISecretStore (simplest — mirrors the shared Data-Protection key ring) or the Akka
replicator (LWW, anti-entropy resync, tombstones). Requires a shared KEK.
G-8 — KEK-rotation runbook + RewrapAll
The RewrapAll(oldKek, newKek) admin primitive + an operator runbook. Deferred (design-only
in this cut).
Out of scope (this component)
- Akka remoting authentication/TLS (ScadaBridge plain TCP remoting) — a separate hardening concern, not secret storage.
- SQL-Server
ISecretStoreprovider construction (seam supports it; build with G-7).