Clustered secrets: Akka replication blocked by upstream deadlock (scadaproj#1) - keep Secrets:Replication:Enabled=false #482
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Tracker for the OtOpcUa side of a defect whose fix belongs upstream in the shared secrets library.
Upstream issue:
scadaproj#1ZB.MOM.WW.Secrets.Replicator.AkkaDotNet0.2.1 deadlocks when its replication actor is created through DI in a real hosted process — resolvingISecretReplicatornever returns. This blocks OtOpcUa from turning on the Akka clustered-secrets topology.Current state in this repo — safe, but do not enable
The wiring is merged (
master, secrets adoption atZB.MOM.WW.Secrets0.2.1) and is inert by design:Secrets:Replication:Enableddefaults tofalseinsrc/Server/ZB.MOM.WW.OtOpcUa.Host/Configuration/SecretsRegistration.cs.AddOtOpcUaSecretscalls plainAddZbSecrets(...)— behaviour is byte-identical to before the adoption. Guarded bySecretsReplicationRegistrationTests.⚠️ Do NOT set
Secrets:Replication:Enabled=trueon any OtOpcUa node until scadaproj#1 is fixed. The startup hook resolvesISecretStore, so the host would hang at startup — including driver-role nodes, whose only visible symptom would be drivers never opening sessions.Why this is filed here rather than only upstream
The wiring, the config flag, and the live gate that must re-run all live in this repo. When scadaproj#1 is fixed:
ZB.MOM.WW.Secrets.Replicator.AkkaDotNetto the fixed version inDirectory.Packages.props.Alternative if this stays blocked
The SQL-Server hub topology (
ZB.MOM.WW.Secrets.Replicator.SqlServer) passed its live gate 6/6 against a real SQL Server 2022 and is unaffected by this defect. OtOpcUa already runs a central config DB, so shared-store or hub mode is a viable fallback if the Akka path cannot be fixed — at the cost of coupling driver-node secret resolution to database reachability, which is why peer-to-peer was preferred.References
scadaproj/components/secrets/GAPS.mdscadaproj/docs/plans/2026-07-18-secrets-0.2.0-upgrade-and-clustering.md(Task 9)Unblocked: the upstream deadlock (scadaproj#1) is root-caused, fixed, and
ZB.MOM.WW.Secrets.*0.2.2is published to the Gitea feed (restore-verified). It was a DI singleton cycle in the Akka package's wiring (store decorator → replicator → actor provider → cache invalidator → resolver → store), not anything Akka-side; fixed by deferring the invalidator edge to first eviction. The 2-node Akka live convergence gate now passes 6/6 against the fixed lib. Next here: bump OtOpcUa to0.2.2, re-run the gate on the OtOpcUa topology, then consider enablingSecrets:Replication:Enabled.Items 1–2 done —
master@2254ae3d1. Bumped. All four
ZB.MOM.WW.Secrets.*pins0.2.1→0.2.2inDirectory.Packages.props. Full solution builds with 0 errors.2. Gate re-run — 6/6 PASS, against the published 0.2.2 feed packages (not source refs): cluster forms → write on A resolves on B (shared-KEK decrypt) → delete tombstones on B with no resurrection → tombstoned secret no longer resolves through B's cache (the check that proves the deferred invalidator evicts) → reverse-direction write converges → wrong-KEK node fails closed via
SecretDecryptionException.Bonus — the promised test now exists.
SecretReplicationStarter's docs referenced a skipped testThe_startup_hook_actually_creates_the_replication_actorthat had never been written because the resolve hung. It's now a real provider-based test: container built exactly as the host builds it,SecretReplicationStarterrun under a 20 s watchdog, and the replication actor proven to exist viaActorSelectionon a self-joined single-node cluster (plainActorSystem, no TestKit — sidesteps the xunit.v3/Akka.TestKit.Xunit2conflict). On 0.2.0 that test fails (no actor); on 0.2.1 it hits the watchdog (the deadlock); on 0.2.2 it passes.SecretsReplicationRegistrationTests8/8. Also corrected the stale comment that blamed the old hang onDistributedPubSubneeding a joined cluster — the actor constructor was never reached; it was the DI cycle (scadaproj#1).Remaining (item 3):
Secrets:Replication:Enabledstays default-false. Enabling is a per-environment operational decision — needs the same KEK distributed to every node first (Secrets:MasterKey), perZB.MOM.WW.Secrets/docs/operations/clustered-secrets.md. Leaving this issue open to track that decision.Item 3 done — replication ENABLED and live-proven on the docker-dev rig (
master@c878fbbd)All six host nodes (central-1/2, site-a-1/2, site-b-1/2) now run with
Secrets:Replication:Enabled=truevia onex-secrets-envcompose anchor: replication on,AnnounceInterval=00:00:05, and the ONE shared dev-only KEK (${OTOPCUA_SECRETS_KEK:-…}committed default per the rig's zero-operator-step pattern; every node deriveskek_id sha256:f3f0c2056e7a).Evidence (rig left running in this state):
Application started, repeatedly, across restarts; the scadaproj#1 hang signature never appeared. Mesh formed (all members Up).gate/dockerdev) seeded into site-a-1's local store only propagated to all five peers within ≤60 s. All six nodes' SQLite stores held the row with byte-identical ciphertext (same sha256 over ciphertext‖nonce‖tag, same kek_id, rev=0) — replication, not re-encryption — and the rows copied from central-2 and site-b-2 decrypt to the exact plaintext under the shared KEK.docker compose restart site-b-2→ rejoined cleanly, row intact, zero errors. The fleet also survived a full local Docker-engine restart with replication enabled (environmental OrbStack wedge mid-verification, not an app defect — no deadlock, no crash).Found during verification, filed separately: #483 — the AdminUI
/admin/secretspage renders but is non-interactive (Secrets.Ui RCL components have no@rendermode; the host applies render modes per-page), so "Add secret" does nothing. Pre-existing, unrelated to replication.With bump (0.2.2), gate re-run (6/6 vs feed packages), and enablement all done and proven, this tracker is complete — closing. Production/wonder enablement, if wanted later, needs its own decision + real KEK distribution (NOT this dev key) per
ZB.MOM.WW.Secrets/docs/operations/clustered-secrets.md, and deserves a fresh issue.