summaryrefslogtreecommitdiff
path: root/src/Agent.Windows/Network/TaskNetworkPolicy.cs
blob: bc4d2913abc8e39e7eabbf6d327aa4271fc9863a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
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 readonly Guid? _sessionId;
    private bool _disposed;

    private TaskNetworkPolicy(WfpDynamicSession session, TaskResourceJournal journal, ISecurityAuditSink audit, Guid entryId,
        SecurityIdentifier appContainerSid, Guid loopbackEntryId, bool loopbackAdded, Guid? sessionId)
    {
        _session = session; _journal = journal; _audit = audit; _entryId = entryId;
        _appContainerSid = appContainerSid; _loopbackEntryId = loopbackEntryId; _loopbackAdded = loopbackAdded;
        _sessionId = sessionId;
    }

    public static TaskNetworkPolicy Apply(TaskResourceJournal journal, SecurityIdentifier appContainerSid, IReadOnlyList<NetworkEndpoint> allowances, ISecurityAuditSink audit, Guid? sessionId = null)
    {
        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, "network.rule.apply", "applied", new Dictionary<string, string>
            {
                ["ipv4_ipv6_default_block"] = "true", ["filter_count"] = ids.Count.ToString(), ["session"] = "dynamic"
            }, SessionId: sessionId));
            audit.Write(new(DateTimeOffset.UtcNow, journal.TaskId, "network.loopback.apply", loopbackAdded ? "added" : "preserved",
                new Dictionary<string, string> { ["app_container_sid"] = appContainerSid.Value }, SessionId: sessionId));
            return new(session, journal, audit, entryId, appContainerSid, loopbackEntryId, loopbackAdded, sessionId);
        }
        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, "network.rule.remove", "removed", new Dictionary<string, string> { ["residual_filters"] = "0" }, SessionId: _sessionId));
        _audit.Write(new(DateTimeOffset.UtcNow, _journal.TaskId, "network.loopback.remove", "removed",
            new Dictionary<string, string> { ["residual_exemptions"] = LoopbackExemption.Contains(_appContainerSid) ? "preexisting" : "0" }, SessionId: _sessionId));
        _disposed = true;
    }
}