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
76
|
using System.Security.AccessControl;
using System.Security.Principal;
using Agent.Windows.Recovery;
namespace Agent.Windows.Security;
public sealed record AclSnapshot(string Path, byte[] SecurityDescriptor, Guid? JournalEntryId = null);
public sealed class TaskAclManager(SecurityIdentifier appContainerSid, ISecurityAuditSink audit, TaskResourceJournal? journal = null)
{
public AclSnapshot Grant(Guid taskId, string path, FileCapability capabilities)
{
string canonical = CanonicalPath.Resolve(path);
FileSystemSecurity security = ReadSecurity(canonical);
byte[] original = security.GetSecurityDescriptorBinaryForm();
Guid? journalEntry = journal?.Prepare("acl", canonical, new Dictionary<string, string>
{
["security_descriptor"] = Convert.ToBase64String(original)
});
FileSystemRights rights = ToRights(capabilities);
if (rights == 0) throw new ArgumentOutOfRangeException(nameof(capabilities));
bool directory = Directory.Exists(canonical);
var rule = new FileSystemAccessRule(appContainerSid, rights,
directory ? InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit : InheritanceFlags.None,
PropagationFlags.None, AccessControlType.Allow);
security.AddAccessRule(rule);
WriteSecurity(canonical, security);
if (journalEntry is not null) journal!.MarkApplied(journalEntry.Value);
audit.Write(new(DateTimeOffset.UtcNow, taskId, "acl.apply", "applied", new Dictionary<string, string> { ["path"] = canonical, ["sid"] = appContainerSid.Value, ["rights"] = rights.ToString() }));
return new(canonical, original, journalEntry);
}
public void Restore(Guid taskId, AclSnapshot snapshot)
{
FileSystemSecurity security = Directory.Exists(snapshot.Path) ? new DirectorySecurity() : new FileSecurity();
security.SetSecurityDescriptorBinaryForm(snapshot.SecurityDescriptor, AccessControlSections.Access);
WriteSecurity(snapshot.Path, security);
if (snapshot.JournalEntryId is not null) journal!.MarkCleaned(snapshot.JournalEntryId.Value);
audit.Write(new(DateTimeOffset.UtcNow, taskId, "acl.restore", "restored", new Dictionary<string, string> { ["path"] = snapshot.Path }));
}
public void NormalizeNewEntry(Guid taskId, string path)
{
string canonical = CanonicalPath.Resolve(path);
FileSystemSecurity security = ReadSecurity(canonical);
security.SetAccessRuleProtection(isProtected: true, preserveInheritance: false);
foreach (FileSystemAccessRule rule in security.GetAccessRules(includeExplicit: true, includeInherited: false, typeof(SecurityIdentifier))
.OfType<FileSystemAccessRule>()
.Where(rule => rule.IdentityReference == appContainerSid)
.ToArray())
security.RemoveAccessRuleSpecific(rule);
security.SetAccessRuleProtection(isProtected: false, preserveInheritance: false);
WriteSecurity(canonical, security);
audit.Write(new(DateTimeOffset.UtcNow, taskId, "acl.normalize", "normalized", new Dictionary<string, string> { ["path"] = canonical }));
}
private static FileSystemSecurity ReadSecurity(string path) => Directory.Exists(path)
? FileSystemAclExtensions.GetAccessControl(new DirectoryInfo(path), AccessControlSections.Access)
: FileSystemAclExtensions.GetAccessControl(new FileInfo(path), AccessControlSections.Access);
private static void WriteSecurity(string path, FileSystemSecurity security)
{
if (security is DirectorySecurity directorySecurity) FileSystemAclExtensions.SetAccessControl(new DirectoryInfo(path), directorySecurity);
else FileSystemAclExtensions.SetAccessControl(new FileInfo(path), (FileSecurity)security);
}
private static FileSystemRights ToRights(FileCapability capabilities)
{
FileSystemRights rights = 0;
if ((capabilities & FileCapability.Read) != 0) rights |= FileSystemRights.ReadAndExecute | FileSystemRights.ListDirectory;
if ((capabilities & FileCapability.Write) != 0) rights |= FileSystemRights.Write;
if ((capabilities & FileCapability.Execute) != 0) rights |= FileSystemRights.ReadAndExecute;
if ((capabilities & FileCapability.Delete) != 0) rights |= FileSystemRights.Delete | FileSystemRights.DeleteSubdirectoriesAndFiles;
return rights;
}
}
|