diff --git a/CLAUDE.md b/CLAUDE.md index b9aedb0..2862380 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -148,7 +148,7 @@ each project's **code-verified current state**, and the **gaps** between. See | Config + validation (options / startup validation) | Adopted (lib `0.1.0`, on the feed; **all 4 apps**, pushed — the "local only / not yet pushed" caveat was stale) | Shared `ZB.MOM.WW.Configuration` lib | [`components/configuration/`](components/configuration/) | [`ZB.MOM.WW.Configuration/`](ZB.MOM.WW.Configuration/) | | Audit (event model + writer seam) | Adopted (lib `0.1.0`, on the feed; **all 4 apps**, pushed to origin) | Shared `ZB.MOM.WW.Audit` lib | [`components/audit/`](components/audit/) | [`ZB.MOM.WW.Audit/`](ZB.MOM.WW.Audit/) | | Galaxy Repository (object-hierarchy SQL browse + gRPC service) | **Adopted** (feed `0.1.0` + `0.2.0`; consumed at `0.2.0` by HistorianGateway **and mxaccessgw**, whose Server wires `AddZbGalaxyRepository` — verified 2026-07-18; the prior "mxaccessgw adoption is a follow-on" claim was stale) | Shared `ZB.MOM.WW.GalaxyRepository` lib | _(design in histsdk + design doc 2026-06-23)_ | [`ZB.MOM.WW.GalaxyRepository/`](ZB.MOM.WW.GalaxyRepository/) | -| Secrets (encrypted store + `${secret:}` resolution) | Built (libs `0.1.2`, **published to the Gitea feed**; **HistorianGateway adopted + live-proven** 2026-07-16; **mxaccessgw adopted G-4/G-5/G-6 + merged to `origin/main` @ `e088dfa`** 2026-07-16, box-verified fail-closed + encrypt-at-rest; **ScadaBridge adopted G-3/G-4/G-5/G-6 + merged to `origin/main` @ `128f1596`** 2026-07-16, **G-3 live-proven vs the real production MxGateway gateway** `wonder-app-vd03:5120` (secret-ref ApiKey → Connected + browsed real Galaxy); **OtOpcUa adopted G-2/G-4/G-5/G-6 + merged to `origin/master` @ `872cf7e3`** (lmxopcua) 2026-07-16 — the last app, so **all four now adopted**; Layer-B driver secrets (Galaxy API key + OpcUaClient `Password`/`UserCertificatePassword`) resolve fail-closed at driver session-open, `AddZbSecrets` registered unconditionally so driver-only nodes work; **G-2 live-proven vs the real production MxGateway** `wonder-app-vd03:5120` — a `secret:`-ref Galaxy ApiKey resolved through OtOpcUa's real `GalaxyDriverBrowser` → dummy key `MxGatewayAuthenticationException`, real key → browsed the real Galaxy root (10 nodes); temp key minted+revoked+deleted); **G-8 KEK-rotation BUILT (lib bumped `0.1.2`→`0.1.3`, 2026-07-17):** `Rewrap` DEK primitive + CAS-guarded `ApplyRewrapAsync` + `KekRotationService.RewrapAllAsync` + `secret rewrap-all` CLI + operator runbook (`ZB.MOM.WW.Secrets/docs/operations/kek-rotation.md`), full suite green + CLI smoke + adversarial crypto review PASS (TOCTOU closed via compare-and-swap). **G-7 clustered replication DESIGNED + PLANNED** — fork resolved to **build the shared SQL-Server `ISecretStore`** (Akka replicator deferred phase-2), executable plan `docs/plans/2026-07-17-secrets-g7-*`. **Corrected 2026-07-18:** G-8 + the G-7 plan **ARE committed and pushed** on `main` (`d82d345`) — the "not yet committed/pushed" note was stale. The real open gap is that **`0.1.3` is NOT on the feed** (feed tops out at `0.1.2`, where all 4 apps are pinned), so KEK rotation is unconsumable until published; G-7 has no `ISecretStore` SQL implementation yet | Shared `ZB.MOM.WW.Secrets` lib (3 packages + CLI) | [`components/secrets/`](components/secrets/) | [`ZB.MOM.WW.Secrets/`](ZB.MOM.WW.Secrets/) | +| Secrets (encrypted store + `${secret:}` resolution) | Built (libs **`0.1.3`**, **published to the Gitea feed**; **HistorianGateway adopted + live-proven** 2026-07-16; **mxaccessgw adopted G-4/G-5/G-6 + merged to `origin/main` @ `e088dfa`** 2026-07-16, box-verified fail-closed + encrypt-at-rest; **ScadaBridge adopted G-3/G-4/G-5/G-6 + merged to `origin/main` @ `128f1596`** 2026-07-16, **G-3 live-proven vs the real production MxGateway gateway** `wonder-app-vd03:5120` (secret-ref ApiKey → Connected + browsed real Galaxy); **OtOpcUa adopted G-2/G-4/G-5/G-6 + merged to `origin/master` @ `872cf7e3`** (lmxopcua) 2026-07-16 — the last app, so **all four now adopted**; Layer-B driver secrets (Galaxy API key + OpcUaClient `Password`/`UserCertificatePassword`) resolve fail-closed at driver session-open, `AddZbSecrets` registered unconditionally so driver-only nodes work; **G-2 live-proven vs the real production MxGateway** `wonder-app-vd03:5120` — a `secret:`-ref Galaxy ApiKey resolved through OtOpcUa's real `GalaxyDriverBrowser` → dummy key `MxGatewayAuthenticationException`, real key → browsed the real Galaxy root (10 nodes); temp key minted+revoked+deleted); **G-8 KEK-rotation BUILT (lib bumped `0.1.2`→`0.1.3`, 2026-07-17):** `Rewrap` DEK primitive + CAS-guarded `ApplyRewrapAsync` + `KekRotationService.RewrapAllAsync` + `secret rewrap-all` CLI + operator runbook (`ZB.MOM.WW.Secrets/docs/operations/kek-rotation.md`), full suite green + CLI smoke + adversarial crypto review PASS (TOCTOU closed via compare-and-swap). **G-7 clustered replication DESIGNED + PLANNED** — fork resolved to **build the shared SQL-Server `ISecretStore`** (Akka replicator deferred phase-2), executable plan `docs/plans/2026-07-17-secrets-g7-*`. **Corrected 2026-07-18:** G-8 + the G-7 plan **ARE committed and pushed** on `main` (`d82d345`) — the "not yet committed/pushed" note was stale. **`0.1.3` PUBLISHED to the feed 2026-07-18** (restore-verified), carrying a **transitive security pin** (`SQLitePCLRaw.lib.e_sqlite3` → `2.1.12`, advisory GHSA-2m69-gcr7-jv3q) that `0.1.2` lacked. All 4 apps still pinned at `0.1.2` → bump to consume KEK rotation + the security fix. G-7 has no `ISecretStore` SQL implementation yet | Shared `ZB.MOM.WW.Secrets` lib (3 packages + CLI) | [`components/secrets/`](components/secrets/) | [`ZB.MOM.WW.Secrets/`](ZB.MOM.WW.Secrets/) | | LocalDb (embedded cache + 2-node sync) | Built (3 pkgs `0.1.0`, **published to the Gitea feed** 2026-07-18, restore-verified from a scratch consumer; no app adoption yet) | Shared `ZB.MOM.WW.LocalDb` lib | [`docs/plans/2026-07-17-localdb-design.md`](docs/plans/2026-07-17-localdb-design.md) | [`ZB.MOM.WW.LocalDb/`](ZB.MOM.WW.LocalDb/) | The auth component is fully populated: a normalized [`spec`](components/auth/spec/SPEC.md), a diff --git a/README.md b/README.md index 310a30d..5762302 100644 --- a/README.md +++ b/README.md @@ -64,13 +64,13 @@ Status verified 2026-07-18 against the feed + consumer branches (see [Roadmap](# | Component | Feed | Adoption | Folder | |---|---|---|---| -| Auth (login / identity / authz) | `0.1.0`–`0.1.4` | all four (pins drift `0.1.1`–`0.1.4`) | [`components/auth/`](components/auth/) | +| Auth (login / identity / authz) | `0.1.0`–`0.1.5` | all four (pins drift `0.1.1`–`0.1.4`; `0.1.5` = security fix, unconsumed) | [`components/auth/`](components/auth/) | | UI Theme (layout / tokens / components) | `0.2.0`–`0.3.1` | all four @ `0.3.1` | [`components/ui-theme/`](components/ui-theme/) | | Audit (event model + writer seam) | `0.1.0` | all four (DEEP) | [`components/audit/`](components/audit/) | | Config + validation | `0.1.0` | all four | [`components/configuration/`](components/configuration/) | | Observability (metrics / traces / logs) | `0.1.0` | all four | [`components/observability/`](components/observability/) | | Health (readiness / liveness / active-node) | `0.1.0` | all four | [`components/health/`](components/health/) | -| Secrets (encrypted store + `${secret:}`) | `0.1.0`–`0.1.2` | all four @ `0.1.2` (lib `0.1.3` unpublished) | [`components/secrets/`](components/secrets/) | +| Secrets (encrypted store + `${secret:}`) | `0.1.0`–`0.1.3` | all four @ `0.1.2` (`0.1.3` published, not yet consumed) | [`components/secrets/`](components/secrets/) | | Galaxy Repository (SQL browse + gRPC) | `0.1.0`, `0.2.0` | HistorianGateway + mxaccessgw | _(design in `docs/plans/`)_ | | LocalDb (embedded cache + 2-node sync) | `0.1.0` | none yet | _(design in `docs/plans/`)_ | @@ -169,14 +169,16 @@ ZB_LDAP_IT=1 dotnet test # requires a reachable GLAuth (e.g. a sister repo's i - ✅ Galaxy Repository — `ZB.MOM.WW.GalaxyRepository` `0.2.0` on the feed; consumed by HistorianGateway **and mxaccessgw** (which wires `AddZbGalaxyRepository` in its Server; previously documented as a pending follow-on — also stale). -- ✅ Secrets — `ZB.MOM.WW.Secrets` (+ `.Abstractions`, `.Ui`) published through `0.1.2`; all four apps on `0.1.2`. +- ✅ Secrets — `ZB.MOM.WW.Secrets` (+ `.Abstractions`, `.Ui`) published through **`0.1.3`** (KEK rotation, + 2026-07-18); all four apps still pinned at `0.1.2`. - ✅ LocalDb — `ZB.MOM.WW.LocalDb` (+ `.Contracts`, `.Replication`) built at `0.1.0` and **published to the feed 2026-07-18** (restore-verified from a scratch consumer). **Open** -- ⬜ Publish `ZB.MOM.WW.Secrets` `0.1.3` (KEK rotation — built, feed still tops out at `0.1.2`) and execute - the G-7 clustered-replication plan — [`components/secrets/GAPS.md`](components/secrets/GAPS.md). +- ⬜ Execute the G-7 clustered-replication plan (shared SQL-Server `ISecretStore`) — [`components/secrets/GAPS.md`](components/secrets/GAPS.md). +- ⬜ Bump the three Auth consumers to `0.1.5` to pick up the SQLitePCLRaw security fix + (mxaccessgw + HistorianGateway `0.1.4`→`0.1.5`, ScadaBridge `0.1.3`→`0.1.5`). - ⬜ Adopt `ZB.MOM.WW.LocalDb` in an app — no consumer references it yet. - ⬜ Give LocalDb a `components/localdb/` entry; it is the one shared lib whose design lives only in [`docs/plans/2026-07-17-localdb-design.md`](docs/plans/2026-07-17-localdb-design.md). diff --git a/ZB.MOM.WW.Auth/Directory.Build.props b/ZB.MOM.WW.Auth/Directory.Build.props index c3bb10e..cb4931c 100644 --- a/ZB.MOM.WW.Auth/Directory.Build.props +++ b/ZB.MOM.WW.Auth/Directory.Build.props @@ -5,7 +5,7 @@ enable enable latest - 0.1.4 + 0.1.5 true diff --git a/ZB.MOM.WW.Auth/Directory.Packages.props b/ZB.MOM.WW.Auth/Directory.Packages.props index 907fce8..a61d3d3 100644 --- a/ZB.MOM.WW.Auth/Directory.Packages.props +++ b/ZB.MOM.WW.Auth/Directory.Packages.props @@ -2,6 +2,8 @@ true + + true @@ -10,6 +12,9 @@ + + diff --git a/ZB.MOM.WW.Secrets/Directory.Packages.props b/ZB.MOM.WW.Secrets/Directory.Packages.props index e8fbd27..a08993c 100644 --- a/ZB.MOM.WW.Secrets/Directory.Packages.props +++ b/ZB.MOM.WW.Secrets/Directory.Packages.props @@ -2,6 +2,8 @@ true + + true @@ -10,6 +12,9 @@ + + diff --git a/ZB.MOM.WW.Secrets/build/push.sh b/ZB.MOM.WW.Secrets/build/push.sh new file mode 100755 index 0000000..5454f4e --- /dev/null +++ b/ZB.MOM.WW.Secrets/build/push.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# push.sh — pack and push the ZB.MOM.WW.Secrets NuGet packages to the Gitea feed. +# +# Required environment variables: +# GITEA_NUGET_SOURCE — full URL of the Gitea NuGet feed +# e.g. https://gitea.dohertylan.com/api/packages/dohertj2/nuget/index.json +# GITEA_NUGET_KEY — Gitea access token with package:write permission +# +# Usage: +# export GITEA_NUGET_SOURCE="https://gitea.dohertylan.com/api/packages/dohertj2/nuget/index.json" +# export GITEA_NUGET_KEY="your-gitea-token" +# ./build/push.sh + +set -euo pipefail + +: "${GITEA_NUGET_SOURCE:?set GITEA_NUGET_SOURCE to your Gitea NuGet feed URL}" +: "${GITEA_NUGET_KEY:?set GITEA_NUGET_KEY to your Gitea access token}" + +dotnet pack -c Release -o ./artifacts + +dotnet nuget push "./artifacts/*.nupkg" \ + --source "$GITEA_NUGET_SOURCE" \ + --api-key "$GITEA_NUGET_KEY" \ + --skip-duplicate diff --git a/components/auth/GAPS.md b/components/auth/GAPS.md index 0b3edf3..0bd7c35 100644 --- a/components/auth/GAPS.md +++ b/components/auth/GAPS.md @@ -3,7 +3,16 @@ Divergence of each project from [`spec/SPEC.md`](spec/SPEC.md), and the ordered backlog to reach the shared `ZB.MOM.WW.Auth` library. Status legend: ⛔ gap · 🟡 partial · ✅ matches. -> **Current state (verified 2026-07-18):** feed carries `0.1.0`–`0.1.4`; consumer pins are +> **`0.1.5` PUBLISHED 2026-07-18 — security fix.** `Auth.ApiKeys` `0.1.4` and earlier ship a +> high-severity transitive vuln: `Microsoft.Data.Sqlite 10.0.7` pulls +> `SQLitePCLRaw.lib.e_sqlite3 2.1.11` (GHSA-2m69-gcr7-jv3q). `0.1.5` pins the patched `2.1.12` +> (same approach as `ZB.MOM.WW.LocalDb`); the `.nuspec` declares it directly, so consumers get +> the fix on bump without pinning themselves. **No API change** — `0.1.5` is `0.1.4` plus the pin. +> **All three ApiKeys consumers are still on vulnerable versions** (mxaccessgw `0.1.4`, +> HistorianGateway `0.1.4`, ScadaBridge `0.1.3`) → bump to `0.1.5`. Note ScadaBridge's +> `0.1.3`→`0.1.5` jump also crosses the `0.1.4` `ExpiresUtc` verifier change. +> +> **Current state (verified 2026-07-18):** feed carries `0.1.0`–`0.1.5`; consumer pins are > OtOpcUa `0.1.1`, ScadaBridge `0.1.3`, MxAccessGateway `0.1.4`, **HistorianGateway `0.1.4`** > (a fourth consumer this file predates, taking all four packages). OtOpcUa is three minors > behind — it does not get the `0.1.4` API-key-expiry enforcement. Library suite: **215 passing, diff --git a/components/secrets/GAPS.md b/components/secrets/GAPS.md index 9fc5e12..bb58f67 100644 --- a/components/secrets/GAPS.md +++ b/components/secrets/GAPS.md @@ -7,13 +7,20 @@ construction). ## Current state (verified 2026-07-18) -- **Source is at `0.1.3`; the feed is at `0.1.2`.** `ZB.MOM.WW.Secrets/Directory.Build.props` - declares `0.1.3` and the G-8 KEK-rotation work (`KekRotationService`, - `RewrapReport`, `secret rewrap-all`, `docs/operations/kek-rotation.md`) is **committed and - pushed** on `main` (`d82d345`). The Gitea feed carries only `0.1.0`–`0.1.2` for all three - packages. **→ `0.1.3` is unpublished: no app can consume KEK rotation yet.** (An earlier note - claiming G-8 was "not yet committed/pushed" was stale — it is committed; only the *publish* - is outstanding.) +- **`0.1.3` PUBLISHED to the Gitea feed 2026-07-18** (all three packages; restore-verified from a + scratch consumer). Carries the G-8 KEK-rotation work (`KekRotationService`, `RewrapReport`, + `secret rewrap-all`, `docs/operations/kek-rotation.md`), committed on `main` at `d82d345`. + (An earlier note claiming G-8 was "not yet committed/pushed" was stale.) +- **`0.1.3` also carries a transitive security fix that `0.1.2` does not.** Packing surfaced + NU1903: `Microsoft.Data.Sqlite 10.0.7` pulls `SQLitePCLRaw.lib.e_sqlite3 2.1.11`, which has a + **high-severity** advisory (GHSA-2m69-gcr7-jv3q). Fixed the way `ZB.MOM.WW.LocalDb` already + did — `CentralPackageTransitivePinningEnabled` + a pin to the patched `2.1.12`. Bumping + `Microsoft.Data.Sqlite` does **not** help (even `10.0.10` still pulls `2.1.11`); the pin is the + fix. Verified: the shipped `.nuspec` declares `SQLitePCLRaw.lib.e_sqlite3 >= 2.1.12` as a direct + dependency, so **consumers get the patched native lib without needing their own pin**, and a + fresh consumer restoring `0.1.3` scans clean. +- **All four apps are still pinned at `0.1.2`** — they get neither KEK rotation nor the security + fix until bumped. - **All four apps are pinned to `0.1.2`** — OtOpcUa, MxAccessGateway, ScadaBridge take `Secrets` + `.Abstractions` + `.Ui`; HistorianGateway takes `Secrets` + `.Ui`. - **G-7 clustered replication is designed + planned, not built** — plan at @@ -23,7 +30,7 @@ construction). ### Open -- ⬜ Publish `ZB.MOM.WW.Secrets` `0.1.3` (+ `.Abstractions`, `.Ui`) to the Gitea feed. +- ⬜ Bump the four apps `0.1.2` → `0.1.3` to pick up KEK rotation + the SQLitePCLRaw security fix. - ⬜ Execute the G-7 plan (shared SQL-Server `ISecretStore`). - ⬜ Per-app live wonder gates for the G-8 rotation path.