diff options
Diffstat (limited to 'tests/Agent.Windows.Tests/SecurityPolicyTests.cs')
| -rw-r--r-- | tests/Agent.Windows.Tests/SecurityPolicyTests.cs | 74 |
1 files changed, 54 insertions, 20 deletions
diff --git a/tests/Agent.Windows.Tests/SecurityPolicyTests.cs b/tests/Agent.Windows.Tests/SecurityPolicyTests.cs index 6dbc9ab..5b10417 100644 --- a/tests/Agent.Windows.Tests/SecurityPolicyTests.cs +++ b/tests/Agent.Windows.Tests/SecurityPolicyTests.cs @@ -11,6 +11,8 @@ using Agent.Core.Tools; using System.Text.Json; using Agent.App.Sandbox; using Agent.App.Configuration; +using LoopbackExemption = Agent.Admin.Host.LoopbackExemption; +using WfpDynamicSession = Agent.Admin.Host.WfpDynamicSession; namespace Agent.Windows.Tests; @@ -793,7 +795,7 @@ public sealed class SecurityPolicyTests try { - RecoveryResult recovered = new RecoveryManager(journals, audit).RecoverAll(); + RecoveryResult recovered = new RecoveryManager(journals, audit, new DirectElevatedNetworkSessionFactory()).RecoverAll(); Assert.True(recovered.IsClean); Assert.Equal(1, recovered.JournalsRecovered); @@ -957,7 +959,8 @@ public sealed class SecurityPolicyTests session.ProbeWriteTransaction(); IReadOnlyList<ulong> ids = session.ApplyDefaultBlock(profile.Sid, - [new NetworkEndpoint(IPAddress.Loopback, 11434), new NetworkEndpoint(IPAddress.IPv6Loopback, 11434)]); + [new Agent.Admin.Host.NetworkEndpoint(IPAddress.Loopback, 11434), + new Agent.Admin.Host.NetworkEndpoint(IPAddress.IPv6Loopback, 11434)]); Assert.Equal(4, ids.Count); Assert.All(ids, id => Assert.NotEqual(0UL, id)); @@ -965,7 +968,7 @@ public sealed class SecurityPolicyTests } [Fact] - public async Task Windows_backend_applies_file_effect_and_cleans_acl_wfp_temp_and_journal() + public async Task Windows_backend_file_only_effect_does_not_apply_acl_or_network_resources() { using TestDirectory directory = new(); string workspace = Path.Combine(directory.Path, "workspace"); @@ -978,7 +981,8 @@ public sealed class SecurityPolicyTests [new FilePolicyEntry(workspace, FileCapability.Read | FileCapability.Write | FileCapability.Delete)], [], [], [], new SandboxJobLimits(), "none", [], Path.Combine(directory.Path, "journals"), Path.Combine(directory.Path, "backups"), 16 * 1024 * 1024, 1024 * 1024, 1000, 10000); - var backend = new WindowsToolBackend(settings, audit); + var elevated = new DirectElevatedNetworkSessionFactory(); + var backend = new WindowsToolBackend(settings, audit, elevatedNetwork: elevated); Guid taskId = Guid.NewGuid(); Guid sessionId = Guid.NewGuid(); await backend.BeginTaskAsync(taskId, sessionId, TestContext.Current.CancellationToken); @@ -995,13 +999,13 @@ public sealed class SecurityPolicyTests Assert.Empty(Directory.EnumerateFiles(Path.Combine(directory.Path, "journals"))); string backups = Path.Combine(directory.Path, "backups"); Assert.True(!Directory.Exists(backups) || !Directory.EnumerateFileSystemEntries(backups).Any()); - Assert.Contains(audit.Events, item => item.Event == "network.rule.remove" && item.Data["residual_filters"] == "0"); + Assert.Equal(0, elevated.Starts); + Assert.DoesNotContain(audit.Events, item => item.Event is "network.rule.apply" or "network.rule.remove"); Assert.Contains(audit.Events, item => item.Event == "task.start" && item.SessionId == sessionId); Assert.Contains(audit.Events, item => item.Event == "tool.call" && item.SessionId == sessionId); Assert.Contains(audit.Events, item => item.Event == "file.write" && item.SessionId == sessionId); Assert.Contains(audit.Events, item => item.Event == "task.end" && item.SessionId == sessionId); - Assert.Single(audit.Events, item => item.Event == "acl.apply"); - Assert.Single(audit.Events, item => item.Event == "acl.restore"); + Assert.DoesNotContain(audit.Events, item => item.Event is "acl.apply" or "acl.restore"); } [Fact] @@ -1023,7 +1027,7 @@ public sealed class SecurityPolicyTests [new ApprovedEntryExecutable("cmd", cmd, EntryExecutablePolicy.ComputeSha256(cmd), true, null)], [], [], new SandboxJobLimits(), "none", [], Path.Combine(directory.Path, "journals"), Path.Combine(directory.Path, "backups"), 16 * 1024 * 1024, 1024 * 1024, 1000, 10000, BaselineCapabilities: ["registryRead", "lpacInstrumentation"]); - var backend = new WindowsToolBackend(settings, audit); + var backend = new WindowsToolBackend(settings, audit, elevatedNetwork: new DirectElevatedNetworkSessionFactory()); Guid taskId = Guid.NewGuid(); await backend.BeginTaskAsync(taskId, Guid.NewGuid(), TestContext.Current.CancellationToken); @@ -1042,6 +1046,34 @@ public sealed class SecurityPolicyTests } [Fact] + public async Task File_only_task_does_not_start_the_elevated_helper() + { + using TestDirectory directory = new(); + string workspace = Path.Combine(directory.Path, "workspace"); + Directory.CreateDirectory(workspace); + string input = Path.Combine(workspace, "input.txt"); + File.WriteAllText(input, "read-only operation"); + var factory = new NoOpElevatedNetworkSessionFactory(); + var settings = new EffectiveSandboxPolicy( + [new FilePolicyEntry(workspace, FileCapability.Read | FileCapability.Write | FileCapability.Delete | FileCapability.Execute, "workspace")], + [], [], [workspace], new SandboxJobLimits(), "none", [], + Path.Combine(directory.Path, "journals"), Path.Combine(directory.Path, "backups"), + 16 * 1024 * 1024, 1024 * 1024, 1000, 10000, + BaselineCapabilities: ["registryRead", "lpacInstrumentation"]); + var backend = new WindowsToolBackend(settings, new MemorySecurityAuditSink(), elevatedNetwork: factory); + Guid taskId = Guid.NewGuid(); + + await backend.BeginTaskAsync(taskId, Guid.NewGuid(), TestContext.Current.CancellationToken); + using JsonDocument read = JsonDocument.Parse($$"""{"path":{{JsonSerializer.Serialize(input)}},"mode":"whole"}"""); + ToolExecutionResult result = await backend.ExecuteAsync(taskId, "file_read", read.RootElement, + TestContext.Current.CancellationToken); + await backend.EndTaskAsync(taskId, result.Success, TestContext.Current.CancellationToken); + + Assert.True(result.Success); + Assert.Equal(0, factory.Starts); + } + + [Fact] public async Task Dir_mkdir_creates_directory_within_approved_root_and_cleans_up() { using TestDirectory directory = new(); @@ -1054,7 +1086,7 @@ public sealed class SecurityPolicyTests [new FilePolicyEntry(workspace, FileCapability.Read | FileCapability.Write | FileCapability.Delete)], [], [], [], new SandboxJobLimits(), "none", [], Path.Combine(directory.Path, "journals"), Path.Combine(directory.Path, "backups"), 16 * 1024 * 1024, 1024 * 1024, 1000, 10000); - var backend = new WindowsToolBackend(settings, audit); + var backend = new WindowsToolBackend(settings, audit, elevatedNetwork: new DirectElevatedNetworkSessionFactory()); Guid taskId = Guid.NewGuid(); await backend.BeginTaskAsync(taskId, Guid.NewGuid(), TestContext.Current.CancellationToken); using JsonDocument mkdir = JsonDocument.Parse($$"""{"path":{{JsonSerializer.Serialize(target)}},"operation":"mkdir"}"""); @@ -1085,7 +1117,7 @@ public sealed class SecurityPolicyTests [new FilePolicyEntry(workspace, FileCapability.Read | FileCapability.Write | FileCapability.Delete)], [], [], [], new SandboxJobLimits(), "none", [], Path.Combine(directory.Path, "journals"), Path.Combine(directory.Path, "backups"), 16 * 1024 * 1024, 1024 * 1024, 1000, 10000); - var backend = new WindowsToolBackend(settings, audit); + var backend = new WindowsToolBackend(settings, audit, elevatedNetwork: new DirectElevatedNetworkSessionFactory()); Guid taskId = Guid.NewGuid(); await backend.BeginTaskAsync(taskId, Guid.NewGuid(), TestContext.Current.CancellationToken); using JsonDocument rmdir = JsonDocument.Parse($$"""{"path":{{JsonSerializer.Serialize(target)}},"operation":"rmdir"}"""); @@ -1113,7 +1145,7 @@ public sealed class SecurityPolicyTests [new FilePolicyEntry(workspace, FileCapability.Read | FileCapability.Write | FileCapability.Delete)], [], [], [], new SandboxJobLimits(), "none", [], Path.Combine(directory.Path, "journals"), Path.Combine(directory.Path, "backups"), 16 * 1024 * 1024, 1024 * 1024, 1000, 10000); - var backend = new WindowsToolBackend(settings, audit); + var backend = new WindowsToolBackend(settings, audit, elevatedNetwork: new DirectElevatedNetworkSessionFactory()); Guid taskId = Guid.NewGuid(); await backend.BeginTaskAsync(taskId, Guid.NewGuid(), TestContext.Current.CancellationToken); using JsonDocument rmdir = JsonDocument.Parse($$"""{"path":{{JsonSerializer.Serialize(target)}},"operation":"rmdir"}"""); @@ -1140,7 +1172,7 @@ public sealed class SecurityPolicyTests [new FilePolicyEntry(workspace, FileCapability.Read)], [], [], [], new SandboxJobLimits(), "none", [], Path.Combine(directory.Path, "journals"), Path.Combine(directory.Path, "backups"), 16 * 1024 * 1024, 1024 * 1024, 1000, 10000); - var backend = new WindowsToolBackend(settings, audit); + var backend = new WindowsToolBackend(settings, audit, elevatedNetwork: new DirectElevatedNetworkSessionFactory()); Guid taskId = Guid.NewGuid(); await backend.BeginTaskAsync(taskId, Guid.NewGuid(), TestContext.Current.CancellationToken); using JsonDocument list = JsonDocument.Parse($$"""{"path":{{JsonSerializer.Serialize(workspace)}},"operation":"list"}"""); @@ -1166,7 +1198,7 @@ public sealed class SecurityPolicyTests [new FilePolicyEntry(workspace, FileCapability.Read | FileCapability.Write)], [], [], [], new SandboxJobLimits(), "none", [], Path.Combine(directory.Path, "journals"), Path.Combine(directory.Path, "backups"), 16 * 1024 * 1024, 1024 * 1024, 1000, 10000); - var backend = new WindowsToolBackend(settings, audit); + var backend = new WindowsToolBackend(settings, audit, elevatedNetwork: new DirectElevatedNetworkSessionFactory()); Guid taskId = Guid.NewGuid(); await backend.BeginTaskAsync(taskId, Guid.NewGuid(), TestContext.Current.CancellationToken); string badTarget = Path.Combine(outside.Path, "escaped"); @@ -1192,7 +1224,7 @@ public sealed class SecurityPolicyTests [new FilePolicyEntry(workspace, FileCapability.Read)], [], [], [], new SandboxJobLimits(), "none", [], Path.Combine(directory.Path, "journals"), Path.Combine(directory.Path, "backups"), 16 * 1024 * 1024, 1024 * 1024, 1000, 10000); - var backend = new WindowsToolBackend(settings, audit); + var backend = new WindowsToolBackend(settings, audit, elevatedNetwork: new DirectElevatedNetworkSessionFactory()); Guid taskId = Guid.NewGuid(); await backend.BeginTaskAsync(taskId, Guid.NewGuid(), TestContext.Current.CancellationToken); using JsonDocument tail = JsonDocument.Parse($$"""{"root":{{JsonSerializer.Serialize(binaryFile)}},"mode":"tail","query":"","tail_lines":10}"""); @@ -1231,7 +1263,7 @@ public sealed class SecurityPolicyTests [new FilePolicyEntry(workspace, FileCapability.Read)], [], [], [], new SandboxJobLimits(), "none", [], Path.Combine(directory.Path, "journals"), Path.Combine(directory.Path, "backups"), 16 * 1024 * 1024, 1024 * 1024, 1000, 10000); - var backend = new WindowsToolBackend(settings, audit); + var backend = new WindowsToolBackend(settings, audit, elevatedNetwork: new DirectElevatedNetworkSessionFactory()); Guid taskId = Guid.NewGuid(); await backend.BeginTaskAsync(taskId, Guid.NewGuid(), TestContext.Current.CancellationToken); using JsonDocument tail = JsonDocument.Parse($$"""{"root":{{JsonSerializer.Serialize(textFile)}},"mode":"tail","query":"","tail_lines":10}"""); @@ -1256,7 +1288,7 @@ public sealed class SecurityPolicyTests [new FilePolicyEntry(workspace, FileCapability.Read | FileCapability.Write | FileCapability.Delete)], [], [], [], new SandboxJobLimits(), "none", [], Path.Combine(directory.Path, "journals"), Path.Combine(directory.Path, "backups"), 16 * 1024 * 1024, 1024 * 1024, 1000, 10000); - var backend = new WindowsToolBackend(settings, audit); + var backend = new WindowsToolBackend(settings, audit, elevatedNetwork: new DirectElevatedNetworkSessionFactory()); Guid taskId = Guid.NewGuid(); await backend.BeginTaskAsync(taskId, Guid.NewGuid(), TestContext.Current.CancellationToken); using JsonDocument rename = JsonDocument.Parse($$"""{"path":{{JsonSerializer.Serialize(source)}},"operation":"rename","new_path":{{JsonSerializer.Serialize(dest)}}}"""); @@ -1287,7 +1319,7 @@ public sealed class SecurityPolicyTests [new FilePolicyEntry(workspace, FileCapability.Read | FileCapability.Write)], [], [], [], new SandboxJobLimits(), "none", [], Path.Combine(directory.Path, "journals"), Path.Combine(directory.Path, "backups"), 16 * 1024 * 1024, 1024 * 1024, 1000, 10000); - var backend = new WindowsToolBackend(settings, audit); + var backend = new WindowsToolBackend(settings, audit, elevatedNetwork: new DirectElevatedNetworkSessionFactory()); Guid taskId = Guid.NewGuid(); await backend.BeginTaskAsync(taskId, Guid.NewGuid(), TestContext.Current.CancellationToken); using JsonDocument rename = JsonDocument.Parse($$"""{"path":{{JsonSerializer.Serialize(source)}},"operation":"rename","new_path":{{JsonSerializer.Serialize(dest)}}}"""); @@ -1317,7 +1349,7 @@ public sealed class SecurityPolicyTests [new FilePolicyEntry(workspace, FileCapability.Read | FileCapability.Write)], [], [], [], new SandboxJobLimits(), "none", [], Path.Combine(directory.Path, "journals"), Path.Combine(directory.Path, "backups"), 16 * 1024 * 1024, 1024 * 1024, 1000, 10000); - var backend = new WindowsToolBackend(settings, audit); + var backend = new WindowsToolBackend(settings, audit, elevatedNetwork: new DirectElevatedNetworkSessionFactory()); Guid taskId = Guid.NewGuid(); await backend.BeginTaskAsync(taskId, Guid.NewGuid(), TestContext.Current.CancellationToken); using JsonDocument rename = JsonDocument.Parse($$"""{"path":{{JsonSerializer.Serialize(source)}},"operation":"rename","new_path":{{JsonSerializer.Serialize(dest)}}}"""); @@ -1358,7 +1390,8 @@ public sealed class SecurityPolicyTests { Guid taskId = Guid.NewGuid(); TaskResourceJournal journal = TaskResourceJournal.Create(Path.Combine(directory.Path, "deny-journal"), taskId); - using (TaskNetworkPolicy.Apply(journal, profile.Sid, [], audit)) + await using (await TaskNetworkPolicy.ApplyAsync(new DirectElevatedNetworkSession(), journal, profile.Sid, + "none", [], audit, null, TestContext.Current.CancellationToken)) { RestrictedProcessResult denied = Curl(launcher, taskId, curl, deniedServer.Url, Environment.SystemDirectory); Assert.NotEqual(0u, denied.ExitCode); @@ -1383,7 +1416,8 @@ public sealed class SecurityPolicyTests [ new(allowedAddress, checked((ushort)allowedV4.Port)) ]; - using (TaskNetworkPolicy.Apply(allowJournal, profile.Sid, allowances, audit)) + await using (await TaskNetworkPolicy.ApplyAsync(new DirectElevatedNetworkSession(), allowJournal, profile.Sid, + "local", allowances, audit, null, TestContext.Current.CancellationToken)) { RestrictedProcessResult v4 = Curl(launcher, allowTaskId, curl, allowedV4.Url, Environment.SystemDirectory); RestrictedProcessResult wrongPort = Curl(launcher, allowTaskId, curl, deniedPort.Url, Environment.SystemDirectory); |
