summaryrefslogtreecommitdiff
path: root/src/Agent.Windows/Security/TaskAclManager.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/Agent.Windows/Security/TaskAclManager.cs')
-rw-r--r--src/Agent.Windows/Security/TaskAclManager.cs74
1 files changed, 61 insertions, 13 deletions
diff --git a/src/Agent.Windows/Security/TaskAclManager.cs b/src/Agent.Windows/Security/TaskAclManager.cs
index 2b091a6..be0080d 100644
--- a/src/Agent.Windows/Security/TaskAclManager.cs
+++ b/src/Agent.Windows/Security/TaskAclManager.cs
@@ -10,27 +10,64 @@ public sealed class TaskAclManager(SecurityIdentifier appContainerSid, ISecurity
{
public AclSnapshot Grant(Guid taskId, string path, FileCapability capabilities)
{
- string canonical = CanonicalPath.Resolve(path);
+ string canonical = ResolvedPath.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);
+ FileSystemRights auditRights;
+ if (directory)
+ {
+ FileSystemRights directoryRights = ToDirectoryRights(capabilities);
+ FileSystemRights fileRights = ToFileRights(capabilities);
+ if (directoryRights == 0 || fileRights == 0) throw new ArgumentOutOfRangeException(nameof(capabilities));
+ security.AddAccessRule(new FileSystemAccessRule(appContainerSid, directoryRights,
+ InheritanceFlags.ContainerInherit, PropagationFlags.None, AccessControlType.Allow));
+ security.AddAccessRule(new FileSystemAccessRule(appContainerSid, fileRights,
+ InheritanceFlags.ObjectInherit, PropagationFlags.InheritOnly, AccessControlType.Allow));
+ auditRights = directoryRights | fileRights;
+ }
+ else
+ {
+ auditRights = ToFileRights(capabilities);
+ if (auditRights == 0) throw new ArgumentOutOfRangeException(nameof(capabilities));
+ security.AddAccessRule(new FileSystemAccessRule(appContainerSid, auditRights,
+ InheritanceFlags.None, PropagationFlags.None, AccessControlType.Allow));
+ }
+ try
+ {
+ WriteSecurity(canonical, security);
+ if (journalEntry is not null) journal!.MarkApplied(journalEntry.Value);
+ }
+ catch (Exception applyFailure)
+ {
+ try
+ {
+ byte[] current = ReadSecurity(canonical).GetSecurityDescriptorBinaryForm();
+ if (!current.AsSpan().SequenceEqual(original))
+ {
+ FileSystemSecurity rollback = Directory.Exists(canonical) ? new DirectorySecurity() : new FileSecurity();
+ rollback.SetSecurityDescriptorBinaryForm(original, AccessControlSections.Access);
+ WriteSecurity(canonical, rollback);
+ byte[] restored = ReadSecurity(canonical).GetSecurityDescriptorBinaryForm();
+ if (!restored.AsSpan().SequenceEqual(original)) throw new InvalidOperationException("ACL rollback verification failed.");
+ }
+ if (journalEntry is not null) journal!.MarkCleaned(journalEntry.Value);
+ }
+ catch (Exception rollbackFailure)
+ {
+ throw new AggregateException("ACL apply failed and immediate rollback did not reach a verified clean state.", applyFailure, rollbackFailure);
+ }
+ throw;
+ }
audit.Write(new(DateTimeOffset.UtcNow, taskId, "acl.apply", "applied", new Dictionary<string, string>
{
["path"] = canonical,
["sid"] = appContainerSid.Value,
- ["rights"] = rights.ToString(),
+ ["rights"] = auditRights.ToString(),
["scope"] = directory ? "root_inheritance" : "file"
}));
return new(canonical, original, journalEntry);
@@ -55,12 +92,23 @@ public sealed class TaskAclManager(SecurityIdentifier appContainerSid, ISecurity
else FileSystemAclExtensions.SetAccessControl(new FileInfo(path), (FileSecurity)security);
}
- private static FileSystemRights ToRights(FileCapability capabilities)
+ private static FileSystemRights ToFileRights(FileCapability capabilities)
{
FileSystemRights rights = 0;
- if ((capabilities & FileCapability.Read) != 0) rights |= FileSystemRights.ReadAndExecute | FileSystemRights.ListDirectory;
+ if ((capabilities & FileCapability.Read) != 0)
+ rights |= FileSystemRights.ReadData | FileSystemRights.ReadAttributes | FileSystemRights.ReadExtendedAttributes | FileSystemRights.ReadPermissions;
if ((capabilities & FileCapability.Write) != 0) rights |= FileSystemRights.Write;
- if ((capabilities & FileCapability.Execute) != 0) rights |= FileSystemRights.ReadAndExecute;
+ if ((capabilities & FileCapability.Execute) != 0) rights |= FileSystemRights.ExecuteFile | FileSystemRights.ReadData | FileSystemRights.ReadAttributes;
+ if ((capabilities & FileCapability.Delete) != 0) rights |= FileSystemRights.Delete;
+ return rights;
+ }
+
+ private static FileSystemRights ToDirectoryRights(FileCapability capabilities)
+ {
+ FileSystemRights rights = FileSystemRights.Traverse | FileSystemRights.ReadAttributes | FileSystemRights.ReadPermissions;
+ if ((capabilities & FileCapability.Read) != 0) rights |= FileSystemRights.ListDirectory | FileSystemRights.ReadExtendedAttributes;
+ if ((capabilities & FileCapability.Write) != 0) rights |= FileSystemRights.CreateFiles | FileSystemRights.CreateDirectories |
+ FileSystemRights.WriteAttributes | FileSystemRights.WriteExtendedAttributes;
if ((capabilities & FileCapability.Delete) != 0) rights |= FileSystemRights.Delete | FileSystemRights.DeleteSubdirectoriesAndFiles;
return rights;
}