fix(mesh): stop reporting override-keyed clusters as REVOKED (lmxopcua#784)

The docker-dev centrals logged "Mesh PSK secret ot-mesh-psk-<cluster> ... is
absent (or blank) in the store; the key is REVOKED" for every cluster on every
contact refresh, while every gRPC dispatch and ack authenticated.

Investigated: not an auth bypass. The rig supplies each cluster's key through
MeshTransport:ClusterPsks (central side, equal to that cluster's nodes'
MeshTransport:GrpcPsk) and seeds nothing in the store. TryGetPsk/Matches consult
the override first on every call, so enforcement was on; only RefreshAsync read
the store for those clusters regardless and logged a revocation that never took
effect. RefreshAsync now skips clusters covered by a non-blank override, so the
REVOKED warning is only logged for a cluster that genuinely has no key.

Test pins it with a positive control (a cluster with no override is still read,
warned about and refused) and enforcement on the override key (wrong token
rejected). Mutation-checked: removing the skip fails the test.

Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
This commit is contained in:
Joseph Doherty
2026-11-09 21:13:56 -05:00
parent d9b95dcf56
commit 43f61eba2e
3 changed files with 78 additions and 1 deletions
@@ -1,3 +1,4 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Shouldly;
@@ -129,6 +130,52 @@ public sealed class MeshPskProviderRefreshTests
foreach (var c in clusters) provider.TryGetPsk(c, out _).ShouldBeTrue();
}
/// <summary>
/// lmxopcua#784 — the docker-dev centrals supply every cluster's key through
/// <c>MeshTransport:ClusterPsks</c> and seed nothing in the store, and logged "absent in the store; the
/// key is REVOKED — commands … refused" for all three clusters on every contact refresh while every
/// dispatch and ack authenticated. The override wins on every lookup, so the key was never revoked: the
/// warning was false. The refresh now leaves override-covered clusters alone. SITE-B (no override) is the
/// positive control — a cluster with no key anywhere still gets the REVOKED warning and is refused.
/// </summary>
[Fact]
public async Task A_cluster_keyed_by_the_config_override_is_not_reported_revoked_and_stays_authenticated()
{
var resolver = new GatedResolver { Value = null }; // the store holds nothing, as on the rig
var options = new MeshTransportOptions();
options.ClusterPsks["SITE-A"] = "rig-psk-site-a";
var logger = new RecordingLogger();
var provider = new MeshPskProvider(Options.Create(options), resolver, logger);
await provider.RefreshAsync(new[] { "site-a", "SITE-B" }, CancellationToken.None);
resolver.Calls.ShouldBe(1, "only SITE-B, which has no override, is read from the store");
var revoked = logger.Entries.Where(e => e.Level == LogLevel.Warning && e.Message.Contains("REVOKED")).ToList();
revoked.Count.ShouldBe(1);
revoked[0].Message.ShouldContain("SITE-B");
// Enforcement is real for both: SITE-A authenticates against the override key and nothing else,
// SITE-B (no key anywhere) is refused.
provider.Matches("SITE-A", "rig-psk-site-a").ShouldBeTrue();
provider.Matches("SITE-A", "wrong").ShouldBeFalse();
provider.TryGetPsk("SITE-B", out _).ShouldBeFalse();
}
private sealed class RecordingLogger : ILogger<MeshPskProvider>
{
public List<(LogLevel Level, string Message)> Entries { get; } = new();
public IDisposable? BeginScope<TState>(TState state) where TState : notnull => null;
public bool IsEnabled(LogLevel logLevel) => true;
public void Log<TState>(
LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
{
lock (Entries) Entries.Add((logLevel, formatter(state, exception)));
}
}
private sealed class ConcurrencyTrackingResolver : ISecretResolver
{
private int _inFlight;