簡體   English   中英

模擬Windows服務時如何調用net.pipe(命名管道)WCF服務

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

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM