Pregunta

Tengo un sitio web que tiene habilitada la autenticación de Windows. Desde una página en el sitio web, los usuarios tienen la capacidad de iniciar un servicio que hace algunas cosas con la base de datos.

Funciona bien para mí iniciar el servicio porque soy un administrador local en el servidor. Pero acabo de hacer que un usuario lo pruebe y no pueden iniciar el servicio.

Mi pregunta es:


¿Alguien sabe de una manera de obtener una lista de servicios en una computadora específica por nombre usando una cuenta de Windows diferente a la que están actualmente conectados?


Realmente no quiero agregar a todos los usuarios que necesitan iniciar el servicio en un grupo de Windows y configurarlos a un administrador local en mi servidor IIS .....

Aquí hay algunos de los códigos que tengo:

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

Mi excepción proviene de la segunda línea en el bloque try. Aquí está el error:

  

System.InvalidOperationException:   No se puede abrir el Administrador de control de servicios en   computadora 'server.domain.com'. Esta   la operación puede requerir otra   privilegios --- >   System.ComponentModel.Win32Exception:   Acceso denegado --- Fin del interior   seguimiento de pila de excepción --- en   System.ServiceProcess.ServiceController.GetDataBaseHandleWithAccess (String   machineName, Int32   serviceControlManaqerAccess) en   System.ServiceProcess.ServiceController.GetServicesOfType (String   machineName, Int32 serviceType) en   TelemarketingWebSite.Utilities.StartService ()

Gracias por la ayuda / información

¿Fue útil?

Solución

Nota: Esto no aborda la enumeración de servicios como un usuario diferente, pero dada la descripción más amplia de lo que está haciendo, creo que es una buena respuesta.

Creo que puede simplificar esto mucho y posiblemente evitar parte del problema de seguridad, si acude directamente al servicio de interés. En lugar de llamar a GetServices, intente esto:

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

Esto se conecta directamente al servicio de interés y omite el paso de enumeración / búsqueda. Por lo tanto, no requiere que la persona que llama tenga el SC_MANAGER_ENUMERATE_SERVICE directamente en el Service Control Manager (SCM), que los usuarios remotos no tienen por defecto. Todavía requiere SC_MANAGER_CONNECT , pero según MSDN que debe otorgarse a usuarios autenticados remotos.

Una vez que haya encontrado el servicio de interés, aún deberá poder detenerlo e iniciarlo, lo que sus usuarios remotos probablemente no tengan derecho a hacer. Sin embargo, es posible modificar el descriptor de seguridad (DACL) en servicios individuales, lo que le permitiría otorgar a sus usuarios remotos acceso para detener e iniciar el servicio sin requerir que sean administradores locales. Esto se realiza a través de la función de API SetNamedSecurityInfo . Los derechos de acceso que debe otorgar son SERVICE_START y SERVICE_STOP . Según exactamente a qué grupos pertenecen estos usuarios, es posible que también deba otorgarles GENERIC_READ . Todos estos derechos están descrito en MSDN .

Aquí hay un código C ++ que realizaría esta configuración, suponiendo que los usuarios de interés estén en los "Controladores de servicio remoto". grupo (que crearía) y el nombre del servicio es "my-service-name". Tenga en cuenta que si desea otorgar acceso a un grupo conocido como Usuarios (no necesariamente una buena idea) en lugar de un grupo que creó, debe cambiar TRUSTEE_IS_GROUP a TRUSTEE_IS_WELL_KNOWN_GROUP .

El código no tiene comprobación de errores, lo que desea agregar. Las tres funciones que pueden fallar (Get / SetNamedSecurityInfo y SetEntriesInAcl) devuelven 0 para indicar el éxito.

Otra nota: También puede configurar el descriptor de seguridad de un servicio utilizando la herramienta SC , que se puede encontrar en% WINDIR% \ System32, pero eso no implica ninguna programación.

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

Esto también podría hacerse desde C # usando P / Invoke, pero eso es un poco más de trabajo.

Si aún desea específicamente enumerar servicios como estos usuarios, debe otorgarles el SC_MANAGER_ENUMERATE_SERVICE directamente en el SCM. Desafortunadamente, según MSDN , la seguridad de SCM solo se puede modificar en Windows Server 2003 sp1 o posterior.

Otros consejos

Gracias por esa línea de código Charlie. Esto es lo que terminé haciendo. Tengo la idea de este sitio web: http: //www.codeproject. com / KB / cs / svcmgr.aspx? display = Print

También tuve que agregar la cuenta a la que estoy accediendo a este grupo de usuarios avanzados en el servidor.

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

Y aquí está mi otra clase con 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);
    }

Puede intentar usar la suplantación de ASP.NET en su archivo web.config y especificar una cuenta de usuario que tenga los permisos adecuados:

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

Eche un vistazo a este artículo en MSDN . Creo que hay otras opciones que no requieren almacenar la contraseña en el archivo web.config, como colocarla en una clave de registro.

Esto hará que el proceso de trabajo ASP.NET se ejecute en el contexto del usuario especificado en lugar del usuario que inició sesión en la aplicación web. Sin embargo , esto plantea un problema de seguridad y volvería a pensar en su diseño. Es posible que desee considerar que la página web ASP.NET active una solicitud a algún otro proceso que realmente controle los servicios, incluso otro servicio de Windows o escriba la solicitud en una tabla de base de datos que el servicio de Windows sondea periódicamente.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top