docs: site secrets ride the artifact deploy; gRPC hub retired in ScadaBridge

This commit is contained in:
Joseph Doherty
2026-09-28 20:22:45 -04:00
parent bc29dc412b
commit 0ccf68951d
8 changed files with 259 additions and 6 deletions
+52
View File
@@ -249,6 +249,58 @@ reporting that it is a standby. The verdict remains fully observable through the
---
## Secrets (2026-09-28)
The Host composes `ZB.MOM.WW.Secrets` (a `scadaproj` shared library, not a ScadaBridge component —
hence it has no row in the Component Registration Matrix below) via `SecretsRegistration.AddScadaBridgeSecrets`,
called on both roles from their respective composition-root `Configure` methods. Design:
`docs/plans/2026-09-28-site-secrets-artifact-deploy-design.md` (see its *Implementation notes (as
built)* section for where the shipped code deviates from the original design).
- **Site role — always a local, undecorated store.** A site node's `ISecretStore`/`ISecretResolver`
is unconditionally the plain node-local SQLite store (`AddZbSecrets`), regardless of any
`Secrets:Store:Provider` value — a Site never reads that key. This is a hard invariant, not
just a default: `DeployedSecretsMirror` (Component-SiteRuntime.md) calls `ISecretStore.DeleteAsync`
directly on the assumption that a delete is node-local; a replication-decorated store would have
published that delete fleet-wide. Secrets reach a site only via Deploy Artifacts (see
Component-DeploymentManager.md → *Site secrets*) — there is no other channel.
- **Central role — `Secrets:Store:Provider`** (`Sqlite` default | `SqlServer`), read **only** on the
Central role:
- `Sqlite` (default): each central node keeps its own per-node SQLite secret store. On a
**clustered** central (≥ 2 seed nodes) this is a documented misconfiguration — `StartupValidator`
logs a boot-time Warning (same shape as the inbound-API-key-store precedent, register row 108),
because the two nodes would deploy different secret sets to sites depending on which is active.
- `SqlServer`: both central nodes read and write **one shared SQL Server store**
(`Secrets:SqlServer:ConnectionString`, a literal/env value — never a `${secret:}` reference,
the same bootstrap-circularity rule the inbound-API-key shared store follows) — the `/admin/secrets`
page edits it, and it is the store `SiteSecretsCollector` resolves from. The pre-host `${secret:}`
expander (the Layer-A pass that resolves `${secret:X}` tokens inside `appsettings` itself, before
the DI container is built) reads the **same** shared store when `Provider = SqlServer`, so a
central node's boot-time config tokens and its running `ISecretResolver` never disagree about
what a token resolves to.
- A connection string that is blank, or is itself a `${secret:}` reference (a bootstrap circularity),
fails fast naming the offending key.
- **Bootstrap secrets are never deployed.** The site's gRPC PSK (`SB-GRPC-PSK-{siteId}`), the LocalDb
replication key, and the secrets master key (KEK) are never data-connection-referenced and are
never shipped through Deploy Artifacts — a site needs them *before* it can receive a deploy at all,
and pushing a rotated one through the channel it authenticates would sever that channel. They stay
out-of-band: service environment variables, a hand-seeded store, or a master-key file, exactly as
before this feature. Every node — every central and every site — **must share the same KEK**;
see `docs/deployment/topology-guide.md` → *Secrets: shared store, per-site deploy, and the KEK*
for where that is (and is not) checked.
- **The gRPC secrets hub is retired in ScadaBridge (2026-09-28).** ScadaBridge no longer composes
`ZB.MOM.WW.Secrets.Replicator.Grpc` on either role, and the historical SQL-Server *replication*
mode (`Secrets:Replication:Mode=SqlServer` — distinct from `Secrets:Store:Provider=SqlServer`
above, which shares one store rather than replicating between two) is retired too. Setting
`Secrets:Replication:Enabled` to anything other than absent/empty/`false` now fails
`StartupValidator.Validate` outright, on **either** role, with a message pointing at Deploy
Artifacts (sites) and `Secrets:Store:Provider=SqlServer` (a clustered central). A leftover
`Secrets:Replication:Mode` or any `Secrets:GrpcHub:*` key (harmless once `Enabled` is unset, but
dead configuration) logs a startup Warning naming both sections so they can be cleaned up. The
library itself keeps the hub for other consumers; only ScadaBridge stops using it.
---
## Component Registration Matrix
| Component | Central | Site | DI (`AddXxx`) | Actors (`AddXxxActors`) | Endpoints (`MapXxx`) |