feat(security): register TimeProvider + S18/S19 option knobs (archreview 03/S18,S19)
This commit is contained in:
+6
-4
@@ -236,8 +236,10 @@ public sealed class MeshRegistrationRoleScopingTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pins the scanner's expected result shape against today's Program.cs — 6 markers,
|
||||
/// 5 braced blocks + the 1 braceless ConfigDb gate, so 6 scanned ranges. Without this, a
|
||||
/// Pins the scanner's expected result shape against today's Program.cs — 5 markers,
|
||||
/// 4 braced blocks + the 1 braceless ConfigDb gate, so 5 scanned ranges (the data-plane
|
||||
/// key-store branch moved into <c>DataPlaneAuthRegistration</c> for arch-review 03/S18, taking
|
||||
/// one braced marker with it). Without this, a
|
||||
/// scanner bug that silently drops or merges a marker (as the pre-fix version did to the
|
||||
/// braceless marker — it fabricated a phantom range spanning an unrelated if-block and swallowed
|
||||
/// a real nested 'if (hasAdmin)' block inside it, yielding 5 ranges instead of 6) would not
|
||||
@@ -248,13 +250,13 @@ public sealed class MeshRegistrationRoleScopingTests
|
||||
{
|
||||
var source = ReadProgramSource();
|
||||
|
||||
CountOccurrences(source, HasAdminMarker).ShouldBe(6,
|
||||
CountOccurrences(source, HasAdminMarker).ShouldBe(5,
|
||||
"Program.cs's 'if (hasAdmin)' marker count changed. If a marker was added or removed, " +
|
||||
"update this pin — and check whether the new/removed occurrence is braced or braceless — " +
|
||||
"rather than letting the scanner silently mis-shape its result.");
|
||||
|
||||
var ranges = FindHasAdminBlockRanges(source);
|
||||
ranges.Count.ShouldBe(6,
|
||||
ranges.Count.ShouldBe(5,
|
||||
"expected one scanned range per 'if (hasAdmin)' marker, including the braceless ConfigDb " +
|
||||
"gate. A mismatch means the scanner adopted an unrelated brace or otherwise merged/dropped " +
|
||||
"a marker instead of handling each one independently.");
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ZB.MOM.WW.OtOpcUa.Cluster;
|
||||
using ZB.MOM.WW.OtOpcUa.Configuration;
|
||||
using ZB.MOM.WW.OtOpcUa.Core.Authorization;
|
||||
using ZB.MOM.WW.OtOpcUa.Host.Configuration;
|
||||
using ZB.MOM.WW.OtOpcUa.OpcUaServer.Security;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Host.Tests.OpcUa;
|
||||
|
||||
/// <summary>
|
||||
/// Guards the role-conditional data-plane authentication wiring (arch-review 03/S18). A decorator
|
||||
/// registered in one branch of a role conditional is the "built but never wired" shape, so the branch
|
||||
/// each node role lands in is asserted rather than assumed.
|
||||
/// </summary>
|
||||
public sealed class ApiKeyStoreRegistrationTests
|
||||
{
|
||||
/// <summary>
|
||||
/// The authorizer's clock and the registered clock must be the SAME instance.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="DataPlaneAuthorizer"/> already documents that a divergence between two clocks denies
|
||||
/// every request with a verdict indistinguishable from "no grant matched" — found once by a test
|
||||
/// clock two hours ahead. One registered singleton threaded into every consumer makes the
|
||||
/// divergence unrepresentable instead of merely unlikely, so the registration is what is pinned.
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
public void The_authorizer_and_the_key_store_share_one_TimeProvider()
|
||||
{
|
||||
using var sp = BuildNode(hasAdmin: true, hasDriver: true);
|
||||
|
||||
var registered = sp.GetRequiredService<TimeProvider>();
|
||||
var authorizer = sp.GetRequiredService<DataPlaneAuthorizer>();
|
||||
|
||||
registered.ShouldBeSameAs(Probe.Instance);
|
||||
authorizer.Clock.ShouldBeSameAs(registered);
|
||||
}
|
||||
|
||||
/// <summary>Builds the data-plane authentication wiring for one node role combination.</summary>
|
||||
/// <param name="hasAdmin">Whether the node carries the <c>admin</c> role.</param>
|
||||
/// <param name="hasDriver">Whether the node carries the <c>driver</c> role.</param>
|
||||
/// <returns>The built provider; the caller disposes it.</returns>
|
||||
internal static ServiceProvider BuildNode(bool hasAdmin, bool hasDriver)
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddLogging(b => b.AddProvider(NullLoggerProvider.Instance));
|
||||
services.AddSingleton<TimeProvider>(Probe.Instance);
|
||||
services.Configure<ApiKeyOptions>(_ => { });
|
||||
services.Configure<AclOptions>(_ => { });
|
||||
services.Configure<AkkaClusterOptions>(o => o.Roles = ["admin", "driver", "cluster-MAIN"]);
|
||||
services.AddSingleton<IDataPlaneAuthoritySource>(EmptyDataPlaneAuthoritySource.Instance);
|
||||
services.AddSingleton<PermissionTrieCache>();
|
||||
services.AddSingleton<DataPlaneAuthorityState>();
|
||||
|
||||
if (hasAdmin)
|
||||
{
|
||||
services.AddDbContextFactory<OtOpcUaConfigDbContext>(
|
||||
o => o.UseInMemoryDatabase($"apikey-reg-{Guid.NewGuid():N}"));
|
||||
}
|
||||
|
||||
services.AddOtOpcUaDataPlaneAuth(hasAdmin, hasDriver);
|
||||
return services.BuildServiceProvider();
|
||||
}
|
||||
|
||||
/// <summary>A clock distinguishable from <see cref="TimeProvider.System"/> by reference.</summary>
|
||||
private sealed class Probe : TimeProvider
|
||||
{
|
||||
public static readonly Probe Instance = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DateTimeOffset GetUtcNow() => new(2026, 8, 20, 12, 0, 0, TimeSpan.Zero);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user