using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.AdminUI.Uns; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Uns; /// /// lmxopcua#835 (a credential reference naming ANOTHER driver instance's secret) and #836 (the OpcUaClient /// user-certificate path confined to a node directory) at the SAVE path (RawTreeService): nothing reaches the /// config DB, through driver and device create + update. /// [Trait("Category", "Unit")] public sealed class CredentialInstanceAndPathSavePathTests { private static CancellationToken Ct => TestContext.Current.CancellationToken; private static string Galaxy(string reference) => """{"Gateway":{"Endpoint":"http://evil:5001","ApiKeySecretRef":"REF"}}""".Replace("REF", reference); private static async Task<(RawTreeService Svc, string Db, string VictimId)> WithVictimAsync() { var name = RawTreeTestDb.SeedFresh(); var svc = new RawTreeService(RawTreeTestDb.Factory(name)); var victim = await svc.CreateDriverAsync(RawTreeTestDb.ClusterId, null, "Victim", DriverTypeNames.Galaxy, Galaxy("secret:galaxy/victim-own/apikey"), Ct); victim.Ok.ShouldBeTrue(victim.Error); return (svc, name, victim.CreatedId!); } [Fact] public async Task CreateDriver_refuses_a_reference_naming_another_instance() { var (svc, name, victimId) = await WithVictimAsync(); var byName = await svc.CreateDriverAsync(RawTreeTestDb.ClusterId, null, "evil1", DriverTypeNames.Galaxy, Galaxy("secret:galaxy/Victim/apikey"), Ct); var byId = await svc.CreateDriverAsync(RawTreeTestDb.ClusterId, null, "evil2", DriverTypeNames.Galaxy, Galaxy($"secret:galaxy/{victimId}/apikey"), Ct); byName.Ok.ShouldBeFalse(); byName.Error!.ShouldContain("lmxopcua#835"); byId.Ok.ShouldBeFalse(); using var db = RawTreeTestDb.CreateNamed(name); db.DriverInstances.ShouldNotContain(d => d.Name == "evil1" || d.Name == "evil2"); } [Fact] public async Task Update_and_device_paths_refuse_and_keep_the_stored_config() { var (svc, name, victimId) = await WithVictimAsync(); var evil = await svc.CreateDriverAsync(RawTreeTestDb.ClusterId, null, "evil", DriverTypeNames.Galaxy, Galaxy("secret:galaxy/shared/apikey"), Ct); evil.Ok.ShouldBeTrue(evil.Error); byte[] rv; string deviceId; byte[] deviceRv; using (var db = RawTreeTestDb.CreateNamed(name)) { rv = db.DriverInstances.Single(d => d.DriverInstanceId == evil.CreatedId).RowVersion; var device = db.Devices.Single(d => d.DriverInstanceId == evil.CreatedId); deviceId = device.DeviceId; deviceRv = device.RowVersion; } var stolen = Galaxy($"secret:galaxy/{victimId}/apikey"); (await svc.UpdateDriverAsync(evil.CreatedId!, "evil", stolen, null, rv, Ct)).Error.ShouldNotBeNull().ShouldContain("lmxopcua#835"); (await svc.UpdateDeviceAsync(deviceId, "dev", stolen, true, deviceRv, Ct)).Ok.ShouldBeFalse(); (await svc.CreateDeviceAsync(evil.CreatedId!, "dev2", stolen, Ct)).Ok.ShouldBeFalse(); using var check = RawTreeTestDb.CreateNamed(name); check.DriverInstances.Single(d => d.DriverInstanceId == evil.CreatedId).DriverConfig.ShouldContain("secret:galaxy/shared/apikey"); check.Devices.Where(d => d.DriverInstanceId == evil.CreatedId).ShouldAllBe(d => !d.DeviceConfig.Contains(victimId)); } [Fact] public async Task A_driver_may_save_its_own_id_scoped_secret_on_update_and_on_its_devices() { var (svc, name, victimId) = await WithVictimAsync(); byte[] rv; string deviceId; byte[] deviceRv; using (var db = RawTreeTestDb.CreateNamed(name)) { rv = db.DriverInstances.Single(d => d.DriverInstanceId == victimId).RowVersion; var device = db.Devices.Single(d => d.DriverInstanceId == victimId); deviceId = device.DeviceId; deviceRv = device.RowVersion; } var own = Galaxy($"secret:galaxy/{victimId}/apikey"); (await svc.UpdateDriverAsync(victimId, "Victim", own, null, rv, Ct)).Ok.ShouldBeTrue(); (await svc.UpdateDeviceAsync(deviceId, "dev", Galaxy("secret:galaxy/Victim/apikey"), true, deviceRv, Ct)).Ok.ShouldBeTrue(); } [Theory] [InlineData("""{"AuthType":"Certificate","UserCertificatePath":"C:\\Windows\\System32\\config\\systemprofile\\host.pfx"}""")] [InlineData("""{"AuthType":"Certificate","UserCertificatePath":"/etc/ssl/private/host.pfx"}""")] [InlineData("""{"AuthType":"Certificate","UserCertificatePath":"../host.pfx"}""")] public async Task An_opcuaclient_user_certificate_path_outside_the_directory_is_never_stored(string config) { var name = RawTreeTestDb.SeedFresh(); var svc = new RawTreeService(RawTreeTestDb.Factory(name)); var result = await svc.CreateDriverAsync(RawTreeTestDb.ClusterId, null, "ouc836", DriverTypeNames.OpcUaClient, config, Ct); result.Ok.ShouldBeFalse(); result.Error!.ShouldContain("lmxopcua#836"); DriverConfigSaveGate.Validate(DriverTypeNames.OpcUaClient, config).ShouldNotBeNull(); // the modals' live check too using var db = RawTreeTestDb.CreateNamed(name); db.DriverInstances.ShouldNotContain(d => d.Name == "ouc836"); } [Fact] public async Task A_relative_user_certificate_path_saves() { var name = RawTreeTestDb.SeedFresh(); var svc = new RawTreeService(RawTreeTestDb.Factory(name)); (await svc.CreateDriverAsync(RawTreeTestDb.ClusterId, null, "ouc-ok", DriverTypeNames.OpcUaClient, """{"AuthType":"Certificate","UserCertificatePath":"plant1/user.pfx"}""", Ct)).Ok.ShouldBeTrue(); } [Fact] public async Task LoadDeviceDriverInstanceId_names_the_owning_driver() { var name = RawTreeTestDb.SeedFresh(); var svc = new RawTreeService(RawTreeTestDb.Factory(name)); (await svc.LoadDeviceDriverInstanceIdAsync(RawTreeTestDb.FolderedDeviceId, Ct)).ShouldBe(RawTreeTestDb.FolderedDriverId); (await svc.LoadDeviceDriverInstanceIdAsync("DEV-nope", Ct)).ShouldBeNull(); } }