using System.IO.Pipes; using System.Runtime.Versioning; using System.Security.AccessControl; using System.Security.Principal; namespace ZB.MOM.WW.OtOpcUa.Core.Scripting.OutOfProcess; /// /// The ScriptWorker pipe's explicit ACL on Windows (lmxopcua#780 design ยง6, "an explicit ACL when the worker runs as a /// different account"): a protected DACL that admits the host account and โ€” when Scripting:Worker:Identity is /// set โ€” the worker identity, each with read/write, and nobody else (no SYSTEM, no Administrators, no Everyone). The /// owner is the host token's owner, as .NET's PipeOptions.CurrentUserOnly sets it, so the worker's /// CurrentUserOnly client check (pipe owner == its token owner, which a restricted token keeps) still /// authenticates the server end. /// [SupportedOSPlatform("windows")] internal static class WorkerPipeSecurity { /// The rights each admitted principal gets: read/write data, attributes and the security descriptor (read). internal const PipeAccessRights GrantedRights = PipeAccessRights.ReadWrite; /// Builds the pipe ACL for this host process. /// The worker identity's SID string, or null when the worker keeps the host's token. /// The pipe security. public static PipeSecurity ForCurrentHost(string? workerSid) { using var identity = WindowsIdentity.GetCurrent(); var owner = identity.Owner ?? identity.User ?? throw new InvalidOperationException("the host token has no owner"); var user = identity.User ?? owner; return Create(owner, user, workerSid is null ? null : new SecurityIdentifier(workerSid)); } /// Builds the pipe ACL. /// The pipe owner (the host token's owner). /// The host account (its token's user SID). /// The worker identity, or null. /// The pipe security: protected, allow-only, one ACE for the host account and one for the worker identity. public static PipeSecurity Create(SecurityIdentifier owner, SecurityIdentifier hostUser, SecurityIdentifier? worker) { var security = new PipeSecurity(); security.SetAccessRuleProtection(isProtected: true, preserveInheritance: false); security.SetOwner(owner); // The host account and the worker identity, nothing else: not the owner when it differs (an elevated host's // owner is BUILTIN\Administrators โ€” .NET's CurrentUserOnly would grant it; the owner keeps only its implicit // READ_CONTROL / WRITE_DAC). var principals = new List { hostUser }; if (worker is not null && !principals.Contains(worker)) principals.Add(worker); foreach (var principal in principals) security.AddAccessRule(new PipeAccessRule(principal, GrantedRights, AccessControlType.Allow)); return security; } /// Creates the single-instance server end of a worker pipe with . /// The random pipe name. /// The ACL (). /// The server stream. public static NamedPipeServerStream CreateServer(string pipeName, PipeSecurity security) => NamedPipeServerStreamAcl.Create(pipeName, PipeDirection.InOut, maxNumberOfServerInstances: 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous, inBufferSize: 0, outBufferSize: 0, security); }