[Secrets] Akka replication actor only starts on first ISecretStore use — read-only nodes never receive rotations #33
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?
From the scadaproj#15 shared-library review (
code-reviews/Secrets/findings.md, Secrets-010), verified at91fb2b4. Update the finding's Resolution row when fixed.ZB.MOM.WW.Secrets/src/ZB.MOM.WW.Secrets.Replicator.AkkaDotNet/DependencyInjection/AkkaSecretsServiceCollectionExtensions.cs:79Description
The actor is created only when
SecretReplicationActorProvider.ActorRefis read:SecretReplicationActorProviderwrapssystem.ActorOf(...)in aLazy<IActorRef>(lines 128–132)..ActorRefis theISecretReplicatorfactory (line 91).ReplicatingSecretStorefactory (lines 99–102), which is theISecretStoreregistration.No hosted service resolves any of these. The only hosted service registered is
SecretsMigrationHostedService, which takesISecretsStoreMigratoronly. So until application code resolvesISecretStore(orISecretResolver, which depends on it), there is no actor. Without the actor, this node:The node is invisible to replication, and nothing logs that.
Failure scenario
On a 2-node pair, node B's host uses the pre-host
${secret:}expander, which reads its own store instance. At runtime B never touchesISecretStore: nobody opens/admin/secretson B, and there is no runtime resolve. An operator rotatesdb/centralon node A.Verification: a throwaway probe project (not committed) builds the real graph and starts the hosted services. It printed
actor exists = Falsebefore resolvingISecretStoreandTrueafter.HostedProcessResolutionTestshides the gap because it resolves the store explicitly. Its own comment says OtOpcUa's startup hook happens to resolve the store, so the known consumer is likely covered by accident. The library contract does not guarantee this.Recommendation
Register a small
IHostedServiceinAddZbSecretsAkkaReplicationthat readsSecretReplicationActorProvider.ActorRefinStartAsync. Do this after the ActorSystem exists; resolving the provider does not re-enter the scadaproj#1 cycle, because the invalidator is still deferred. Add a test that starts the hosted services and asserts that/user/<ActorName>exists without anything resolvingISecretStore. Failing that, document that hosts must resolveISecretStoreat startup.Resolution
Unresolved.
Fixed on main 9fd1a7e (Secrets-010 → Resolved): a new hosted service creates the Akka replication actor once the host has started (in StartAsync without a host lifetime). The ActorSystem must be resolvable at host start even on nodes that never touch a secret — a failure is logged, not fatal. OtOpcUa's own SecretReplicationStarter becomes redundant once it adopts 0.6.6. Evidence: Secrets solution (5 test projects) all pass — Core 246 (1 DPAPI skip), SqlServer 60 (16 live-SQL skips), Akka 44, Grpc 196, Ui 20; each regression test fails with its fix removed. Ships in Secrets 0.6.6 (unpublished).