summaryrefslogtreecommitdiff
path: root/src/Agent.Windows/Network/TaskNetworkPolicy.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/Agent.Windows/Network/TaskNetworkPolicy.cs')
-rw-r--r--src/Agent.Windows/Network/TaskNetworkPolicy.cs73
1 files changed, 73 insertions, 0 deletions
diff --git a/src/Agent.Windows/Network/TaskNetworkPolicy.cs b/src/Agent.Windows/Network/TaskNetworkPolicy.cs
new file mode 100644
index 0000000..59b2602
--- /dev/null
+++ b/src/Agent.Windows/Network/TaskNetworkPolicy.cs
@@ -0,0 +1,73 @@
+using System.Security.Principal;
+using Agent.Windows.Recovery;
+using Agent.Windows.Security;
+
+namespace Agent.Windows.Network;
+
+public sealed class TaskNetworkPolicy : IDisposable
+{
+ private readonly WfpDynamicSession _session;
+ private readonly TaskResourceJournal _journal;
+ private readonly ISecurityAuditSink _audit;
+ private readonly Guid _entryId;
+ private readonly SecurityIdentifier _appContainerSid;
+ private readonly Guid _loopbackEntryId;
+ private readonly bool _loopbackAdded;
+ private bool _disposed;
+
+ private TaskNetworkPolicy(WfpDynamicSession session, TaskResourceJournal journal, ISecurityAuditSink audit, Guid entryId,
+ SecurityIdentifier appContainerSid, Guid loopbackEntryId, bool loopbackAdded)
+ {
+ _session = session; _journal = journal; _audit = audit; _entryId = entryId;
+ _appContainerSid = appContainerSid; _loopbackEntryId = loopbackEntryId; _loopbackAdded = loopbackAdded;
+ }
+
+ public static TaskNetworkPolicy Apply(TaskResourceJournal journal, SecurityIdentifier appContainerSid, IReadOnlyList<NetworkEndpoint> allowances, ISecurityAuditSink audit)
+ {
+ bool originallyExempt = LoopbackExemption.Contains(appContainerSid);
+ Guid loopbackEntryId = journal.Prepare("network.loopback_exemption", appContainerSid.Value,
+ new Dictionary<string, string> { ["originally_present"] = originallyExempt.ToString() });
+ bool loopbackAdded = false;
+ Guid entryId = journal.Prepare("wfp.dynamic", appContainerSid.Value, new Dictionary<string, string>
+ {
+ ["session"] = "dynamic", ["allowances"] = string.Join(',', allowances.Select(static item => $"{item.Address}:{item.Port}"))
+ });
+ WfpDynamicSession session = WfpDynamicSession.Open();
+ try
+ {
+ loopbackAdded = LoopbackExemption.Ensure(appContainerSid);
+ journal.MarkApplied(loopbackEntryId, new Dictionary<string, string> { ["added"] = loopbackAdded.ToString() });
+ IReadOnlyList<ulong> ids = session.ApplyDefaultBlock(appContainerSid, allowances);
+ journal.MarkApplied(entryId, new Dictionary<string, string> { ["filter_ids"] = string.Join(',', ids) });
+ audit.Write(new(DateTimeOffset.UtcNow, journal.TaskId, "wfp.apply", "applied", new Dictionary<string, string>
+ {
+ ["ipv4_ipv6_default_block"] = "true", ["filter_count"] = ids.Count.ToString(), ["session"] = "dynamic"
+ }));
+ audit.Write(new(DateTimeOffset.UtcNow, journal.TaskId, "network.loopback.apply", loopbackAdded ? "added" : "preserved",
+ new Dictionary<string, string> { ["app_container_sid"] = appContainerSid.Value }));
+ return new(session, journal, audit, entryId, appContainerSid, loopbackEntryId, loopbackAdded);
+ }
+ catch
+ {
+ session.Dispose();
+ journal.MarkCleaned(entryId);
+ if (loopbackAdded) LoopbackExemption.Remove(appContainerSid);
+ journal.MarkCleaned(loopbackEntryId);
+ throw;
+ }
+ }
+
+ public void Dispose()
+ {
+ if (_disposed) return;
+ _session.RemovePolicy();
+ _session.Dispose();
+ _journal.MarkCleaned(_entryId);
+ if (_loopbackAdded) LoopbackExemption.Remove(_appContainerSid);
+ _journal.MarkCleaned(_loopbackEntryId);
+ _audit.Write(new(DateTimeOffset.UtcNow, _journal.TaskId, "wfp.remove", "removed", new Dictionary<string, string> { ["residual_filters"] = "0" }));
+ _audit.Write(new(DateTimeOffset.UtcNow, _journal.TaskId, "network.loopback.remove", "removed",
+ new Dictionary<string, string> { ["residual_exemptions"] = LoopbackExemption.Contains(_appContainerSid) ? "preexisting" : "0" }));
+ _disposed = true;
+ }
+}