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);
}