From db441d9d99d19e6f56f8a461598979678a68f6c1 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Mon, 9 Nov 2026 15:23:37 -0500 Subject: [PATCH] docs(secrets): document the plain-IConfigurationRoot reload gap and OtOpcUa's redundant starter (scadaproj#49) The reload gap for a plain IConfigurationRoot is documented rather than fixed: closing it would mean inserting a provider into ConfigurationRoot's internal list. No family host is exposed (plain roots are built with reloadOnChange: false; the rest pass a ConfigurationManager). clustered-secrets.md notes that AddZbSecretsAkkaReplication now starts the actor itself, so OtOpcUa's SecretReplicationStarter becomes redundant once it adopts 0.6.6. Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV --- ZB.MOM.WW.Secrets/README.md | 12 ++++++++++++ .../docs/operations/clustered-secrets.md | 5 +++++ 2 files changed, 17 insertions(+) diff --git a/ZB.MOM.WW.Secrets/README.md b/ZB.MOM.WW.Secrets/README.md index 4f99b13..683227b 100644 --- a/ZB.MOM.WW.Secrets/README.md +++ b/ZB.MOM.WW.Secrets/README.md @@ -105,6 +105,18 @@ keeps its startup value until restart — editing it, or rotating the secret, ta start. A plain `ConfigurationBuilder().Build()` root gets the values re-applied after each reload, which is weaker; build such a root without `reloadOnChange`. +*Known gap, not fixed (scadaproj#49):* a plain `IConfigurationRoot` has a fixed provider list, so +the overlay cannot be added to it; the library writes the values into its providers and re-applies +them from the root's reload token. Direct `IConfiguration` reads see the pinned value after a reload, +but an `IOptionsMonitor` bound to that root can rebind from the placeholder first — reload callbacks +run in an order the library does not control (the token's callbacks run last-registered-first, and +the expander registers early). Inserting a provider into a built root would mean mutating +`ConfigurationRoot`'s internal list, which this library deliberately does not do. Nobody in the +family is exposed today (ScadaBridge and the ignitionoee router build their plain root with +`reloadOnChange: false`; the other hosts pass a `ConfigurationManager`). If you must reload a plain +root, expand into a `ConfigurationManager` instead, or re-read the key per use rather than through +`IOptionsMonitor`. + The master key is 32 bytes, provided base64 in `ZB_SECRETS_MASTER_KEY` (Environment source). A missing/invalid key fails closed at startup (`MasterKeyUnavailableException`). diff --git a/ZB.MOM.WW.Secrets/docs/operations/clustered-secrets.md b/ZB.MOM.WW.Secrets/docs/operations/clustered-secrets.md index 021d194..f67dcd8 100644 --- a/ZB.MOM.WW.Secrets/docs/operations/clustered-secrets.md +++ b/ZB.MOM.WW.Secrets/docs/operations/clustered-secrets.md @@ -154,6 +154,11 @@ Optional but recommended: without it the protocol still round-trips under Akka's serializer, but the serializer carrying secret ciphertext becomes an inherited default rather than a deliberate choice. +Since `0.6.6` `AddZbSecretsAkkaReplication` starts the replication actor itself once the host has +started (scadaproj#33), so a node that only expands `${secret:}` references pre-host still +replicates. **OtOpcUa's own `SecretReplicationStarter` is then redundant** — it resolves the same +actor, so keeping it is harmless; drop it when OtOpcUa adopts `0.6.6` (scadaproj#49). + ### Securing the transport Rows on the wire are ciphertext and the KEK never travels, so a passive eavesdropper learns