[英]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.