【问题标题】:How to call net.pipe (named pipe) WCF services while impersonating in a Windows Service如何在 Windows 服务中模拟时调用 net.pipe(命名管道)WCF 服务
【发布时间】:2016-01-06 11:26:21
【问题描述】:

我在通过 net.pipe 从 C# Windows 服务使用 Windows 模拟调用 WCF 服务时遇到问题。

背景

该服务从队列中读取数据并创建子应用程序域,每个子应用程序域根据从队列中提取的项目运行一个特定的模块。我们将 Windows 服务称为“JobQueueAgent”,将每个模块称为“Job”。我将继续使用这些术语。可以将作业配置为以指定用户身份运行。我们在作业的应用程序域内使用模拟来完成此操作。 以下是服务中的逻辑和凭证流程:

JobQueueAgent(Windows 服务 - 主用户)>> 创建作业域 >> 工作域(应用域)>>冒充子用户>> 在模拟线程上运行作业 >> 工作(模块-子用户)>>工作逻辑

“主要用户”和“子用户”都是具有“作为服务登录”权限的域帐户。

服务在运行 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.GetCurrentProcessToken() 在 System.ServiceModel.Channels.AppContainerInfo.RunningInAppContainer() 在 System.ServiceModel.Channels.AppContainerInfo.get_IsRunningInAppContainer() 在 System.ServiceModel.Channels.PipeSharedMemory.BuildPipeName(字符串 pipeGuid)

net.pipe 未在模拟下运行时成功。将模拟用户添加到管理员组时,net.pipe 调用也会成功。这意味着用户在模拟时需要一些特权才能拨打电话。我们无法确定用户在模拟时进行 net.pipe 调用所需的策略、特权或访问权限。将用户设置为管理员是不可接受的。

这是一个已知问题吗?用户成功需要特定的权利吗?我可以进行代码更改来解决此问题吗? Using WCF's net.pipe in a website with impersonate=true 似乎表明由于 NetworkService,这在 ASP.NET 应用程序中不起作用。不确定,但这不应该适用于此。

【问题讨论】:

  • 你说net.pipe在你不冒充的时候成功了;在您尝试模拟的用户帐户的上下文中从普通应用程序(而不是服务)运行时,它是否成功?换句话说,您确定问题与模拟有关,而不仅仅是因为被模拟的用户帐户缺乏必要的连接权限?
  • 另外,您应该尝试使用交互式登录类型 (2) 而不是服务登录类型 (5)。我认为这不会有什么不同,但我不确定。
  • 我可以在控制台应用程序中重新创建问题。很遗憾,交互式登录无法正常工作。
  • 不使用模拟时调用成功。
  • 但是,模拟是一项要求,所以问题仍然存在。

标签: .net windows wcf named-pipes impersonation


【解决方案1】:

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

【讨论】:

    【解决方案2】:

    啊,问题来了:

    服务器堆栈跟踪:在 System.ServiceModel.Channels.AppContainerInfo.GetCurrentProcessToken()

    当您尝试打开管道时,系统会检查您是否在应用容器中。这涉及查询进程令牌,而您所模拟的用户无权执行此操作。

    这对我来说似乎是一个错误。您可以尝试向 Microsoft 提出付费支持案例,但不能保证他们愿意发布修补程序,或者他们能够尽快解决问题以满足您的需求。

    所以我看到了两个可行的解决方法:

    • 在模拟之前,更改进程访问令牌上的 ACL 以授予 TOKEN_QUERY 对新登录令牌的访问权限。我相信登录令牌将包含登录 SID,因此这将是最安全的选择,但授予对用户帐户的访问权限应该不会太危险。据我所知,TOKEN_QUERY 访问权限不会泄露任何特别敏感的信息。

    • 您可以在子用户的上下文中启动子进程,而不是使用模拟。效率较低且不太方便,但这是解决问题的简单方法。

    【讨论】:

    • 感谢您的洞察力。有趣的是,我实际上已经与 Microsoft 建立了付费支持案例。也许我会得到一个修补程序:)。我已经将子进程视为B计划,但我会尝试TOKEN_QUERY的想法,看看它是否有效。
    • @cruikshj - 如果这是一个错误,您是否曾向 MSFT 确认过?我遇到了类似的问题,除了我没有做任何明确的模仿。我的客户端位于 impersonate=true 的 IIS Web 应用程序中,因此我不确定您发布的修复程序是否有效以及它将如何工作。该代码在 .NET 2.0 运行时运行良好,因此我认为这与 .NET 4.0 中的 CAS 行为变化有关,但到目前为止,我还无法通过针对 CAS 问题列出的变通办法解决它。
    【解决方案3】:

    【讨论】:

    • 第一个链接中的答案似乎是在谈论 WCF 服务主机的权限,而不是尝试联系主机的客户端的权限。第二个链接中的答案说 WCF 服务应该托管在服务应用程序中,根据您的问题,您已经在做。所以我不认为这是你的问题。
    猜你喜欢
    • 1970-01-01
    • 2019-08-17
    • 2016-08-05
    • 1970-01-01
    • 1970-01-01
    • 2018-05-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多