Repository navigation
Expand file tree
/
Copy pathManagedBridgeConfiguration.cs
More file actions
127 lines (118 loc) · 7.99 KB
/
Copy pathManagedBridgeConfiguration.cs
File metadata and controls
127 lines (118 loc) · 7.99 KB
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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
using System.Security.AccessControl;
using System.Security.Principal;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Runtime.Versioning;
namespace MissionController.Host;
[SupportedOSPlatform("windows")]
public sealed record ManagedBridgeConfiguration(
int Schema, string ServiceName, string ServiceSid, string PipeName, string Distribution,
int Port, string StateDirectory, string WorkspaceDirectory, string[] OperatorSids, string Egress = "",
string? AgentId = null, string? AgentVersion = null, string Substrate = ManagedBridgeConfiguration.LinuxLease)
{
/// <summary>The service fronts a WSL guest's lease daemon, which Distribution and Port name.</summary>
public const string LinuxLease = "linux-lease";
/// <summary>
/// Installed where WSL is unavailable: no guest exists, so Distribution is empty and Port is 0, and
/// the Linux-lease tools refuse. Configurations written before this existed omit the field and
/// read as <see cref="LinuxLease"/>, which is what every one of them is.
/// </summary>
public const string WindowsOnly = "windows-only";
public bool HasLinuxLease => Substrate == LinuxLease;
public static ManagedBridgeConfiguration Load(string path, bool serving = false)
{
path = Path.GetFullPath(path);
// Read permission is harmless; a writable config, executable parent, or junction is not.
RequireAdministratorOwnedPath(path);
var config = JsonSerializer.Deserialize<ManagedBridgeConfiguration>(File.ReadAllText(path))
?? throw new InvalidDataException("Empty managed configuration.");
config.Validate(serving);
if (serving && WindowsIdentity.GetCurrent().User?.Value != config.ServiceSid)
throw new UnauthorizedAccessException("The bridge must run under its configured service identity.");
if (serving)
{
RequireAdministratorOwnedPath(Environment.ProcessPath!);
config.RequirePrivateState();
}
return config;
}
internal void Validate(bool serving = false)
{
if ((AgentId is not null || AgentVersion is not null)
&& (!Regex.IsMatch(AgentId ?? "", @"\A[a-z][a-z0-9]*(?:-[a-z0-9]+)*\z")
|| AgentId!.Length > 48 || !Regex.IsMatch(AgentVersion ?? "", @"\A[0-9a-f]{64}\z")))
throw new InvalidDataException("Managed agent endpoints require an agent ID and approved contract version.");
if (Substrate is not (LinuxLease or WindowsOnly))
throw new InvalidDataException("Unknown managed substrate.");
// A Windows-only install created no guest. A distro or port in its configuration would point the
// service at a WSL instance the deployment never made and cannot vouch for.
bool guestNamed = HasLinuxLease
? Name(Distribution) && Port is >= 1024 and <= 65535
: string.IsNullOrEmpty(Distribution) && Port == 0;
if (Schema != 1 || !Name(ServiceName) || !Name(PipeName) || !guestNamed)
throw new InvalidDataException("Invalid managed bridge configuration.");
_ = new SecurityIdentifier(ServiceSid);
if (OperatorSids is not { Length: > 0 and <= 32 }) throw new InvalidDataException("Explicit operator SIDs are required.");
foreach (string sid in OperatorSids)
{
_ = new SecurityIdentifier(sid);
if (sid == ServiceSid || !sid.StartsWith("S-1-5-21-", StringComparison.Ordinal))
throw new InvalidDataException("Operators must be explicit account SIDs, distinct from the service.");
}
foreach (string directory in new[] { StateDirectory, WorkspaceDirectory })
if (!Path.IsPathFullyQualified(directory) || (serving && !Directory.Exists(directory)))
throw new InvalidDataException("Managed directories must already exist at absolute paths.");
string state = Path.GetFullPath(StateDirectory).TrimEnd(Path.DirectorySeparatorChar);
string workspace = Path.GetFullPath(WorkspaceDirectory).TrimEnd(Path.DirectorySeparatorChar);
if (state.Equals(workspace, StringComparison.OrdinalIgnoreCase) || state.StartsWith(workspace + "\\", StringComparison.OrdinalIgnoreCase)
|| workspace.StartsWith(state + "\\", StringComparison.OrdinalIgnoreCase))
throw new InvalidDataException("Workspace and private service state must be separate directories.");
_ = string.IsNullOrEmpty(Egress) ? McpEgressPolicy.None : McpEgressPolicy.Parse(Egress);
}
private static bool Name(string value) => Regex.IsMatch(value ?? "", @"\A[A-Za-z0-9][A-Za-z0-9._-]{0,63}\z");
internal static void RequireAdministratorOwnedPath(string path)
{
for (FileSystemInfo? entry = new FileInfo(path); entry is not null; entry = entry switch
{
FileInfo file => file.Directory,
DirectoryInfo directory => directory.Parent,
_ => null
})
{
if ((entry.Attributes & FileAttributes.ReparsePoint) != 0)
throw new UnauthorizedAccessException("Managed paths cannot traverse reparse points.");
var acl = entry is FileInfo f ? (FileSystemSecurity)f.GetAccessControl() : ((DirectoryInfo)entry).GetAccessControl();
var trusted = new HashSet<string>(StringComparer.Ordinal) { "S-1-5-18", "S-1-5-32-544", "S-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464" };
string owner = acl.GetOwner(typeof(SecurityIdentifier))!.Value;
if (!trusted.Contains(owner)) throw new UnauthorizedAccessException($"Managed path is not administrator-owned: {entry.FullName}");
const FileSystemRights fileWrites = FileSystemRights.WriteData | FileSystemRights.AppendData |
FileSystemRights.WriteAttributes | FileSystemRights.WriteExtendedAttributes | FileSystemRights.Delete |
FileSystemRights.DeleteSubdirectoriesAndFiles | FileSystemRights.ChangePermissions | FileSystemRights.TakeOwnership;
// ProgramData permits creation of siblings. That does not replace an existing protected
// child; DELETE_CHILD, ownership and ACL changes on an ancestor would permit replacement.
var writes = entry is FileInfo ? fileWrites : FileSystemRights.Delete |
FileSystemRights.DeleteSubdirectoriesAndFiles | FileSystemRights.ChangePermissions | FileSystemRights.TakeOwnership;
foreach (FileSystemAccessRule rule in acl.GetAccessRules(true, true, typeof(SecurityIdentifier)))
{
// Inherit-only ACEs do not grant access to this object (ProgramData has such entries).
if (rule.AccessControlType == AccessControlType.Allow && (rule.PropagationFlags & PropagationFlags.InheritOnly) == 0
&& (rule.FileSystemRights & writes) != 0 && !trusted.Contains(rule.IdentityReference.Value))
throw new UnauthorizedAccessException($"Untrusted write access to managed path: {entry.FullName}");
}
}
}
private void RequirePrivateState()
{
var directory = new DirectoryInfo(StateDirectory);
if ((directory.Attributes & FileAttributes.ReparsePoint) != 0)
throw new UnauthorizedAccessException("Private state cannot be a reparse point.");
RequireAdministratorOwnedPath(Path.Combine(directory.Parent!.FullName, "bridge.json"));
var trusted = new HashSet<string>(StringComparer.Ordinal) { ServiceSid, "S-1-5-18", "S-1-5-32-544" };
var acl = directory.GetAccessControl();
if (!trusted.Contains(acl.GetOwner(typeof(SecurityIdentifier))!.Value))
throw new UnauthorizedAccessException("Private state has an untrusted owner.");
foreach (FileSystemAccessRule rule in acl.GetAccessRules(true, true, typeof(SecurityIdentifier)))
if (rule.AccessControlType == AccessControlType.Allow && !trusted.Contains(rule.IdentityReference.Value))
throw new UnauthorizedAccessException("Private state must be accessible only to the service and administrators.");
}
}