2015-10-08 4 views
2

У меня возникла проблема с вызовом службы WCF поверх net.pipe с помощью олицетворения Windows из службы Windows C#.Как вызвать службы WCF net.pipe (named pipe) во время олицетворения в службе Windows

фон

служба считывает из очереди и создает детские приложения доменов, каждый из которых выполняет определенный модуль согласно пункту извлекается из очереди. Мы называем службу Windows «JobQueueAgent», а каждый модуль - «Job». Я буду использовать эти условия в будущем. Задание может быть настроено для работы в качестве указанного пользователя. Мы используем олицетворение внутри домена приложения задания, чтобы выполнить это. Ниже поток логики и учетных данных в службе:

JobQueueAgent (Windows Service - основной пользователь) >> Создать домен работу >> Работа домена (Domain App) >> IMPERSONATE суб пользователя >> Выполнить работу на потоке с олицетворением >> Работа (модуль - подпользователь) >> Логика работы

«Первичный пользователь» и «Субпользователь» являются учетными записями домена с правами на «вход в систему как услугу».

Служба работает на виртуальном сервере под управлением Windows Server 2012 R2.

Ниже приведен код на С # олицетворения Я использую:

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

Проблема

Некоторые работы необходимы, чтобы сделать net.pipe WCF службы вызовов в другую службу Windows, запущенной на сервере , Вызов 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 (String pipeGuid)

net.pipe успешно, когда он не работает под олицетворения. Вызов net.pipe также преуспевает, когда олицетворяемый пользователь добавляется в группу «Администраторы». Это означает, что есть некоторая привилегия, которую пользователь должен выполнить для вызова, находясь под олицетворением. Мы не смогли определить, какая политика, привилегия или доступ пользователю должны сделать вызов net.pipe при выдаче себя за олицетворение. Недопустимо, чтобы пользователь был администратором.

Это известная проблема? Существует ли конкретное право пользователя на успех? Могу ли я изменить код, чтобы решить эту проблему? Using WCF's net.pipe in a website with impersonate=true, похоже, указывает, что это не будет работать в приложении ASP.NET из-за NetworkService. Не уверен, но это не должно применяться здесь.

+0

Вы говорите, что net.pipe преуспевает, когда вы не выполняете олицетворение; удастся ли это выполнить из обычного приложения (а не службы) в контексте учетной записи пользователя, которую вы пытаетесь выдавать себя за себя? Другими словами, вы уверены, что проблема связана с олицетворением, а не только из-за того, что пользовательская учетная запись не имеет необходимых прав для подключения? –

+0

Кроме того, вы должны попробовать использовать тип интерактивного входа (2), а не тип входа в систему (5). Я не думаю, что это будет иметь какое-то значение, но я не уверен. –

+0

Я могу воссоздать проблему в консольном приложении. К сожалению, интерактивный вход в систему не работает. – cruikshj

ответ

3

С помощью поддержки Microsoft, я был в состоянии решить эту проблему путем изменения прав доступа к идентичности потока (что-то предложил Гарри Джонстон в другой ответ). Вот код олицетворения, который я использую сейчас:

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

Похоже, что это невозможно без разрешенных (административных) разрешений.

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

Хотя, дуплексный контракт, описанный здесь может содержать ключ.

Client on non-admin user can't communicate using net.pipe with services

+0

Ответ в этой первой ссылке, похоже, говорит о разрешениях хоста службы WCF, а не о разрешениях клиента, пытающегося связаться с хостом. Ответ во второй ссылке говорит о том, что служба WCF должна размещаться в сервисном приложении, которое, согласно вашему вопросу, вы уже делаете. Поэтому я не думаю, что это твоя проблема. –

1

Ах, вот в чем проблема:

сервера трассировки стека: на System.ServiceModel.Channels.AppContainerInfo.GetCurrentProcessToken()

При попытке открыть pipe, система проверяет, находитесь ли вы в контейнере приложения или нет. Это включает в себя запрос к токену процесса, который пользователь, которому вы выдаете себя, не имеет права делать.

Это кажется ошибкой для меня. Вы можете попробовать открыть платное приложение поддержки с Microsoft, но нет гарантии, что они захотят выпустить исправление или что они смогут решить проблему достаточно быстро, чтобы удовлетворить ваши потребности.

Так я вижу два возможных обходные пути

  • Перед олицетворения, изменить ACL на маркер доступа процесса для предоставления TOKEN_QUERY доступа к новому маркер входа в систему. Я считаю, что токен входа в систему будет содержать идентификатор входа в систему, поэтому это будет самый безопасный выбор, но не должно быть слишком опасным для предоставления доступа к учетной записи пользователя. Насколько я знаю, право доступа TOKEN_QUERY не раскрывает особо конфиденциальной информации.

  • Вы можете запустить дочерний процесс в контексте субпользователя, вместо использования олицетворения. Менее эффективным и менее удобным, но это был бы простой способ решения проблемы.

+0

Спасибо за понимание.Забавно, я на самом деле уже открыл платный случай поддержки с Microsoft. Возможно, я получу исправление :). Я рассмотрел дочерний процесс как план B, но я попробую идею TOKEN_QUERY и посмотрю, работает ли он. – cruikshj