0d4978953f
SecretsGrpcHubClientOptions gains a getter-only FallbackEndpoints list:
additional hub addresses tried in order when a call fails with a
transport-level RpcException. Validation fails startup on blank/relative
entries and on any duplicate of the primary or another fallback (a
duplicated endpoint silently halves the configured failover).
New FailoverSecretsHubReader composes one GrpcSecretsHubClient per
endpoint: the answering endpoint is sticky (a sweep is GetManifest + N
GetSecrets batches — without stickiness every call after a primary
outage re-pays a full CallDeadline before reaching the healthy
fallback), rotation wraps so a recovered primary is retried when the
stuck-to endpoint fails, only RpcException triggers failover
(cancellation and defects propagate untouched), and when every endpoint
fails the last RpcException is rethrown so the sweep degrades to
last-known-good exactly as today. Failover warnings name only the two
endpoint URIs and the status code — never any credential material.
DI: zero fallbacks keeps the registration graph byte-identical to 0.4.x
(single keyed channel under ChannelServiceKey, plain
GrpcSecretsHubClient); with fallbacks, one container-owned keyed channel
per endpoint (fallback i under "{ChannelServiceKey}:fallback:{i}") wired
into the failover reader. GrpcSecretsHubClient itself is unchanged.
Safe only when every listed endpoint serves the SAME store — for
ScadaBridge, the central pair over one shared SQL Server store; the
options doc and CHANGELOG carry the warning.
Claude-Session: https://claude.ai/code/session_014WNM4vjoVksyyBraTXSZE1
6.7 KiB
6.7 KiB
Changelog
All notable changes to the ZB.MOM.WW.Secrets family are recorded here. Format loosely
follows Keep a Changelog; versions are shared across every
package in the family (see Directory.Build.props) even when a given release only touches
one of them.
0.5.0 — Unreleased
Added
ZB.MOM.WW.Secrets.Replicator.Grpc— hub endpoint failover for followers (scadaproj#4).SecretsGrpcHubClientOptionsgainsFallbackEndpoints: additional hub addresses tried, in order, when a call to the primaryEndpoint(or an earlier fallback) fails with a transport-level gRPC error. The answering endpoint is sticky — a sweep that failed over does not re-pay the dead endpoint's fullCallDeadlineon every subsequent call — and rotation wraps, so a recovered primary is retried the next time the stuck-to endpoint fails. OnlyRpcExceptiontriggers failover; cancellation and everything else propagate untouched, and when every endpoint fails the lastRpcExceptionis rethrown so the sweep degrades to last-known-good exactly as before. Each failover logs a Warning naming the failed and next endpoint URIs (never any credential material).- Same-store requirement: list only endpoints that serve the SAME store — for ScadaBridge, the central pair over one shared SQL Server store. Endpoints backed by independent stores turn a primary outage into a silent convergence stop (failing over to an emptier hub "succeeds"), which is the exact defect scadaproj#4 recorded.
- Validation fails startup on a blank/relative fallback or one duplicating the primary or another fallback (a duplicated endpoint silently halves the configured failover).
Unchanged
- With
FallbackEndpointsempty (the default), the registration graph is identical to 0.4.x — one keyed channel, oneGrpcSecretsHubClient, no failover wrapper — and the wire behavior of every call is byte-identical. Every other package in the family is behaviorally unchanged in this release.
0.4.1 — 2026-08-07
Fixed
ZB.MOM.WW.Secrets.Replicator.Grpc— the hub now logs its denials. A ScadaBridge live gate found thatSecretsHubAuthInterceptorrefused calls correctly but wrote nothing about it: the only trace of a refusal was a frameworkGrpc.AspNetCore.ServerInformation line, so a follower whose bearer token had been mis-rotated silently stopped converging and the hub showed it only at INF. Every denial is now counted, and aWarningsummarising the window is emitted on the first denial after startup or after 60 quiet seconds — carrying the number of denials since the previous summary plus a breakdown by cause (missing/malformed credential, wrong credential, hub token unconfigured). A single probe is therefore visible immediately, while a sustained attack is bounded to one log line per minute — the endpoint is unauthenticated by construction, so a per-denial warning would be an unbounded log write driven by an unauthenticated caller.
Unchanged
- Wire behavior is byte-identical. The cause breakdown is server-side diagnostics only; every
rejection still returns the same uniform
Unauthenticatedstatus with the same detail, and the credential comparison is still constant-time. No token material, presented or configured, appears in any log line at any level. - The existing warn-once-on-unconfigured-token line is unchanged and still emitted exactly once;
denials in that state also count toward the new summary's
unconfiguredcategory. - Every other package in the family is behaviorally unchanged in this release.
0.4.0 — 2026-08-07
Added
- New package
ZB.MOM.WW.Secrets.Replicator.Grpc— a central-hostedsecrets_hub.v1gRPC hub for the sites-talk-to-central production topology (scadaproj#3; design docdocs/plans/2026-08-07-secrets-production-topology-and-grpc-hub.md).- Pull-only by construction: the proto exposes no write RPC. Updates originate at central only; a site never pushes a secret upstream.
- Fail-closed bearer auth on the hub server: constant-time comparison, uniform denial response (no signal distinguishing "wrong token" from "missing token"), a warn-once log on an unconfigured/empty token rather than a silent open door.
GetSecretsenforces a batch-size cap, rejecting an oversized request withResourceExhaustedinstead of serving it partially or unbounded.- Site-side
GrpcSecretSyncServicesweeps the hub on an interval with per-row parse isolation — one malformed/undecryptable row logs and is skipped, it does not fault the whole sweep. - Keyed
GrpcChannelregistration so multiple hub targets can be wired in one process without channel collisions. - Offline behavior is last-known-good: if the hub is unreachable, the site keeps serving the most recently synced values rather than failing secret resolution.
Unchanged
ZB.MOM.WW.Secrets.Abstractions,ZB.MOM.WW.Secrets,ZB.MOM.WW.Secrets.Ui,ZB.MOM.WW.Secrets.Replicator.SqlServer, andZB.MOM.WW.Secrets.Replicator.AkkaDotNetare behaviorally unchanged in this release — no breaking changes to any existing package.
0.3.0 — 2026-07-19
- Menu-driven Spectre.Console interactive operator console added to the
secretCLI (list / set / reveal / delete, reference audit & seed, KEK doctor, ciphertext-only bundle export/import). Seedocs/operations/interactive-console.md.
0.2.3 — 2026-07-19
ZB.MOM.WW.Secrets.Ui: fixed the delete-confirmation modal being permanently invisible on every Bootstrap host (ConfirmDeleteModalused bareclass="modal"with no shipped CSS; Bootstrap's.modal{display:none}hid it everywhere but mxaccessgw). Fixed via azb-secrets-modal*class rename plus a component-emitted<style>block.
0.2.2 — 2026-07-18
- Fixed the Akka replicator's hosted-process DI deadlock (a circular singleton dependency
hidden behind factory lambdas that MS.DI's
StackGuardturned into a silent cross-thread hang rather than a stack overflow). NewDeferredSecretCacheInvalidatordefers the one cycle-closing edge to first eviction.
0.2.1 — 2026-07-18
- Fixed the Akka replicator being silently inert since 0.2.0 (a
TryAddregistration-order bug published into a no-op sink and never spawned an actor). SQL-Server replication was never affected.
0.2.0 — 2026-07-18
- Added clustered replication (G-7):
ZB.MOM.WW.Secrets.Replicator.SqlServer(shared store, or local-store-with-hub) andZB.MOM.WW.Secrets.Replicator.AkkaDotNet(peer-to-peer over distributed pub/sub).