125 lines
8.6 KiB
Markdown
125 lines
8.6 KiB
Markdown
# Secrets — GAPS (adoption backlog)
|
|
|
|
The delta between each project's [`current-state`](current-state/) and the
|
|
[`SPEC`](spec/SPEC.md), 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).
|
|
|
|
## Execution status (2026-07-16)
|
|
|
|
- **mxaccessgw — G-4 + G-5 + G-6 DONE.** Executed via the plan below (8 tasks, subagent-driven,
|
|
classification-driven reviews), **merged + pushed to `origin/main` (mxaccessgw @ `e088dfa`)**.
|
|
Offline: 0-warning build, full suite 781 pass / 44 pre-existing worker-COM (0 non-worker),
|
|
behavioral claim-type authz test, no Data-Protection disturbance. Box-verified on windev
|
|
(real .NET 10.0.8): fail-closed on unseeded `${secret:}` (`SecretNotFoundException`, refuses
|
|
to start) + CLI seed/`get` decrypt round-trip + **zero plaintext in the SQLite store at rest**.
|
|
(`/admin/secrets` interactive reveal not re-proven on this box — same shared `.Ui` component
|
|
already live-proven via HistorianGateway; box gateway runs LDAP-disabled/anonymous so a real
|
|
LDAP-via-secret login wasn't exercised there.) Two review-caught fixes folded in: the LDAP
|
|
password change's test/doc blast radius (validator-helper + host-test-bootstrap + doc truth),
|
|
and the live-LDAP integration suite now honors the `MxGateway__Ldap__ServiceAccountPassword`
|
|
env override. **G-4/G-5/G-6 below are the remaining OtOpcUa + ScadaBridge scope.**
|
|
- **ScadaBridge — G-3 + G-4 + G-5 + G-6 DONE.** Executed via the plan below (10 tasks, subagent-driven,
|
|
classification-driven reviews), **FF-merged + pushed to `origin/main` (ScadaBridge @ `128f1596`)**.
|
|
Offline: 0-warning build, all suites green (Host 279, DCL 235 incl. 5 G-3 resolver tests, Security 181,
|
|
CentralUI 925, InboundAPI 269, SiteRuntime 522/523 [1 pre-existing ScriptActor timing-flake, passes
|
|
isolated + branch touched no SiteRuntime src], Commons/ConfigDb/ManagementService/Communication/ClusterInfra
|
|
all green). **G-3 LIVE-PROVEN vs the REAL production MxGateway gateway** (`wonder-app-vd03.zmr.zimmer.com:5120`):
|
|
a throwaway harness drove the real `MxGatewayDataConnection` through the DI factory + `AddZbSecrets` with a
|
|
`secret:`-ref `ApiKey` → **dummy key `Unauthenticated`** (proves the resolved value is transmitted, not
|
|
fail-closed first) → **real read-scoped key → `Connected` + browsed the real Galaxy root (10 nodes)**. Temp
|
|
key created via the gateway dashboard, then **revoked + deleted + functionally confirmed dead**. Notable
|
|
deltas from the plan: T3 registers `AddZbSecrets` on **both** the Central branch (UI) and
|
|
`SiteServiceRegistration` (DCL adapter) — the plan assumed one container; T6 claim-type resolved **MATCH**
|
|
(`ZbClaimTypes.Role == ClaimTypes.Role`, adversarially verified) → `AddSecretsAuthorization()` used directly;
|
|
T8 (G-5) shipped as a **docs-only** production-posture runbook (`docs/operations/2026-07-16-secrets-clustered-master-key.md`)
|
|
because hardcoding `Source=File` + `/shared` paths would break the Central-boot Host.Tests + local dev (the
|
|
pre-host expander migrates the store unconditionally every Central boot). One review-flagged non-blocking
|
|
follow-up: a pre-existing xUnit env-mutation CI-flake that T4 amplifies (single `[Collection]` / disable
|
|
parallelization for Host.Tests).
|
|
- **OtOpcUa — planned, NOT executed (the LAST remaining app; recommended next — carries the highest-value G-2 Layer-B driver secrets).**
|
|
|
|
## 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`](../../docs/plans/2026-07-16-secrets-adoption-design.md)
|
|
— shared design: library API, the two resolution layers (pre-host `${secret:}` config
|
|
expander vs runtime `ISecretResolver`), wiring template, master-key/clustering fork.
|
|
- [`docs/plans/2026-07-16-secrets-adoption-otopcua.md`](../../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`](../../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`](../../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 stock
|
|
`AddJsonFile`+`AddEnvironmentVariables` chain 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-level `GalaxySecretRef`
|
|
(`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`/`UserCertificatePassword` and the `dev:`/literal Galaxy API key live cleartext in
|
|
the central config DB; ScadaBridge's MxGateway per-endpoint `ApiKey` lives plaintext in the
|
|
ConfigDb `DataConnections` JSON. 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 or `ProtectKeysWith*`; key rings are
|
|
unencrypted-at-rest. `ZB.MOM.WW.Secrets` adds 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 `ISecretStore` provider construction (seam supports it; build with G-7).
|