У меня есть веб-сайт, на котором включена проверка подлинности 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: Cannot open Service Control Manager on computer 'server.domain.com'. This operation might require other privileges. ---> System.ComponentModel.Win32Exception: Access is denied --- End of inner exception stack trace --- at System.ServiceProcess.ServiceController.GetDataBaseHandleWithAccess(String machineName, Int32 serviceControlManaqerAccess) at System.ServiceProcess.ServiceController.GetServicesOfType(String machineName, Int32 serviceType) at TelemarketingWebSite.Utilities.StartService()
Спасибо за помощь / информацию





Вы можете попробовать использовать олицетворение ASP.NET в файле web.config и указать учетную запись пользователя с соответствующими разрешениями:
<system.web>
<identity impersonate = "true" userName = "Username" password = "Password" />
</system.web
Взгляните на эта статья в MSDN. Я считаю, что есть и другие варианты, которые не требуют хранения пароля в файле web.config, например, вместо этого помещать его в раздел реестра.
Это приведет к тому, что рабочий процесс ASP.NET будет работать в контексте указанного пользователя, а не пользователя, вошедшего в веб-приложение. тем не мение, это создает проблему безопасности, и я бы сильно переосмыслил ваш дизайн. Вы можете подумать о том, чтобы веб-страница ASP.NET в свою очередь запускала запрос к какому-либо другому процессу, который фактически контролирует службы, даже к другой службе Windows, или записывала запрос в таблицу базы данных, которую служба Windows периодически опрашивает.
Вы можете продолжать использовать их учетную запись Windows для аутентификации. Я предполагаю, что вы используете встроенную проверку подлинности в IIS и модель проверки подлинности ASP.NET. (<authentication mode = "Windows" />). Вы можете использовать оба вместе, и IIS по-прежнему будет согласовывать учетные данные пользователя без дополнительного входа в систему.
Примечание. Это не касается перечисления сервисов от имени другого пользователя, но, учитывая более широкое описание того, что вы делаете, я думаю, что это хороший ответ.
Я думаю, вы можете значительно упростить это и, возможно, частично избежать проблем с безопасностью, если обратитесь непосредственно к интересующей службе. Вместо вызова 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++, который будет выполнять эту настройку, предполагая, что интересующие вас пользователи находятся в группе «Контроллеры удаленных служб» (которую вы должны создать), а имя службы - «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 с пакетом обновления 1 или более поздней версии.
Спасибо за эту строчку кода, Чарли. Вот что я в итоге сделал. Я получил идею с этого сайта: 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);
}
Круто, удачи в этом. Я был бы очень осторожен, чтобы убедиться, что вы правильно отменили олицетворение, когда закончите. Для этого вам нужно избавиться от контекста олицетворения, возвращаемого из Impersonate. Невыполнение этого требования приведет к утечке имитации и вызовет проблемы в будущем.
Чтобы было ясно, если вы не отмените олицетворение, этот поток будет продолжать олицетворять указанную учетную запись на неопределенный срок. В лучшем случае это вероятная причина ошибок, а в худшем - брешь в безопасности. Лучше быть с этим поосторожнее.
Мне нужно иметь имя пользователя Windows, потому что я показываю вещи на веб-сайте на основе их входа в Windows. Я не хотел, чтобы у пользователя был дополнительный логин.