问题
I am having an issue calling a WCF service over net.pipe with Windows impersonation from a C# Windows service.
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”. 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
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.
The following is the C# impersonation code I am using:
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. The net.pipe call fails when running under impersonation.
Here is the exception I get in this scenario:
Unhandled Exception: System.ComponentModel.Win32Exception: Access is denied
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)
The net.pipe succeeds when not running under impersonation. The net.pipe call also succeeds when the impersonated user is added to the Administrators group. 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. 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. Not sure, but that shouldn’t apply here.
回答1:
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). 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();
}
}
}
回答2:
Ah, here's the problem:
Server stack trace: at 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.
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. 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. To the best of my knowledge, theTOKEN_QUERY
access right does not reveal any particularly sensitive information.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.
回答3:
Looks like this is not possible without elevated (administrator) permissions.
https://social.msdn.microsoft.com/Forums/vstudio/en-US/6a26497f-0346-4929-ad42-ff4459be60e4/error-while-attempting-to-call-netpipe-wcf-service-while-impersonating?forum=wcf#6a26497f-0346-4929-ad42-ff4459be60e4
Although, the duplex contract described here may hold the key.
Client on non-admin user can't communicate using net.pipe with services
来源:https://stackoverflow.com/questions/33022583/how-to-call-net-pipe-named-pipe-wcf-services-while-impersonating-in-a-windows