Files
scadaproj/ZB.MOM.WW.Secrets/CHANGELOG.md
T
Joseph Doherty c86cead5c2 fix(secrets): rate-limited denial warning on the Grpc hub (live-gate finding) — 0.4.1
The ScadaBridge live gate (check 4) found SecretsHubAuthInterceptor denying
correctly but logging nothing: the only trace of a refusal was a framework
Grpc.AspNetCore.Server Information line. A follower whose bearer token was
mis-rotated therefore stopped converging silently, with the hub showing it
only at INF.

Every denial is now counted and summarised in a Warning emitted on the first
denial after startup or after a 60-second quiet window, carrying the denials
since the previous summary plus a breakdown by cause. A single probe is
visible immediately; a sustained flood is bounded to one line per window,
which is what makes warning safe at all — the endpoint is unauthenticated by
construction, so a per-denial warning would be an unbounded log write driven
by an unauthenticated caller. Counters are Interlocked and the emit is
decided by a compare-exchange on the last-emit timestamp, so exactly one
thread emits per window and a denial counted after the drain carries into
the next summary rather than being lost.

The cause breakdown is server-side diagnostics only. Denied() is untouched:
every rejection still returns the identical uniform Unauthenticated status,
the comparison is still constant-time, and no token material appears in any
log line. The existing warn-once-on-unconfigured-token signal is unchanged
and still emitted exactly once; denials in that state count toward the new
summary's "unconfigured" category.

TimeProvider is an optional default-valued parameter rather than a second
overload — the type must keep exactly one public constructor or the gRPC
interceptor activator silently stops invoking the auth gate.

Two existing tests scoped their assertions to the configuration warning,
since both kinds now share a category: the new line is deliberate behavior,
not drift.

Claude-Session: https://claude.ai/code/session_014WNM4vjoVksyyBraTXSZE1
2026-08-07 08:26:49 -04:00

94 lines
5.0 KiB
Markdown

# Changelog
All notable changes to the `ZB.MOM.WW.Secrets` family are recorded here. Format loosely
follows [Keep a Changelog](https://keepachangelog.com/); versions are shared across every
package in the family (see `Directory.Build.props`) even when a given release only touches
one of them.
## 0.4.1 — 2026-08-07
### Fixed
- **`ZB.MOM.WW.Secrets.Replicator.Grpc` — the hub now logs its denials.** A ScadaBridge live
gate found that `SecretsHubAuthInterceptor` refused calls correctly but wrote nothing about
it: the only trace of a refusal was a framework `Grpc.AspNetCore.Server` Information 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 a `Warning` summarising 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 `Unauthenticated` status 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 `unconfigured` category.
- 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-hosted `secrets_hub.v1`
gRPC hub for the sites-talk-to-central production topology (scadaproj#3; design doc
[`docs/plans/2026-08-07-secrets-production-topology-and-grpc-hub.md`](../docs/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.
- `GetSecrets` enforces a batch-size cap, rejecting an oversized request with
`ResourceExhausted` instead of serving it partially or unbounded.
- Site-side `GrpcSecretSyncService` sweeps 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 `GrpcChannel` registration 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`, and `ZB.MOM.WW.Secrets.Replicator.AkkaDotNet` are
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 `secret` CLI (list /
set / reveal / delete, reference audit & seed, KEK doctor, ciphertext-only bundle
export/import). See
[`docs/operations/interactive-console.md`](docs/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 (`ConfirmDeleteModal` used bare `class="modal"` with no shipped CSS;
Bootstrap's `.modal{display:none}` hid it everywhere but mxaccessgw). Fixed via a
`zb-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 `StackGuard` turned into a silent cross-thread
hang rather than a stack overflow). New `DeferredSecretCacheInvalidator` defers 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 `TryAdd` registration-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) and `ZB.MOM.WW.Secrets.Replicator.AkkaDotNet` (peer-to-peer over
distributed pub/sub).