.Net 2.0 ServiceController.GetServices () - PullRequest
       34

.Net 2.0 ServiceController.GetServices ()

5 голосов
/ 17 октября 2008

У меня есть веб-сайт с включенной аутентификацией Windows. На странице веб-сайта пользователи могут запускать службу, выполняющую какие-либо операции с базой данных.

У меня нормально работает запуск службы, потому что я являюсь локальным администратором на сервере. Но у меня только что было пользовательское тестирование, и они не могут запустить службу.

Мой вопрос:


Кто-нибудь знает, как получить список служб на указанном компьютере по имени, используя учетную запись Windows, отличную от той, с которой они в настоящее время вошли в систему? Я действительно не хочу добавлять всех пользователей, которым необходимо запустить службу, в группу Windows и назначать их всем локальному администратору на моем сервере IIS .....

Вот часть кода, который у меня есть:

public static ServiceControllerStatus FindService()
        {
            ServiceControllerStatus status = ServiceControllerStatus.Stopped;

            try
            {
                string machineName = ConfigurationManager.AppSettings["ServiceMachineName"];
                ServiceController[] services = ServiceController.GetServices(machineName);
                string serviceName = ConfigurationManager.AppSettings["ServiceName"].ToLower();

                foreach (ServiceController service in services)
                {
                    if (service.ServiceName.ToLower() == serviceName)
                    {
                        status = service.Status;
                        break;
                    }
                }
            }
            catch(Exception ex)
            {
                status = ServiceControllerStatus.Stopped;
                SaveError(ex, "Utilities - FindService()");
            }

            return status;
        }

Мое исключение исходит из второй строки в блоке try. Вот ошибка:

System.InvalidOperationException: Не удается открыть диспетчер управления службами на компьютер 'server.domain.com'. это операция может потребовать других привилегии. ---> System.ComponentModel.Win32Exception: Доступ запрещен --- Конец внутреннего трассировка стека исключений --- в System.ServiceProcess.ServiceController.GetDataBaseHandleWithAccess (String machineName, Int32 serviceControlManaqerAccess) в System.ServiceProcess.ServiceController.GetServicesOfType (String machineName, Int32 serviceType) в TelemarketingWebSite.Utilities.StartService ()

Спасибо за помощь / информация

Ответы [ 3 ]

6 голосов
/ 17 октября 2008

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

Я думаю, что вы можете значительно упростить это и, возможно, избежать части проблемы с безопасностью, если перейдете непосредственно к интересующей службе. Вместо вызова GetServices попробуйте это:

string machineName = ConfigurationManager.AppSettings["ServiceMachineName"];
string serviceName = ConfigurationManager.AppSettings["ServiceName"];
ServiceController service = new ServiceController( serviceName, machineName );
return service.Status;

Это напрямую связано с интересующей службой и обходит этап перечисления / поиска. Следовательно, не требуется, чтобы у вызывающего абонента было право SC_MANAGER_ENUMERATE_SERVICE на диспетчер управления службами (SCM), которого удаленные пользователи по умолчанию не имеют. Это все еще требует SC_MANAGER_CONNECT, но в соответствии с MSDN , которое должно быть предоставлено удаленным аутентифицированным пользователям.

После того, как вы нашли интересующую службу, вам все равно нужно будет остановить и запустить ее, что, вероятно, не имеют права ваши удаленные пользователи. Тем не менее, можно изменить дескриптор безопасности (DACL) для отдельных служб, что позволит вам предоставить удаленным пользователям доступ для остановки и запуска службы, не требуя, чтобы они были локальными администраторами. Это делается с помощью API-функции SetNamedSecurityInfo . Права доступа, которые вам нужно предоставить, - SERVICE_START и SERVICE_STOP. В зависимости от того, к каким именно группам принадлежат эти пользователи, вам также может потребоваться предоставить им GENERIC_READ. Все эти права описаны в MSDN .

Вот некоторый код C ++, который будет выполнять эту настройку, предполагая, что интересующие вас пользователи находятся в группе «Remote Service Controllers» (которую вы бы создали), а имя службы - «my-service-name». Обратите внимание, что если вы хотите предоставить доступ к известной группе, такой как «Пользователи» (не обязательно хорошая идея), а не к группе, которую вы создали, вам нужно изменить TRUSTEE_IS_GROUP на TRUSTEE_IS_WELL_KNOWN_GROUP.

В коде нет проверки ошибок, которую вы хотели бы добавить. Все три функции, которые могут потерпеть неудачу (Get / SetNamedSecurityInfo и SetEntriesInAcl), возвращают 0, чтобы указать успех.

Другое примечание: Вы также можете установить дескриптор безопасности службы, используя инструмент SC , который можно найти в папке% WINDIR% \ System32, но который не требует программирования.

#include "windows.h"
#include "accctrl.h"
#include "aclapi.h"

