test(mesh): pin the admin-only role scoping of IMeshPskProvider/IMeshCommandDialer registrations (security register item 3)

This commit is contained in:
Joseph Doherty
2026-08-06 10:22:29 -04:00
parent a94eb8a335
commit 2e06dfe52d
@@ -0,0 +1,169 @@
using Shouldly;
using Xunit;
namespace ZB.MOM.WW.OtOpcUa.Host.Tests.Configuration;
/// <summary>
/// Security register item 3 (DI role-scoping pin). <c>IMeshPskProvider</c> and
/// <c>IMeshCommandDialer</c> are central's per-cluster key lookup + outbound command dialer —
/// a driver-only node holds exactly its own <c>MeshTransport:GrpcPsk</c> and never dials
/// another node, so these two singletons must be registered ONLY inside one of Program.cs's
/// <c>if (hasAdmin)</c> blocks. Nothing in the language enforces that placement; this is a
/// source scan so a refactor that hoists either registration out of its block fails loudly
/// here instead of silently widening the mesh trust boundary to driver-only nodes.
/// </summary>
/// <remarks>
/// Program.cs gates several unrelated concerns behind repeated <c>if (hasAdmin)</c> checks
/// (auth/AdminUI mounting, health probes, gRPC endpoint mapping, ...), so this pin cannot
/// assume there is only one such block in the file — it must find the SPECIFIC block that
/// contains each registration and prove that containment is unambiguous.
/// </remarks>
[Trait("Category", "Unit")]
public sealed class MeshRegistrationRoleScopingTests
{
private const string PskRegistration =
"AddSingleton<IMeshPskProvider, MeshPskProvider>";
private const string DialerRegistration =
"AddSingleton<IMeshCommandDialer, MeshGrpcCommandDialer>";
private const string HasAdminMarker = "if (hasAdmin)";
private static string ReadProgramSource()
{
var dir = new DirectoryInfo(AppContext.BaseDirectory);
while (dir is not null && !File.Exists(Path.Combine(dir.FullName, "ZB.MOM.WW.OtOpcUa.slnx")))
dir = dir.Parent;
dir.ShouldNotBeNull();
return File.ReadAllText(Path.Combine(
dir!.FullName, "src", "Server", "ZB.MOM.WW.OtOpcUa.Host", "Program.cs"));
}
private static int CountOccurrences(string haystack, string needle)
{
var count = 0;
var index = 0;
while ((index = haystack.IndexOf(needle, index, StringComparison.Ordinal)) >= 0)
{
count++;
index += needle.Length;
}
return count;
}
/// <summary>
/// Brace-matches every <c>if (hasAdmin)</c> block in the file (not a regex over the block
/// body — the body is free-form C# and a regex would be fragile to reformatting) and
/// returns each block's (open-brace, close-brace) character range.
/// </summary>
private static List<(int Start, int End)> FindHasAdminBlockRanges(string source)
{
var ranges = new List<(int Start, int End)>();
var searchIndex = 0;
while (true)
{
var markerIndex = source.IndexOf(HasAdminMarker, searchIndex, StringComparison.Ordinal);
if (markerIndex < 0)
break;
var openBraceIndex = source.IndexOf('{', markerIndex + HasAdminMarker.Length);
openBraceIndex.ShouldBeGreaterThan(-1,
$"'{HasAdminMarker}' at offset {markerIndex} is not followed by an opening brace.");
var depth = 0;
var closeBraceIndex = -1;
for (var i = openBraceIndex; i < source.Length; i++)
{
if (source[i] == '{')
depth++;
else if (source[i] == '}')
{
depth--;
if (depth == 0)
{
closeBraceIndex = i;
break;
}
}
}
closeBraceIndex.ShouldBeGreaterThan(-1,
$"brace-matching for the '{HasAdminMarker}' block starting at offset {markerIndex} never closed.");
ranges.Add((openBraceIndex, closeBraceIndex));
searchIndex = closeBraceIndex + 1;
}
return ranges;
}
/// <summary>
/// Locates the single <c>if (hasAdmin)</c> block that contains <paramref name="registration"/>.
/// Fails loudly — rather than silently picking the first match — if the registration is
/// missing entirely, sits outside every admin-role block, or (should a future edit make two
/// blocks both contain matching text) the containing block is ambiguous. Any of those is a
/// reason to stop and re-examine the pin, not to guess.
/// </summary>
private static string ExtractContainingHasAdminBlock(string source, string registration, string whyItMatters)
{
var ranges = FindHasAdminBlockRanges(source);
ranges.ShouldNotBeEmpty(
$"Program.cs no longer contains any '{HasAdminMarker}' block — the admin-role gate this " +
$"pin relies on is gone. {whyItMatters}");
var registrationIndex = source.IndexOf(registration, StringComparison.Ordinal);
registrationIndex.ShouldBeGreaterThan(-1,
$"'{registration}' was not found anywhere in Program.cs. {whyItMatters}");
var containingBlocks = ranges
.Where(r => registrationIndex >= r.Start && registrationIndex <= r.End)
.ToList();
containingBlocks.ShouldNotBeEmpty(
$"'{registration}' is registered OUTSIDE every '{HasAdminMarker}' block. {whyItMatters}");
containingBlocks.Count.ShouldBe(1,
$"'{registration}' falls inside {containingBlocks.Count} '{HasAdminMarker}' blocks, so " +
"which one actually gates it is ambiguous — extend this pin to disambiguate rather than " +
$"trusting the first match. {whyItMatters}");
var (start, end) = containingBlocks[0];
return source.Substring(start, end - start + 1);
}
[Fact]
public void MeshPskProvider_IsRegisteredExactlyOnce_InsideAnUnambiguousHasAdminBlock()
{
const string why =
"A driver-only node must not carry central's per-cluster key lookup, which it can never " +
"legitimately use.";
var source = ReadProgramSource();
// Global uniqueness first: if this text appeared twice, block-containment below could pass
// even though a second, unguarded copy exists elsewhere in the file.
CountOccurrences(source, PskRegistration).ShouldBe(1,
$"IMeshPskProvider must be registered exactly once in Program.cs. {why}");
var block = ExtractContainingHasAdminBlock(source, PskRegistration, why);
block.ShouldContain(PskRegistration, Case.Sensitive,
$"IMeshPskProvider must be registered inside 'if (hasAdmin)'. {why}");
}
[Fact]
public void MeshGrpcCommandDialer_IsRegisteredExactlyOnce_InsideAnUnambiguousHasAdminBlock()
{
const string why =
"A driver-only node never dials another node and must not carry central's outbound mesh " +
"command dialer.";
var source = ReadProgramSource();
CountOccurrences(source, DialerRegistration).ShouldBe(1,
$"IMeshCommandDialer must be registered exactly once in Program.cs. {why}");
var block = ExtractContainingHasAdminBlock(source, DialerRegistration, why);
block.ShouldContain(DialerRegistration, Case.Sensitive,
$"IMeshCommandDialer must be registered inside 'if (hasAdmin)'. {why}");
}
}