c86cead5c2
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
94 lines
5.0 KiB
Markdown
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).
|