int main()
{
    char serviceName[] = "my-service-name";
    char userGroup[] = "Remote Service Controllers";

    // retrieve the security info
    PACL pDacl = NULL;
    PSECURITY_DESCRIPTOR pDescriptor = NULL;
    GetNamedSecurityInfo( serviceName, SE_SERVICE,
        DACL_SECURITY_INFORMATION, NULL, NULL,
        &pDacl, NULL, &pDescriptor );

    // add an entry to allow the users to start and stop the service
    EXPLICIT_ACCESS access;
    ZeroMemory( &access, sizeof(access) );
    access.grfAccessMode = GRANT_ACCESS;
    access.grfAccessPermissions = SERVICE_START | SERVICE_STOP;
    access.Trustee.TrusteeForm = TRUSTEE_IS_NAME;
    access.Trustee.TrusteeType = TRUSTEE_IS_GROUP;
    access.Trustee.ptstrName = userGroup;
    PACL pNewDacl;
    SetEntriesInAcl( 1, &access, pDacl, &pNewDacl );

    // write the changes back to the service
    SetNamedSecurityInfo( serviceName, SE_SERVICE,
        DACL_SECURITY_INFORMATION, NULL, NULL,
        pNewDacl, NULL );

    LocalFree( pNewDacl );
    LocalFree( pDescriptor );
}

Это также можно сделать из C # с использованием P / Invoke, но это немного больше работы.

Если вы все еще хотите иметь возможность перечислять службы в качестве этих пользователей, вам нужно предоставить им SC_MANAGER_ENUMERATE_SERVICE прямо в SCM. К сожалению, в соответствии с MSDN , безопасность SCM может быть изменена только в Windows Server 2003 sp1 или более поздней версии.

2 голосов
/ 17 октября 2008

Спасибо за эту строчку кода Чарли. Вот что я в итоге сделал. Я получил идею с этого сайта: http://www.codeproject.com/KB/cs/svcmgr.aspx?display=Print

Мне также пришлось добавить учетную запись, к которой я обращаюсь, в группу «Опытные пользователи» на сервере.

public static ServiceControllerStatus FindService()
        {
            ServiceControllerStatus status = ServiceControllerStatus.Stopped;
    try
            {
                string machineName = ConfigurationManager.AppSettings["ServiceMachineName"];
                string serviceName = ConfigurationManager.AppSettings["ServiceName"].ToLower();

                ImpersonationUtil.Impersonate();

                ServiceController service = new ServiceController(serviceName, machineName);
                status = service.Status;
            }
            catch(Exception ex)
            {
                status = ServiceControllerStatus.Stopped;
                SaveError(ex, "Utilities - FindService()");
            }

            return status;
        }

А вот другой мой класс с ImpersonationUtil.Impersonate ():

public static class ImpersonationUtil
    {
        public static bool Impersonate()
        {
            string logon = ConfigurationManager.AppSettings["ImpersonationUserName"];
            string password = ConfigurationManager.AppSettings["ImpersonationPassword"];
            string domain = ConfigurationManager.AppSettings["ImpersonationDomain"];

            IntPtr token = IntPtr.Zero;
            IntPtr tokenDuplicate = IntPtr.Zero;
            WindowsImpersonationContext impersonationContext = null;

            if (LogonUser(logon, domain, password, 2, 0, ref token) != 0)
                if (DuplicateToken(token, 2, ref tokenDuplicate) != 0)
                    impersonationContext = new WindowsIdentity(tokenDuplicate).Impersonate();
            //

            return (impersonationContext != null);
        }

        [DllImport("advapi32.dll", CharSet = CharSet.Auto)]
        public static extern int LogonUser(string lpszUserName, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);

        [DllImport("advapi32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto, SetLastError = true)]
        public extern static int DuplicateToken(IntPtr hToken, int impersonationLevel, ref IntPtr hNewToken);
    }
1 голос
/ 17 октября 2008

Вы можете попробовать использовать олицетворение ASP.NET в файле web.config и указать учетную запись пользователя, которая имеет соответствующие разрешения:

    <system.web>
       <identity impersonate="true" userName="Username" password="Password" />
    </system.web

Взгляните на эту статью на MSDN . Я полагаю, что есть и другие варианты, которые не требуют хранения пароля в файле web.config, например, поместив его в ключ реестра.

Это заставит рабочий процесс ASP.NET запускаться в контексте указанного пользователя, а не пользователя, вошедшего в веб-приложение. Однако , это создает проблему безопасности, и я бы настоятельно переосмыслил ваш дизайн. Возможно, вы захотите, чтобы веб-страница ASP.NET по очереди отключала запрос к другому процессу, который фактически контролирует службы, даже к другой службе Windows, или записывает запрос в таблицу базы данных, которую служба Windows периодически опрашивает.

...