[英]How to call net.pipe (named pipe) WCF services while impersonating in a Windows Service
我在从C#Windows服务通过Windows模拟通过net.pipe调用WCF服务时遇到问题。
背景
该服务从队列中读取并创建子应用程序域,每个子域都对从队列中提取的项目运行特定的模块。 我们将Windows服务称为“ JobQueueAgent”,并将每个模块称为“ Job”。 我将继续使用这些术语。 可以将作业配置为以指定用户身份运行。 我们在工作的应用程序域内使用模拟来完成此任务。 以下是服务中的逻辑和凭证流:
JobQueueAgent(Windows服务–主用户)>>创建作业域>> Job Domain(应用程序域)>>模拟子用户>>通过模拟在线程上运行作业>> Job(模块–子用户)>>作业逻辑
“主要用户”和“子用户”都是具有“作为服务登录”权限的域帐户。
该服务在运行Windows Server 2012 R2的虚拟服务器上运行。
以下是我正在使用的C#模拟代码:
namespace JobQueue.WindowsServices
{
using System;
using System.ComponentModel;
using System.Net;
using System.Runtime.InteropServices;
using System.Security.Authentication;
using System.Security.Permissions;
using System.Security.Principal;
internal sealed class ImpersonatedIdentity : IDisposable
{
[PermissionSetAttribute(SecurityAction.Demand, Name = "FullTrust")]
public ImpersonatedIdentity(NetworkCredential credential)
{
if (credential == null) throw new ArgumentNullException("credential");
if (LogonUser(credential.UserName, credential.Domain, credential.Password, 5, 0, out _handle))
{
_context = WindowsIdentity.Impersonate(_handle);
}
else
{
throw new AuthenticationException("Impersonation failed.", newWin32Exception(Marshal.GetLastWin32Error()));
}
}
~ImpersonatedIdentity()
{
Dispose();
}
public void Dispose()
{
if (_handle != IntPtr.Zero)
{
CloseHandle(_handle);
_handle = IntPtr.Zero;
}
if (_context != null)
{
_context.Undo();
_context.Dispose();
_context = null;
}
GC.SuppressFinalize(this);
}
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool LogonUser(string userName, string domain, string password, int logonType,int logonProvider, out IntPtr handle);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CloseHandle(IntPtr handle);
private IntPtr _handle = IntPtr.Zero;
private WindowsImpersonationContext _context;
}
}
问题
需要一些作业才能对服务器上运行的另一个Windows服务进行net.pipe WCF服务调用。 在模拟下运行时,net.pipe调用失败。
在这种情况下,这是我的例外情况:
未处理的异常:System.ComponentModel.Win32Exception:访问被拒绝
服务器堆栈跟踪:在System.ServiceModel.Channels.AppContainerInfo.RunningInAppContainer()在System.ServiceModel.Channels.AppContainerInfo.get_IsRunningInAppContainer()在System.ServiceModel.Channels.AppContainerInfo.GetCurrentProcessToken()在System.ServiceModel.Channels.PipeSharedMemory.BuildPipeName (字符串pipeGuid)
如果没有模拟运行,net.pipe将成功。 将模拟用户添加到Administrators组时,net.pipe调用也会成功。 这意味着在模拟过程中,用户需要有一些特权才能拨打电话。 我们无法确定用户在模拟时进行net.pipe调用所需的策略,特权或访问权限。 使用户成为管理员是不可接受的。
这是一个已知的问题? 用户需要获得成功的特别权利吗? 我可以进行代码更改来解决此问题吗? 在具有impersonate = true的网站中使用WCF的net.pipe似乎表明由于NetworkService,这在ASP.NET应用程序中将不起作用。 不确定,但这不应该在这里适用。
在Microsoft支持的帮助下,我能够通过修改线程身份的访问权限来解决此问题(Harry Johnston在另一个答案中提出了建议)。 这是我现在正在使用的模拟代码:
using System;
using System.ComponentModel;
using System.Net;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Security.Authentication;
using System.Security.Permissions;
using System.Security.Principal;
internal sealed class ImpersonatedIdentity : IDisposable
{
[PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
public ImpersonatedIdentity(NetworkCredential credential)
{
if (credential == null) throw new ArgumentNullException(nameof(credential));
_processIdentity = WindowsIdentity.GetCurrent();
var tokenSecurity = new TokenSecurity(new SafeTokenHandleRef(_processIdentity.Token), AccessControlSections.Access);
if (!LogonUser(credential.UserName, credential.Domain, credential.Password, 5, 0, out _token))
{
throw new AuthenticationException("Impersonation failed.", new Win32Exception(Marshal.GetLastWin32Error()));
}
_threadIdentity = new WindowsIdentity(_token);
tokenSecurity.AddAccessRule(new AccessRule<TokenRights>(_threadIdentity.User, TokenRights.TOKEN_QUERY, InheritanceFlags.None, PropagationFlags.None, AccessControlType.Allow));
tokenSecurity.ApplyChanges();
_context = _threadIdentity.Impersonate();
}
~ImpersonatedIdentity()
{
Dispose();
}
public void Dispose()
{
if (_processIdentity != null)
{
_processIdentity.Dispose();
_processIdentity = null;
}
if (_token != IntPtr.Zero)
{
CloseHandle(_token);
_token = IntPtr.Zero;
}
if (_context != null)
{
_context.Undo();
_context.Dispose();
_context = null;
}
GC.SuppressFinalize(this);
}
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool LogonUser(string userName, string domain, string password, int logonType, int logonProvider, out IntPtr handle);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CloseHandle(IntPtr handle);
private WindowsIdentity _processIdentity;
private WindowsIdentity _threadIdentity;
private IntPtr _token = IntPtr.Zero;
private WindowsImpersonationContext _context;
[Flags]
private enum TokenRights
{
TOKEN_QUERY = 8
}
private class TokenSecurity : ObjectSecurity<TokenRights>
{
public TokenSecurity(SafeHandle safeHandle, AccessControlSections includeSections)
: base(false, ResourceType.KernelObject, safeHandle, includeSections)
{
_safeHandle = safeHandle;
}
public void ApplyChanges()
{
Persist(_safeHandle);
}
private readonly SafeHandle _safeHandle;
}
private class SafeTokenHandleRef : SafeHandle
{
public SafeTokenHandleRef(IntPtr handle)
: base(IntPtr.Zero, false)
{
SetHandle(handle);
}
public override bool IsInvalid
{
get { return handle == IntPtr.Zero || handle == new IntPtr(-1); }
}
protected override bool ReleaseHandle()
{
throw new NotImplementedException();
}
}
}
嗯,这是问题所在:
服务器堆栈跟踪:位于System.ServiceModel.Channels.AppContainerInfo.GetCurrentProcessToken()
当您尝试打开管道时,系统会检查您是否在应用程序容器中。 这涉及查询流程令牌,您所模拟的用户无权执行该令牌。
对我来说,这似乎是一个错误。 您可以尝试与Microsoft展开付费支持案例,但不能保证他们会发布修补程序,也不能保证他们能够尽快解决问题以满足您的需求。
因此,我看到了两个可行的解决方法:
模拟之前,更改进程访问令牌上的ACL,以授予TOKEN_QUERY
访问新登录令牌的权限。 我认为登录令牌将包含登录SID,因此这是最安全的选择,但授予用户帐户访问权限应该不会太危险。 据我所知, TOKEN_QUERY
访问权限不会泄露任何特别敏感的信息。
您可以在子用户的上下文中启动子进程,而不是使用模拟。 效率较低,使用也不方便,但这将是解决问题的简单方法。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.