diff --git a/docs/GatewayConfiguration.md b/docs/GatewayConfiguration.md
index 618a2f2..378a734 100644
--- a/docs/GatewayConfiguration.md
+++ b/docs/GatewayConfiguration.md
@@ -743,8 +743,8 @@ gateway↔worker link is a per-session **named pipe**
(`mxgw-{gatewayPid}-{sessionUid}`), not a network socket. It is not
TLS-encrypted and does not need to be: it never leaves the local Windows host and
is secured by the OS pipe ACL: on Windows the gateway creates each pipe with a
-protected DACL that grants only its own account (the account the worker is
-launched as) and denies network logons; on the non-Windows test matrix it uses
+protected DACL whose only entry grants its own account (the account the worker
+is launched as); on the non-Windows test matrix it uses
`PipeOptions.CurrentUserOnly`. See `WorkerPipeSecurity` and [Worker Frame Protocol](./WorkerFrameProtocol.md).
## Related Documentation
diff --git a/src/ZB.MOM.WW.MxGateway.Server/Sessions/WorkerPipeSecurity.cs b/src/ZB.MOM.WW.MxGateway.Server/Sessions/WorkerPipeSecurity.cs
index f095ab0..b99bf29 100644
--- a/src/ZB.MOM.WW.MxGateway.Server/Sessions/WorkerPipeSecurity.cs
+++ b/src/ZB.MOM.WW.MxGateway.Server/Sessions/WorkerPipeSecurity.cs
@@ -10,8 +10,13 @@ namespace ZB.MOM.WW.MxGateway.Server.Sessions;
/// credential-bearing commands (secured writes, AuthenticateUser, alarm-ack passwords), so
/// its unguessable name is not enough: only the account the gateway runs as — which is also the
/// account the worker is launched as, because the launcher sets no alternate credentials — may
-/// open it, and never over the network.
+/// open it.
///
+///
+/// There is deliberately no deny entry for NT AUTHORITY\NETWORK. That SID marks a network
+/// logon, not a remote caller: a gateway started from an SSH or WinRM session carries it in
+/// its own token, so a deny entry locked its own same-machine workers out (found on windev, #187).
+///
internal static class WorkerPipeSecurity
{
/// Gets the options every worker pipe is created with on non-Windows hosts.
@@ -34,8 +39,8 @@ internal static class WorkerPipeSecurity
}
///
- /// Creates a pipe ACL that grants full control to only, denies network
- /// logons, and does not inherit any other entries.
+ /// Creates a pipe ACL that grants full control to only and does not
+ /// inherit any other entries.
///
/// The account the gateway and its workers run as.
/// The pipe security descriptor.
@@ -51,12 +56,6 @@ internal static class WorkerPipeSecurity
security.SetOwner(user);
security.AddAccessRule(new PipeAccessRule(user, PipeAccessRights.FullControl, AccessControlType.Allow));
- // A network logon of the same account still carries the user SID; the deny ACE keeps the
- // pipe local-only regardless.
- security.AddAccessRule(new PipeAccessRule(
- new SecurityIdentifier(WellKnownSidType.NetworkSid, domainSid: null),
- PipeAccessRights.FullControl,
- AccessControlType.Deny));
return security;
}
diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Sessions/WorkerPipeSecurityTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Sessions/WorkerPipeSecurityTests.cs
index 58b32a2..ba6ace2 100644
--- a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Sessions/WorkerPipeSecurityTests.cs
+++ b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Sessions/WorkerPipeSecurityTests.cs
@@ -13,10 +13,10 @@ namespace ZB.MOM.WW.MxGateway.Tests.Gateway.Sessions;
public sealed class WorkerPipeSecurityTests
{
- /// Verifies the factory grants only the given user and denies network logons.
+ /// Verifies the factory grants only the given user.
[WindowsFact]
[SupportedOSPlatform("windows")]
- public void Create_GrantsOnlyTheUserAndDeniesNetwork()
+ public void Create_GrantsOnlyTheUser()
{
SecurityIdentifier user = new("S-1-5-21-1000-2000-3000-1001");
@@ -24,7 +24,7 @@ public sealed class WorkerPipeSecurityTests
Assert.True(security.AreAccessRulesProtected);
Assert.Equal(user, security.GetOwner(typeof(SecurityIdentifier)));
- AssertOnlyUserAllowedAndNetworkDenied(security, user);
+ AssertOnlyUserAllowed(security, user);
}
///
@@ -41,12 +41,14 @@ public sealed class WorkerPipeSecurityTests
using NamedPipeServerStream pipe = SessionWorkerClientFactory.CreatePipe(NewPipeName());
PipeSecurity applied = pipe.GetAccessControl();
- AssertOnlyUserAllowedAndNetworkDenied(applied, user);
+ AssertOnlyUserAllowed(applied, user);
}
///
/// Verifies the pipe still accepts a client running as the same user — the worker is launched
/// under the gateway's own account, so the restriction must not break the session handshake.
+ /// On windev this runs from an SSH (network-logon) session, which is what caught the earlier
+ /// deny-NETWORK entry.
///
[Fact]
public async Task CreatePipe_AcceptsClientRunningAsTheSameUser()
@@ -77,24 +79,22 @@ public sealed class WorkerPipeSecurityTests
}
[SupportedOSPlatform("windows")]
- private static void AssertOnlyUserAllowedAndNetworkDenied(PipeSecurity security, SecurityIdentifier user)
+ private static void AssertOnlyUserAllowed(PipeSecurity security, SecurityIdentifier user)
{
SecurityIdentifier everyoneSid = new(WellKnownSidType.WorldSid, null);
SecurityIdentifier anonymousSid = new(WellKnownSidType.AnonymousSid, null);
- SecurityIdentifier networkSid = new(WellKnownSidType.NetworkSid, null);
List rules = security
.GetAccessRules(includeExplicit: true, includeInherited: true, typeof(SecurityIdentifier))
.Cast()
.ToList();
- PipeAccessRule[] allows = rules.Where(r => r.AccessControlType == AccessControlType.Allow).ToArray();
- PipeAccessRule allow = Assert.Single(allows);
+ // Exactly one entry: an allow for the gateway's user. In particular no deny entry — a deny
+ // for NETWORK locked out same-machine workers when the gateway ran from an SSH logon.
+ PipeAccessRule allow = Assert.Single(rules);
+ Assert.Equal(AccessControlType.Allow, allow.AccessControlType);
Assert.Equal(user, allow.IdentityReference);
Assert.Equal(PipeAccessRights.FullControl, allow.PipeAccessRights & PipeAccessRights.FullControl);
- PipeAccessRule deny = Assert.Single(rules, r => r.AccessControlType == AccessControlType.Deny);
- Assert.Equal(networkSid, deny.IdentityReference);
-
Assert.DoesNotContain(rules, r => r.IdentityReference == everyoneSid && r.AccessControlType == AccessControlType.Allow);
Assert.DoesNotContain(rules, r => r.IdentityReference == anonymousSid && r.AccessControlType == AccessControlType.Allow);
}