简体   繁体   English

模拟Windows服务时如何调用net.pipe(命名管道)WCF服务

[英]How to call net.pipe (named pipe) WCF services while impersonating in a Windows Service

I am having an issue calling a WCF service over net.pipe with Windows impersonation from a C# Windows service. 我在从C#Windows服务通过Windows模拟通过net.pipe调用WCF服务时遇到问题。

Background 背景

The service reads from a queue and creates children app domains, each running a particular module per the item pulled from the queue. 该服务从队列中读取并创建子应用程序域,每个子域都对从队列中提取的项目运行特定的模块。 We call the Windows service a “JobQueueAgent” and each module a “Job”. 我们将Windows服务称为“ JobQueueAgent”,并将每个模块称为“ Job”。 I will use these terms going forward. 我将继续使用这些术语。 A job can be configured to run as a specified user. 可以将作业配置为以指定用户身份运行。 We use impersonation inside the job's app domain to accomplish this. 我们在工作的应用程序域内使用模拟来完成此任务。 The following is the flow of logic and credentials in the service: 以下是服务中的逻辑和凭证流:

JobQueueAgent (Windows Service – Primary User) >> Create job domain >> Job Domain (App Domain) >> Impersonate sub user >> Run job on thread with impersonation >> Job (Module – Sub User) >> Job logic JobQueueAgent(Windows服务–主用户)>>创建作业域>> Job Domain(应用程序域)>>模拟子用户>>通过模拟在线程上运行作业>> Job(模块–子用户)>>作业逻辑

The “Primary User” and “Sub User” are both domain accounts with rights to “login as a service”. “主要用户”和“子用户”都是具有“作为服务登录”权限的域帐户。

The service runs on a virtual server running Windows Server 2012 R2. 该服务在运行Windows Server 2012 R2的虚拟服务器上运行。

The following is the C# impersonation code I am using: 以下是我正在使用的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;
    }
}

The Problem 问题

Some jobs are required to make net.pipe WCF service calls to another Windows service running on the server. 需要一些作业才能对服务器上运行的另一个Windows服务进行net.pipe WCF服务调用。 The net.pipe call fails when running under impersonation. 在模拟下运行时,net.pipe调用失败。

Here is the exception I get in this scenario: 在这种情况下,这是我的例外情况:

Unhandled Exception: System.ComponentModel.Win32Exception: Access is denied 未处理的异常:System.ComponentModel.Win32Exception:访问被拒绝

Server stack trace: at System.ServiceModel.Channels.AppContainerInfo.GetCurrentProcessToken() at System.ServiceModel.Channels.AppContainerInfo.RunningInAppContainer() at System.ServiceModel.Channels.AppContainerInfo.get_IsRunningInAppContainer() at System.ServiceModel.Channels.PipeSharedMemory.BuildPipeName(String pipeGuid) 服务器堆栈跟踪:在System.ServiceModel.Channels.AppContainerInfo.RunningInAppContainer()在System.ServiceModel.Channels.AppContainerInfo.get_IsRunningInAppContainer()在System.ServiceModel.Channels.AppContainerInfo.GetCurrentProcessToken()在System.ServiceModel.Channels.PipeSharedMemory.BuildPipeName (字符串pipeGuid)

The net.pipe succeeds when not running under impersonation. 如果没有模拟运行,net.pipe将成功。 The net.pipe call also succeeds when the impersonated user is added to the Administrators group. 将模拟用户添加到Administrators组时,net.pipe调用也会成功。 This implies there is some privilege the user needs to make the call while under impersonation. 这意味着在模拟过程中,用户需要有一些特权才能拨打电话。 We have not been able to determine what policy, privilege or access the user needs to make the net.pipe call while impersonating. 我们无法确定用户在模拟时进行net.pipe调用所需的策略,特权或访问权限。 It is not acceptable to make the user an administrator. 使用户成为管理员是不可接受的。

Is this a known issue? 这是一个已知的问题? Is there a particular right the user needs to succeed? 用户需要获得成功的特别权利吗? Is there a code change I can make to resolve this issue? 我可以进行代码更改来解决此问题吗? Using WCF's net.pipe in a website with impersonate=true seems to indicate that this will not work in an ASP.NET application due to NetworkService. 在具有impersonate = true的网站中使用WCF的net.pipe似乎表明由于NetworkService,这在ASP.NET应用程序中将不起作用。 Not sure, but that shouldn't apply here. 不确定,但这不应该在这里适用。

With the help of Microsoft Support, I was able to resolve this issue by modifying the access rights of the thread identity (something suggested by Harry Johnston in another answer). 在Microsoft支持的帮助下,我能够通过修改线程身份的访问权限来解决此问题(Harry Johnston在另一个答案中提出了建议)。 Here is the impersonation code I am now using: 这是我现在正在使用的模拟代码:

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

Ah, here's the problem: 嗯,这是问题所在:

Server stack trace: at System.ServiceModel.Channels.AppContainerInfo.GetCurrentProcessToken() 服务器堆栈跟踪:位于System.ServiceModel.Channels.AppContainerInfo.GetCurrentProcessToken()

When you attempt to open the pipe, the system is checking to see whether you're in an app container or not. 当您尝试打开管道时,系统会检查您是否在应用程序容器中。 That involves querying the process token, which the user you're impersonating doesn't have permission to do. 这涉及查询流程令牌,您所模拟的用户无权执行该令牌。

This seems like a bug to me. 对我来说,这似乎是一个错误。 You could try opening a paid support case with Microsoft, but there's no guarantee that they will be willing to issue a hotfix or that they will be able to resolve the problem soon enough to meet your needs. 您可以尝试与Microsoft展开付费支持案例,但不能保证他们会发布修补程序,也不能保证他们能够尽快解决问题以满足您的需求。

So I see two plausible workarounds: 因此,我看到了两个可行的解决方法:

  • Before impersonating, change the ACL on the process access token to grant TOKEN_QUERY access to the new logon token. 模拟之前,更改进程访问令牌上的ACL,以授予TOKEN_QUERY访问新登录令牌的权限。 I believe the logon token will contain a logon SID, so that would be the safest choice, but it shouldn't be too dangerous to grant access to the user account instead. 我认为登录令牌将包含登录SID,因此这是最安全的选择,但授予用户帐户访问权限应该不会太危险。 To the best of my knowledge, the TOKEN_QUERY access right does not reveal any particularly sensitive information. 据我所知, TOKEN_QUERY访问权限不会泄露任何特别敏感的信息。

  • You could launch a child process in the sub-user's context instead of using impersonation. 您可以在子用户的上下文中启动子进程,而不是使用模拟。 Less efficient and less convenient, but it would be a simple way of resolving the problem. 效率较低,使用也不方便,但这将是解决问题的简单方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM