Mostrando entradas con la etiqueta IIS. Mostrar todas las entradas
Mostrando entradas con la etiqueta IIS. Mostrar todas las entradas

lunes, 20 de julio de 2015

Forms TimeOut vs sessionState Timeout vs ExecutionTimeout

En este artículo hablaremos de los timeout que se configuran en el web.config en aplicaciones .Net:

  • timeout del tag Form
  • timeout del tag sessionState
  • ExecutionTimeout del tag httpRuntime

Ejemplo de una configuración del web.config:

<system.web>
<authentication mode="Forms">
<forms name="test" loginUrl="~/test.aspx" protection="All" path="/" timeout="40" slidingExpiration="false/true" />
</authentication>

<httpRuntime maxRequestLength="138000" executionTimeout="36000"></httpRuntime>
<sessionState timeout="480"></sessionState>
</system.web>

timeout del tag Form


Por defecto 30 minutos. En el ejemplo está en 40 minutos. Es el tiempo en minutos que el ticket de autenticación es válida (no de la cookie donde esté almacenada el ticket que puede tener otro tiempo o no tener por lo que caduca cuando se cierra el navegador), pasado este tiempo el ticket expira, y al siguiente request te tira al sitio de inicio/login deforma automática. En caso de que la cookies es persistente, el tiempo de timeout es el mismo entre el ticket y la cookie donde está la cookie.

slidingExpiration: Por defecto True en todos los Frameworks, aunque en MSDN hay un error ya que en algunas referencias dice que es False para Framework 2.0 pero en los mismos comentarios MSDN dicen que es True, por ejemplo aquí indica que es True: https://msdn.microsoft.com/en-us/library/system.web.security.formsauthentication.slidingexpiration(v=vs.80).aspx y aquí dice que por defecto es False para Framework 2.0: https://msdn.microsoft.com/es-cl/library/1d3t3c61(v=vs.80).aspx. Bueno oficialmente por defecto es True para todos los Frameworks.

En modo false es lo que es conocido como absolute expiration, si el tiempo timeout es 30 (30 min) si un usuario hace un request a las 13 hrs, luego si a las 13:31 hace un nuevo request, este no se procesará y en cambio, le dejará automáticamente en el login.

En modo true, sliding expiration (expiración corrida), la cookie y el ticket se actualizan si el usuario hace un request luego que haya pasado la mitad del tiempo. Por ejemplo si el timeout es 20 y usas sliding expiration en true, y el usuario visita el sitio a las 14 hrs, el usuario recibirá una cookie que expirará a las 14:20 hrs. La cookie se actualiza solo si el usuario visita el sitio luego de las 14:10 hrs.
Si el usuario hace una visita a las 14:12 hrs, la cookie se actualiza y ahora expirará a las 14:32 hrs.
Si el usuario visita el sitio a las 14:09 hrs, la cookie no se actualizará y si se da que el usuario visita el sitio a las 14:21 hrs, el ticket estará expirado y lo dejará en el home de forma automática.

Cuando ticket expira, da el famoso error de Ticket expirado:

Event code: 4005
Event message: Forms authentication failed for the request. Reason: The ticket supplied has expired.


Si no usa el tag authentication en el web.config, por defecto el Framework lo setea así (en Framework 2.0): https://msdn.microsoft.com/en-us/library/532aee0e(v=vs.80).aspx para v2.0, o https://msdn.microsoft.com/en-us/library/532aee0e(v=vs.100).aspx para v4.0.

<authentication mode="Windows">
</authentication> Este modo en el web.config es solo para decirle a .NET como manejar la autenticación no tiene que ver con IIS o el servidor.
De echo, si miras IIS en la sección Autenticación, solo quedará el modo anónimo que es el modo que por defecto trae habilitado. Si quieres modo Windows Authentication y que pida el usuario y clave, debes cambiar en el IIS en la sección Autenticación, y dejar modo Anónimo desactivado y Windows modo activado. Y al ejecutar el sitio pedirá un usuario y clave de Windows.

Si se cambia en el IIS 7 esta configuración, estás afectando a la sección de tu web.config:
system.webServer/security/authentication o applicationHost.config
Y no a <authentication> de <system.web>
En IIS 6 este cambio afecta a la metabase y no a un web.config.

Si no usa el tag forms dentro de authentication en el web.config, por defecto el Framework lo setea así (en Framework 2.0): https://msdn.microsoft.com/es-cl/library/1d3t3c61(v=vs.80).aspx

<forms
name=".ASPXAUTH"
loginUrl="login.aspx"
defaultUrl="default.aspx"
protection="All"
timeout="30"
path="/"
requireSSL="false"
slidingExpiration="true"
cookieless="UseDeviceProfile" domain=""
enableCrossAppRedirects="false">
<credentials passwordFormat="SHA1" />
</forms>

timeout del tag sessionState


Por defecto es 20 minutos. Es el tiempo en minutos que estarán los datos en memoria para una sesión en particular. Por ejemplo, si guardas un objeto en la clase Session se pierde la información a los 480 minutos. El time out se resetea en cada request.
Solo válido si se usa sessionState, es decir que no está como:

<sessionState mode="Off">

Como dato, por defecto el sessionState es InProc:

<sessionState mode="InProc">

Si no usa el tag sessionState en el web.config, por defecto el Framework lo setea así en Framework 2.0: https://msdn.microsoft.com/en-us/library/h6bb9cz9(v=vs.80).aspx

<sessionState
mode="InProc"
stateConnectionString="tcpip=127.0.0.1:42424"
stateNetworkTimeout="10"
sqlConnectionString="data source=127.0.0.1;Integrated Security=SSPI"
sqlCommandTimeout="30"
customProvider=""
cookieless="UseCookies"
cookieName="ASP.NET_SessionId"
timeout="20"
allowCustomSqlDatabase="false"
regenerateExpiredSessionId="true"
partitionResolverType=""
useHostingIdentity="true">
<providers>
<clear />
</providers>
</sessionState>

executionTimeout del tag httpRuntime


Por defecto es 110 segundos. En el ejemplo tiene 36000 (10 hrs). Es el número máximo de segundos que se permite que se ejecute una solicitud antes de que ASP.NET la concluya automáticamente y quede como failed o de el famoso error The request has been aborted. Request timed out.

Este tiempo de espera solo se aplica si el atributo de depuración del elemento

<compilation> está establecido en false. Así que executionTimeout sólo lo toma funciona en modo release, de lo contrario toma el valor por defecto, 110 seg.

Si estas haciendo una aplicación que sube un archivo pesado, por ejemplo de 50 MG, este valor debe ser por ejemplo 3600 (1 hr) para que .NET no la cierre automáticamente.

Tamaño de archivos de subida: maxRequestLength y maxAllowedContentLength

Si quieres permitir que el peso máximo de un archivo a subir sea de 200 MB debes setear dos tag maxRequestLength y maxAllowedContentLength ambos deben ir en el web.config.

El tag maxRequestLength está en KB e indica el tamaño máximo de un archivo soportado por ASP.NET. Si son 200 MB el valor sería 2048000. Por defecto está en 4096 es decir 4 MB.

Si no se usa el tag httpRuntime en el web.config, por defecto el Framework lo setea así en Framework 2.0: https://msdn.microsoft.com/en-us/library/e1f13641(v=vs.80).aspx

<httpRuntime
executionTimeout="110"
maxRequestLength="4096"
requestLengthDiskThreshold="256"
useFullyQualifiedRedirectUrl="false"
minFreeThreads="8"
minLocalRequestFreeThreads="4"
appRequestQueueLimit="5000"
enableKernelOutputCache="true"
enableVersionHeader="true"
requireRootedSaveAsPath="true"
enable="true"
shutdownTimeout="90"
delayNotificationTimeout="5"
waitChangeNotification="0"
maxWaitChangeNotification="0"
requestPriority="Normal"
enableHeaderChecking="true"
sendCacheControlHeader="true"
apartmentThreading="false"
/>

En IIS 7 además se debe setear el atributo maxAllowedContentLength que está en Bytes, indica el tamaño máximo de un archivo a subir soportado por IIS. Aquí los 200 MB serían 2097152000. El valor por defecto es 30000000, lo que equivale a 28.6 MB.
Ojo si que en IIS 7 este valor va en una estructura aparte:

<system.webServer>
<security>
<requestFiltering>
<requestLimits maxAllowedContentLength="2097152000"/>
</requestFiltering>
</security>
</system.webServer>

NOTA: Si estás con IIS 7 o superior, debes setear AMBOS TAG en el mismo valor, sino tomará el menor de ellos como el valor con prioridad.

Referencias


miércoles, 17 de junio de 2015

¿Cómo determinar que cuenta es la que IIS usa para ejecutar un sitio Web?

Es importante entender que cuenta es la que IIS ejecuta en un sitio web dado, sobretodo cuando necesitas hacer cambios a las opciones de seguridad. Por ejemplo, tu aplicación web escribe archivos en el disco o en una base de datos (por ejemplo un Access .mdb) y te toca darle los permisos correctos a dicha carpeta o base de datos. Antes de cambiar esas opciones de seguridad, deberías saber que cuenta está usando IIS. Este artículo detalla varias opciones disponibles, tanto para el ASP clásico como para ASP.NET.

Hay una gran diferencia entre las aplicación ASP y ASP.NET cuando se quiere determinar el contexto de usuario que ejecuta el IIS, así que este documento está dividido en dos secciones: ASP Clásico y ASP.NET.

ASP Clásico

Por defecto, si un sitio web que permite acceso anónimo, esta cuenta es llamada IUSR_NombreMáquina, donde NombreMáquina es el nombre del computador. Sin embargo, cuando usas un mecanismo de seguridad en IIS diferente a acceso Anónimo, puedes cambiar manualmente la cuenta que usa IIS, o puedes correr tu sitio web en modo "Out Of Process", o quizá usar la cuanta de otro usuario. La siguiente tabla lista las posibles cuentas de usuarios que IIS usa en diferentes escenarios:

Escenario: El Sitio Web / Directorio Virtual / Aplicación está configurada como Anonymous Access.
Cuenta usada: IUSR_NombreMáquina

Escenario: El Sitio Web / Directorio Virtual / Aplicación está configurado como Anonymous Access, y a la vez esta como "Out Of Process" (el campo Application Protection del IIS está seteada como High en el tab "Home Directory" o el tab de Virtual Directory de tu aplicación web)
Cuenta usada: IWAM_NombreMáquina

Escenario: El Sitio Web / Directorio Virtual / Aplicación está configurado como Basic Authentication o Integrated Windows Authentication.
Cuenta usada: La cuenta que usas para logearte en la aplicación web.

Escenario: El Sitio Web / Directorio Virtual / Aplicación está configurado como Anonymous Access, pero manualmente cambias la cuenta usada para usar anonymous access.
Cuenta usada: La cuenta que especificas.

Para encontrar como está configurado tu sistema, sigue estos pasos:

  • Ve al Administrative Tools -> Internet Information Services:

Figura 1: Internet Information Services del IIS 5.

  • Expande el lado izquierdo y verás Default Web Site. Si estás configurando otro Sitio Web / Directorio Virtual / Aplicación, selecciónalo en vez del por defecto. Este artículo asume que estás configurando Default Web Site.
  • Botón derecho sobre Default Web Site y escoge Properties.
  • Abre el tab Directory Security y haz clic en el botón Edit... en la sección Anonymous access y authentication control. Verás una pantalla similar a esta:

Figura 2: El diálogo Authentication Methods para un sitio dado en IIS 5.

  • Si está chequeado Anonymous access (como la pantalla de arriba), el nombre usuario que vez en el campo User name es la cuenta de IIS que se usa. Si no está chequeado Anonymous access y si lo está Basic y/o Integrated Windows authentication, la cuenta que estás usando para logearte al sitio web es usado por IIS. Nota que cuando está activo Anonymous access, no importa si está chequeado Basic o Integrated authentication; la cuenta usada por IIS sigue siendo la anonymous, IUSR_NombreMáquina o la cuenta.
  • Finalmente tienes que chequear si el sitio está corriendo Out of Process. Para hacer esto, cierra del diálogo Authentication Methods, y ve al tab Home Directory: 

Figura 3: El tab Home Directory IIS 5.

Figura 4 A: El tab Home Directory IIS 6.

Figura 4 B: IIS 5 Isolation mode en IIS 6. La cuenta de usuario que se asigna en este caso es la llamada ASPNET account.

  • Si la Application Protection está como Alto (High) (Aislada o Isolated) y estás usando Anonymous Access, la cuenta que está usando IIS es IWAM_NombreMáquina.
    Nota: Aislar una app significa que se configura para que corra como un proceso (espacio de memoria) que está separado del servidor web y de otras aplicaciones.
    En todos los restantes escenarios, IIS estará usando la cuenta determinada por los pasos anteriores.

ASP.NET

Para  ASP.NET, la cosa es un poco diferente. Por defecto, ASP.NET corre con una cuenta especial llamada ASPNET. Esta cuenta es la cuenta con "menos privilegios", lo que significa que está restringida a algunas pocas cosas que puede hacer en el sistema.

Para hacer un poco más confusas las cosas, en Windows Server 2003 que tiene IIS 6, un cuenta llamada Network Service (en los Windows en Español es Servicio de Red) es usada por defecto, en vez de la cuenta ASPNET. En Windows Server 2008 R2 que tiene IIS 7.5 es lo mismo, Network Service es la cuenta por defecto.

Nota: Todas las aplicaciones corren en un proceso.
En IIS 5 (y IIS 6 en modo de Isolation IIS 5), las aplicaciones web corren en el proceso llamado ASP.NET Worker Process (aspnet_wp.exe). Por defecto este proceso corre con la cuenta ASPNET.
En IIS 6 y superiores, las aplicaciones corren bajo el proceso llamado IIS Worker Process (w3wp.exe). Por defecto este proceso corre con la cuenta Network Service.

En IIS 6 había un grupo llamado IIS_WPG, el cual es un contenedor de todos las identidades de todos los applicacion pool.

Desde IIS 7 hacia arriba tenemos:

  • La cuenta IUSR reemplaza IUSR_NombreMáquina. No requiere password por ser del sistema.
  • El grupo IIS_IUSRS reemplaza al grupo IIS_WPG.

Así, si estás usando Anonymous Access o Basic / Integrated security, la cuenta siempre será ASPNET o la cuenta Network Service.

Para ver que tipo de acceso estás usando en IIS 7.5:



Figura 5: Modos de autenticación en IIS 7.5. Por defecto Autenticación Anónima está activa por defecto.

Figura 6: En IIS 6 hacia arriba, se puede configurar la identidad del AppPool. En IIS 7.5 por defecto es ApplicationPoolIdentity, en IIS 6/7 es NetworkService pero permite cambiarla a ApplicationPoolIdentity si deseas.

Figura 7: Seteando una identidad NetworkService

Figura 8: El grupo IIS_IUSRS (de IIS 7 hacia arriba) con las cuentas dinámicas APPPOOL/Sitio X.

Sin embargo, puedes cambiar esa cuenta modificando el web.config de la app. Para hacer esto, agrega <identity impersonate="true" /> en la sección <system.web>. Si agregas el elemento, IIS usará el usuario actual en vez de la cuenta ASPNET. Esto significa que con Anonymous Access activado, esta cuenta sería la cuenta anónima. Usualmente esta es la cuenta IUSR_MachineName, pero si usas ASP Clásico chequea IIS, el usuario seteado en User Name (Figura 2).

Si no estás usando Anonymous Access, pero si Basic o Integrated Security, la cuenta que se usa es aquella del usuario actual logeado. Puedes también explícitamente setear una cuenta dada con los atributos username y clave del elemento <identity>.

La siguiente tabla lista las diferentes posibilidades. La primera columna determina si se activa o no la impersonation en el archivo Web.Config. La segunda y tercera columna lista las opciones con y sin acceso Anonymous:

ASP.NET Impersonation: Desactivado
Acceso anónimo: cuenta ASPNET o Network Service
Acceso no anónimo (Basic, Integrated, otro): cuenta ASPNET o Network Service

ASP.NET Impersonation: Activada
Acceso anónimo: IUSR_NombreMáquina
Acceso no anónimo (Basic, Integrated, otro): usuario autenticado

ASP.NET Impersonation: Activada con una cuenta de usuario
Acceso anónimo: la cuenta dada
Acceso no anónimo (Basic, Integrated, otro): la cuenta dada

Resumen

La mayor parte del código ejecutado en una aplicación web se hace bajo un contexto de un IIS worker process y lo típico es que ese proceso corra con un identidad configurada en el application pool.
Por defecto, los AppPool están configurados para usar una cuenta interna, en IIS 7 es la cuenta Network Service, en IIS 7.5 es una cuenta virtual llamada ApplicationPoolIdentity que en verdad es una cuenta con un ID único, el nombre de la cuenta es IIS AppPool\host (ID único) por ejemplo: IIS APPPOOL\Misitio.cl (S-1-5-82-1545844221-6599888).
Cuando se inicia el worker process, en IIS 7 o superior, se hace automáticamente miembro del grupo IIS_IUSRS. En IIS 6 se hace miembro de IIS_WPG. IIS puede crear el grupo IIS_WPG por razones de retrocompatibilidad, en ese caso, IIS_IUSRS se hará miembro del grupo IIS_WPG.
Además, cierto código de la aplicación puede ejecutarse con la identidad del usuario autenticado asociado a cada request.

En resumen tenemos 2 contextos de identidad según lo que la aplicación necesita:

1. Identidad del AppPool: se usa cuando se accede a todos los archivos necesarios para la ejecución del worker process, cuando se accede al web.config, aplicaciones FASTCGI o ejecución de aplicaciones .Net.
Identidades involucradas (pueden ser las 3 a la vez): Primero una de estas 4: Network Service, ApplicacionPoolIdentity, LocalService o LocalSystem. Aparte está involucrada la cuenta de grupo IIS_IUSRS en IIS 7 o superior, IIS_WPG en IIS 6 y por último la cuenta Aplication Pool SID (IIS APPPOOLL\MiSitio.cl).

2. Usuario autenticado: Se usa para acceder a archivos estáticos, correr extensiones ISAPI o programas CGI, ejecutar aplicaciones FastCGI (si está activado impersonate), ejecutar aplicaciones .NET (si está activado impersonate).
Identidades asociadas (pueden ser 2): IUSR es por defecto cuando la autenticación es anónima en IIS 7, en IIS 6 es IUSR_NombreMáquina. Si no, es el usuario configurado en el Pool en Custom Account, alguna de las opciones de Application Pool Identity: Network Service, ApplicacionPoolIdentity, LocalService o LocalSystem. La segunda posible identidad asociada es el usuario autenticado si hay tokens en caso de que esté siendo usado Windows Authentication.

Permisos a carpetas


Vamos al asunto práctico de todo esto. Si debes darle permiso a una carpeta en particular (o archivo) ya que la aplicación te da error de permisos, ¿a qué cuenta de Windows darle?

Depende, si los archivos son vitales para la ejecución del worker process o aplicación web:

  • Si estás con IIS 5, dale a ASPNET.
  • Si estás con IIS 6, dale permisos a la cuenta Network Service, ApplicacionPoolIdentity (IIS APPPOOLL\MiSitio.cl), LocalService o LocalSystem (según como lo tengas configurado). Si IIS 6 está corriendo como IIS 5 Isolation mode, la cuenta a darle permisos sería ASPNET. Otra alternativa sería darle permiso al grupo de todos los pools, IIS_WPG, pero esto sería menos seguro.
  • Si estás con IIS 7 o superior, dale permisos Read o Full Control según el caso al grupo IIS_IUSRS del equipo local.

Ahora, si la carpeta es de archivos estáticos (imágenes, CSS/JS, carpeta con logs, base de datos MDB, etc.) y de uso de la aplicación:

  • Si estás con IIS 5, dale a IUSR_NombreMáquina si es acceso anónimo. Si es anónimo pero es out-of-process, dale a IWAM_NombreMáquina, sino es anónimo, dale a la cuenta dada.
  • Si estás con IIS 6, dale a la cuenta del Pool en particular, cuenta IUSR_NombreMáquina, en caso de autenticación anónima. Si no es anónimo, dale a Network Service, ApplicacionPoolIdentity (IIS APPPOOLL\MiSitio.cl), LocalService o LocalSystem (según como lo tengas configurado). Lo otro sería darle al grupo que tiene los usuarios del AppPool, IIS_WPG, pero esto sería menos seguro.
  • Si estás con IIS 7 o superior, dale permisos Read o Full Control según el caso al grupo IIS_IUSRS del equipo local.

Cuentas y grupos según IIS


  • ApplicationPoolIdentity: es una identidad, una cuenta virtual que puedes seleccionar en el AppPool del IIS. En IIS 7.5 es la que está por defecto. En IIS 6 y 7 por defecto la identidad es Network Service. ApplicationPoolIdentity crea una cuenta de usuario dinámico con un ID único. Por ejemplo si mi sitio web es MiSitio, la cuenta que se crea dinámicamente en el sistema es IIS APPPOOL / MiSitio. Accede a la red con esta misma cuenta. Esta cuenta permite darle permisos solo a este usuario que es de ese único AppPool y no a todos. Además, esta cuanta dinámica pasa a ser parte del Grupo IIS_IUSRS/IIS_WPG.
  • Network Service / Servicio de red: es la cuenta usada por IIS 6 en adelante que corre cuando el acceso es anónimo sin impersonation. Se creo con Windows Server 2003. Tiene muy pocos privilegios locales, incluso menos que LocalSystem y menos que la cuenta Administrador. Tiene acceso a la red. El worker process del AppPool corre con esta cuenta en IIS 6 y IIS 7 por defecto, pero la puedes cambiar.
  • LocalService: cuenta local del sistema, muy pocos privilegios, similar a cuenta Network Service pero sin acceso a la red.
  • LocalSystem: cuenta del sistema, es una cuenta local, pertenece al grupo de administradores, tiene acceso a todo el equipo. No tiene acceso a la red.

IIS 5


  • ASPNET: es una cuenta del sistema usada por IIS 5 para ejecutar aplicaciones .NET. Se crea cuando se instala .NET Framework.
  • Anonymous Access: es la autenticación por defecto. Usa la cuenta ASPNET que pertenece al grupo Guest. IIS no pedirá un nombre de usuario ni clave a algún request.
  • Basic Authentication: El cliente envía el nombre de usuario y contraseña en un string codificado como base64. Se recomienda que sólo se use en HTTPSya que no es muy seguro.
  • Digest Authentication: Hecho para reemplazar Basic Authentication, donde primero el servidor envía un string de datos random llamados nonce, luego el cliente envía el usuario, contraseña como hash hacia el servidor.
  • Integrated Windows Authentication (NTLM): El cliente envía usuario y contraseña como hash a través de la red. El navegador entiende que es información encriptada.

IIS 6


  • ASPNET: es una cuenta usada por IIS 6 pero sólo si está en IIS 5.0 isolation mode para ejecutar aplicaciones .NET.
  • IUSR_NombreMáquina: es la cuenta de usuario por defecto para el acceso anónimo a IIS.
  • IWAM_NombreMáquina: es la cuenta de usuario para ejecutar aplicaciones out-of-process en IIS 5.0 isolation mode.
  • IIS_WPG: cuenta de grupo con permisos y privilegios mínimos del usuario necesarias para crear y ejecutar un worker process en el servicio web.
  • Anonymous Authentication / Autenticación Anónima: es lo mismo que lo anterior pero se renombra así desde IIS 6 en adelante. Es la autenticación por defecto.

IIS 7


  • IUSR: en IIS 7 reemplaza al IUSR_NombreMáquina del IIS 6.
  • IIS_IUSRS: en IIS 7 reemplaza al grupo IIS_WPG del IIS 6.
  • Certificate Authentication: En esta versión de IIS, permite autentificar al cliente automáticamente al usar un certificado, es bastante seguro.

IIS 7.5 y superior


  • ApplicationPoolIdentity: es la identidad por defecto de los AppPool desde el IIS 7.5, antes era NetworkService.

Referencias



Traducido, comentado y completado con aspectos actuales del artículo: http://imar.spaanjaars.com/287/how-do-i-determine-the-security-account-that-iis-uses-to-run-my-web-site

viernes, 5 de junio de 2015

Resolución de problemas con Forms Authentication

Herramientas usadas


  • LogParser 2.2
  • Fiddler
  • Microsoft Network Monitor 3.4

Resumen


A menudo, cuando usas Forms Authentication en una aplicación ASP.NET (no aplica para aplicaciones MVC), se da la necesidad de tener a mano una guía de resolución de problemas cuando un request sucede que intermitentemente la aplicación te redirige a la página login de ese mismo sitio. En un ambiente de Desarrollo puedes fácilmente debugear este problema atachando el proceso en el Visual Studio. En ambiente productivo, sin embargo, la tarea se vuelve muy complicada. Para tracear un problema como este, debes logear toda la información que puedas obtener relativa al problema, hasta llegar a la raíz misma de este.

En esta guía, mostraremos un resumen de lo que es el Forms Authentication. Veremos los escenarios en que un usuario puede ser redirigido a la página de login y como capturar datos que sean relevantes para aislar el problema. También veremos como implementar una interfaz IHttpModule para logear información relativa a Forms Authentication.

Resumen de la Autenticación de ASP.NET Forms


La autenticación de formulario permite autenticar un usuario usando tu propio código para mantener el token de autenticación ya sea en una cookie o en la url de la página. La autenticación participa en el ciclo de vida de una página ASP.NET a través de la clase FormsAuthenticationModule. Puedes acceder a la información de la autenticación a través de la clase FormsAuthentication.

Para usar una autenticación de formulario, creas una página login que colecciona credenciales del usuario y que incluyen código para autentificar dichas credenciales. Típicamente configuras la aplicación para que haga un redirect a una página login cuando los usuarios intentan acceder a un recurso protegido, como una página que requiere autenticación. Si las credenciales del usuario son válidas, puedes llamar a métodos de la clase FormsAuthentication para hacer un redirect de vuelta al request que originalmente se solicitó el recurso con el ticket apropiado de autenticación (cookie). Si no quieres hacer un redirect, puedes solo obtener la cookie de autenticación del formulario o setearla. En los request siguientes, el navegador del usuario pasa la cookie de autenticación en el mismo request, el cual hace que se salte la página de login.

Por defecto, la clase FormsAuthenticationModule se agrega al archivo Machine.config. La clase FormsAuthenticationModule administra el proceso de FormsAuthentication.

La siguiente es una entrada del archivo Machine.config:

<httpModule> 
     <add name="FormsAuthentication" type="System.Web.Security.FormsAuthenticationModule" />           
</httpModule>";

Configuras la autenticación por formulario usando un elemento de configuración. En el caso más simple, tienes una página login. En el archivo de configuración, especificas la URL donde se redirigirán los request no autentificados. Entonces defines las credenciales válidas, en un archivo web.config o en un archivo separado. El siguiente ejemplo muestra una sección de un archivo de configuración que muestra una página login ylas credenciales de autenticación del método Authenticate. Los passwords han sido encriptados usando el método HashPasswordForStoringInConfigFile.

<authentication mode="Forms"> 
     <forms name="MyAuthCookie" loginUrl="/Login.aspx">     
       <credentials passwordFormat="SHA1">        
         <user name="Kim" password="07B7F3EE06F278DB966BE960E7CBBD103DF30CA6" />         
         <user name="John" password="BA56E5E0366D003E98EA1C7F04ABF8FCB3753889"/>         
       </credentials>
     </forms>
</authentication>

Luego de una autenticación ok, el módulo de FormsAuthenticationModule setea el valor de la propiedad User a una referencia del usuario autentifico. El siguiente código de ejemplo muestra como leer con programación la identidad del usuario autentificado con formularios.

String authUser2 = User.Identity.Name;

Una forma conveniente de trabajar con formularios de autenticación es usar controles de ASP.NET membership y ASP.NET login. ASP.NET membership te permite guardar y administrar información del usuario e incluir métodos para autentificar usuarios. Controles de ASP.NET login trabajan en conjunto con ASP.NET membership. Estos encapsulan la lógica para consultar las credenciales del usuario, validar al usuario, recobrar o reemplazar passwords, etc. De echo, los controles de ASP.NET membership y ASP.NET login proveen una capa de abstracción sobre la autenticación de formulario. Esta características reemplazan la mayoría de todos le típico trabajo que generalmente haces cuando tienes que usar autenticación de formularios.

Escenarios


Razones en la que un request se auto redirige a la página login.aspx:

La cookie de autenticación se pierde.

Escenario 1:

Un usuario se logea en el sitio web y en algún punto, el cliente envía un request al servidor y la clase FormsAuthenticationModule no recibe la cookie o no puede leerla.

Escenario 2:

La cookie de autenticación puede perderse cuando el navegador del cliente alcanza el límite de cookies creadas. Por ejemplo en Internet Explorer el límite es 20. Cuando ingresa la cookie número 21, el navegador elimina una cookie de la colección. Si se elimina la cookie .ASPXAUTH, el usuario será redirigido a la página de login en el siguiente request.

Escenario 3:

Luego que el request sale del cliente, hay varias capas que pueden afectar a los paquetes que están siendo enviados. Para determinar si algún dispositivo de red está eliminando la cookie, tienes que capturar una traza de red en el cliente y en el servidor, y entonces buscar en el cuerpo del request si está la cookie. Debes asegurarte que en el request del cliente se envío la cookie y chequear con una traza si se recibió ficha cookie.

Time-out en el ticket de autenticación de formulario

Una cosa que debes tener en mente en las aplicaciones hechas con ASP.NET 2.0 o superiores, es que el timeout de la autenticación de formulario cambió a 30 minutos por defecto. Esto significa que luego de 30 minutos de inactividad, el usuario será reenviado al login (nota: Cada vez que se accede al sitio, se resetea el reloj a 30 minutos - así se cuenta sólo cuando no se está haciendo nada).

Si quieres cambiar el valor del timeout a uno más largo, solo debes cambiar el valor en el web.config local (el valor del timeout está en minutos):

<system.web> 
     <authentication mode="Forms">   
       <forms timeout="50000000"/>                 
     </authentication>
</system.web>

Escenario 4:

La autenticación del formulario puede dar timeout antes que el valor seteado en el archivo de configuración.

Si el ticket de autenticación por formulario es generado manualmente por código, el timeout del ticket sobreescribe el valor seteado en el archivo de configuración. Por lo tanto, si ese valor es menor que el valor del archivo de configuración, el ticket del formulario de autenticación expirará antes y viceversa. Por ejemplo, asumamos que el atributo timeout está seteado a 30 (30 min) en tag Form del archivo Web.config y por código valor de Expiration del ticket está en 20. En este caso, el ticket del formulario de autenticación expiará pasados los 20 minutos y no a los 30 y el usuario tendrá que logearse de nuevo.
Event code: 4005
Event message: Forms authentication failed for the request. Reason: The ticket supplied has expired.

Escenario 5

Una aplicación web ASP.NET 4 usa autenticación de formulario y el visor de eventos dice:

Event code: 4005 
Event message: Forms authentication failed for the request. Reason: The ticket supplied was invalid.

Recopilación de datos y resolución de problemas

Resolución del problema 1:

Puedes determinar si un request no contiene una cookie activando el cookie logging de Microsoft Internet Information Services (IIS). Para hacer esto sigue estos pasos en IIS 6:

-Abre IIS Microsoft Management Console (MMC).
-Botón derecho en el sitio web y luego Properties.
-Botón en el tab Web Site y clic en Enable Logging.
-Asegúrate que el formato del log es W3C Extended Log File Format.
-Clic en Properties. -Clic en Advanced tab y clic en Extended Properties.
-Bajo las Extended Properties, selecciona el checkbox Cookie(cs(Cookie)) y Referer (cs(Referer)). En IIS 7 es similar: Cuando seleccionas el formato "W3C" del combo Format y en "Select Fields" selecciona los 2 checkbox de las cookies.

Luego que el problema ocurre, determina cual es el cliente tiene el problema dado su dirección IP. Filtra el log IIS, con la columna "client IP address".

Nota: Puedes usar Log Parser para formatear los logs del IIS. Baja Log Parser del sitio oficial de Microsoft: http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=24659

Un request con cookies quedará registrado así:

2015-06-02 19:54:41 172.31.2.11 POST /Ventas/TestRequest - 80 - 172.31.17.26 Mozilla/5.0+(Windows+NT+6.1;+WOW64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Chrome/43.0.2357.81+Safari/537.36 Cookie1=Hola;+Cookie2=dyxj/ney47DaZiwEMxUJrw/RvTomKx;+Cookie3=NALDO; http://sitio.cl/ 200 0 0 378

Luego de que tengas la lista de request de un usuario específico, busca por los request de la página login. Ya sabes que hay un redirect a esta página, así que busca por los request que suceden antes de la redirección. Si ves que no hay cookie en dichos request, el cliente no la mandó o esta fue eliminada de la red entre el cliente y el servidor.

Nota: El primer request del usuario no tiene la cookie de autenticación a menos que crees un cookie persistente. El log del IIS sólo muestra las cookies que son recibidas en el request. El primer request que debería tener la cookie de autenticación será la request después que suceda se realice el login de forma correcta.

Resolución del problema 2:

Microsoft Internet Explorer tiene las siguientes limitaciones en cuanto a las cookies (limitaciones RFC 2109):
  • 300 cookies en total
  • 4096 bytes por cookie
  • 20 cookies por cada host o dominio

El artículo oficial es: http://support.microsoft.com/kb/306070

La cookie de autenticación puede perderse si se alcanzó el límite de cookies en el cliente. En Internet Explorer tiene la limitación de 20 cookies. Eso quiere decir que si llega una cookie 21, se sobreescribe la primera. Si la cookie .ASPXAUTH es eliminada, el usuario será redirigido a la página de login en el siguiente request. Puedes usar fiddler para ver los header de los request y response y así saber si el cliente está mandando la cookie o no. Puedes bajar Fiddler 2 de Telerik: http://fiddler2.com/fiddler2/

Ejecuta fiddler en el cliente, elimina las trazas http existentes, accede a tu aplicación que implementa la autenticación de formulario, logeate y observa el tráfico http en fiddler para ver si hay algún cambio de la cookie entre el cliente y el servidor. Luego que capturas el tráfico, si fue ok el login, haz doble clic al request para las respuestas, y en tab Cookie ver que se cree la cookie con Set-Cookie.

Por defecto Internet Explorer puede almacenar un máximo de 20 cookies por dominio. Si un servidor envía más de 20 cookies a un cliente, el navegador automáticamente elimina las cookies más viejas.

Cada cookie consiste en un par nombre-valor. Este par debe ser seguido por valores pares de atributos separados por punto y coma. Este límite ha sido aumentado para simplificar el desarrollo y hosting de aplicaciones web sobre dominios que deben usar cookies. Instalando el update 937143 incrementa el número de cookies que Internet Explorer puede almacenar por cada dominio de 20 a 50. Para más detalles mira este link: http://support.microsoft.com/kb/941495

Resolución del problema 3:

Luego que el request sale de un cliente, hay varias capas que pueden afectar a los paquetes que son enviados como un firewall, proxys o balanceadores de carga. Para determinar si un dispositivo de red está eliminando una cookie, tienes que capturar el tráfico de red entre el cliente y el servidor, y ver como está la cookie en el cuerpo del request. Tienes que ver el cliente y ver si la cookie se envió, y luego en el servidor ver la traza para asegurarte que llegó la cookie.

Request del cliente

Este es un request GET luego que el usuario ha sido autenticado. El ticket de autenticación está marcado en amarillo. Esto confirma que la cookie existe en el lado del cliente. Cuando usas una herramienta de captura de red, como Netmon, verás el tráfico que realmente es enviado a través del adaptador.


Request por el lado del servidor

Cuando ves que el request llegó al servidor, te debes asegurar que llegó la misma información que salió del cliente. Si el servidor no recibe la misma información, necesitas investigar otros dispositivos de la red para determinar donde se eliminó la cookie.

Nota: Ha habido casos de filtros ISAPI que eliminan las cookies. Si confirmas que el servidor web recibió la cookie, pero la cookie no se ve en los Logs del IIS, chequea los filtros ISAPI. Tienes que eliminar los filtros y validar que el problema se resolvió.

Resolución del problema 5:

-Si el escenario involucra una granja de servidores o web farm (un balanceador, distintos servidores, una base de datos, un worker process por cada sitio), los Machinekeys deberían ser los mismo en todos lados. Usa el mismo machinekey para mantener la consistencia entre todos los servidores de la granja:

<machineKey validationKey="87AC8F432C8DB844A4EFD024301AC..." decryptionKey="E001A307CCC8B1ADEA..." validation="SHA1" />

-Compara los valores de los timeout de ambos módulos de autenticación de formulario y el módulo sesión de todos los servidores.

-Compara la versión del archivo System.Web.dll dentro de la carpeta Framework de ASP.NET 4 entre todos los servidores de la granja. Cuando falla con el error: Forms authentication failed for the request. Reason: The ticket supplied was invalid. Esto pasa cuando alguno de los servidores no está en la misma versión de Update del MS .NET framework 4.

-Instala Reliability Update 1 para .NET Framework 4 kb2533523 en el servidor que falta y reiniciar ese servidor. Esto corregirá el problema: http://support.microsoft.com/kb/2533523

Fuente: http://www.iis.net/learn/troubleshoot/security-issues/troubleshooting-forms-authentication

jueves, 29 de mayo de 2014

Agregar o Editar Binding en IIS con AppCmd.exe o C#

Hola a todos, he estado jugando con IIS 8 (Windows 8) y me he puesto a ver como agregar y editar un Binding mediante línea de comandos o por código C#.

Sabemos que un Binding es un elemento que permite configurar información requerida por los request que se quieren comunicar con un sitio Web. Un sitio web en IIS puede tener N bindings. Es especialmente útil cuando quieres que diferentes direcciones de sitios web usen un sitio web (archicos y clases .net) unico. Por ejemplo, un holding de empresas que poseen diferentes URL pero el servidor que las procesa es único.
Un binding se conforma de: IP:Puerto:HostName

Vamos por partes, primero creamos un sitio Web vacio y dejamos el binding por defecto:

Ahora, agregamos otro binding para ese mismo sitio desde el mismo IIS para hacer la prueba de lo facil que es usando Add:

Ahora empieza lo interesante, vamos agregar un tercer binding por línea de comandos usando appcmd.exe.
Viendo el sitio oficial de Technet, no dice mucho, da un ejemplo básico de como usarlo en la línea de comandos.
Ejecuta el Command prompt en Modo Administrador sino con algunas instrucciones te puede dar el error:
Cannot read configuration file due to insufficient permissions 

Partamos, ejecutando la ayuda del comando SET de appcmd:
> C:\Windows\System32\inetsrv\appcmd.exe set site "test" /?
Ahí verás las opciones de SET, arriba se ven las opciones para Binding:
Luego ejecuta:
> C:\Windows\system32>C:\Windows\System32\inetsrv\appcmd.exe set site /site.name:test /+bindings.[protocol='http',bindingInformation='*:80:mitest']

Donde: test es mi sitio web ya configurado, http es mi protocolo y *:80:mitest es el binding en si: * es todas las IP, 80 es el puerto y mitest es el nombre del nuevo binding.
Con el + agregas binding a la colección, con el - eliminas.
No puedes ejecutar directamente > appcmd set ... como en el ejemplo ya que no está en las variables de ambiente pordefecto, así que debes usar la rura completa C:\Windows\inetsrv.

Ahora ahora todo ok, tenemos 3 binding.

Si tratamos de modificar un binding por línea de comandos, nos encontramos con el primer problema, no tiene ID (un BindingID), tienes que buscarlo, para luego modificarlo. Si ejecutaste la ayuda de SET, te darás cuenta que no hay algun comando que ayude (al menos no lo encontré).

Ahora veamos como agregar por linea de comandos pero ejecutado por código C#:
protected void Button1_Click(object sender, EventArgs e)
{
 string windir = Environment.GetEnvironmentVariable("windir");
 
 string comando = windir +"\\System32\\inetsrv\\appcmd.exe set site /site.name:test /+bindings.[protocol='http',bindingInformation='*:80:mitest']";
     
 ExecuteCommandAsync(comando);
}

public void ExecuteCommandSync(object command)
{
    try
    {
        System.Diagnostics.ProcessStartInfo procStartInfo = new System.Diagnostics.ProcessStartInfo("cmd", "/c " + command);
       
        procStartInfo.RedirectStandardOutput = true;
        procStartInfo.UseShellExecute = false;                
        procStartInfo.CreateNoWindow = true;
        procStartInfo.WindowStyle = ProcessWindowStyle.Hidden;

        System.Diagnostics.Process proc = new System.Diagnostics.Process();
        proc.StartInfo = procStartInfo;
        proc.Start();
        
        string result = proc.StandardOutput.ReadToEnd();
        
        Console.WriteLine(result);
        Debug.WriteLine(result);
    }
    catch (Exception objException)
    {
        Debug.WriteLine("Error:" + objException.Message);
    }
}

public void ExecuteCommandAsync(string command)
{
    try
    {
        
        Thread objThread = new Thread(new ParameterizedThreadStart(ExecuteCommandSync));
        
        objThread.IsBackground = true;
        
        objThread.Priority = ThreadPriority.Normal;
        
        objThread.Start(command);
    }
    catch (ThreadStartException objException)
    {
        Debug.WriteLine("Error:" + objException.Message);
    }
    catch (ThreadAbortException objException)
    {
        Debug.WriteLine("Error:" + objException.Message);
    }
    catch (Exception objException)
    {
        Debug.WriteLine("Error:" + objException.Message);
        
    }
}
El problema acá, es un tema muy comentado en foros de ayuda como StackOverflow, ya que da un maldito error de permisos:
Cannot read configuration file due to insufficient permissions.
Ya sea ejecutando el Visual Studio como administrador, o dando permisos en la carpeta inetsrv no pude sacar este error. Así que no lo dejé ahí y busque otra vía.

Ya que todo lo anterior no funcionó, queda la alternativa de encontrar alguna clase maestra. Vamos a listar, agregar y modificar via código C# con la clase ServerManager.
Primero, en tu proyecto en Visual Studio debes referenciar a la DLL: C:\Windows\System32\inetsrv\Microsoft.Web.Administration.dll.
Lo otro es que el Pool confugurado en el sitio Web en el IIS, en mi caso uso DefaultAppPool, debe estar correctamente configurado lo que es Identity, sino te dará errores de permiso.
Por ultimo usamos la clase ServerManager donde podemos manipular AppPools, Sitios o Binding.

Por ejemplo, con ServerManager tenemos todo el control y podemos recorrer los Sitios y los AppPools de IIS:
using (ServerManager serverManager = new ServerManager())
{
    if (serverManager.ApplicationPools == null)
        return;

    for (int i = 0; i < serverManager.Sites.Count; i++)
    {
        Microsoft.Web.Administration.ApplicationPoolCollection appPoolCollection = serverManager.ApplicationPools[i];
    }

    if (serverManager.Sites == null)
        return;

    for (int i = 0; i < serverManager.Sites.Count; i++)
    { 
       Microsoft.Web.Administration.BindingCollection bindingCollection = serverManager.Sites[i].Bindings;
    }
}
Si queremos listar todos los bindings e información de todos los sitios instalados en IIS, es simple:
public void ListBinding()
{
    Label1.Text = "";
    using (ServerManager serverManager = new ServerManager())
    {      
        if (serverManager.Sites == null)
            return;

        for (int i = 0; i < serverManager.Sites.Count; i++)
        {
            Debug.WriteLine(Environment.NewLine);
            Debug.WriteLine(serverManager.Sites[i].Id.ToString() + " - " + serverManager.Sites[i].Name);

            Label1.Text += "\r\n" + "[" + serverManager.Sites[i].Id.ToString() + " - " + serverManager.Sites[i].Name + "]";

            Microsoft.Web.Administration.BindingCollection bindingCollection = serverManager.Sites[i].Bindings;

            for (int j = 0; j < bindingCollection.Count; j++)
            {
                Debug.WriteLine(bindingCollection[j].Host +" " + bindingCollection[j].BindingInformation);

                Label1.Text += " Binding (" + j.ToString() + "): " + bindingCollection[j].Host + " " + bindingCollection[j].BindingInformation;                
            }
        }
    }
}
Este es el código que permite agregar un binding a un sitio dado. En mi caso uso un "keyName" que es el nombre del nuevo elemento (un nuevo sub-sitio que puede cambiar su información de binding). También requiere el ID del sitio Web:
public void AddBinding(int id, SiteBinding siteBinding, string KeyName)
{
    using (ServerManager serverManager = new ServerManager())
    {
        if (serverManager.Sites == null)
            return;

        for (int i = 0; i < serverManager.Sites.Count; i++)
        {
            if (serverManager.Sites[i].Id == id)
            {
                Microsoft.Web.Administration.BindingCollection bindingCollection = serverManager.Sites[i].Bindings;
                
                Microsoft.Web.Administration.Binding binding = serverManager.Sites[i].Bindings.CreateElement("binding");

                binding["protocol"] = siteBinding.Protocol;

                binding["bindingInformation"] = string.Format(@"{0}:{1}:{2}", siteBinding.IPAddress, siteBinding.Port.ToString(), siteBinding.HostName);

                bool existe = false;
                for (int j = 0; j < bindingCollection.Count; j++)
                {
                    if (bindingCollection[j].Host == siteBinding.HostName)
                    {
                        existe = true;
                        break;
                    }
                }

                if (existe == false)
                {
                    bindingCollection.Add(binding);
                    serverManager.CommitChanges();
                }

                if (bindingNameBase.ContainsKey(newKeyName) == false)
                {
                    bindingNameBase.Add(newKeyName, siteBinding.HostName);
                }

                
            }
        }
    }
}
El código para editar un binding existente de un sitio web existente. Uso el hashtable para mantener los IDs de los Binding:
public void EditBinding(int id, SiteBinding siteBinding, string keyName)
{
    using (ServerManager serverManager = new ServerManager())
    {
        if (serverManager.Sites == null)
            return;

        for (int i = 0; i < serverManager.Sites.Count; i++)
        {
            if (serverManager.Sites[i].Id == id)
            {
                Microsoft.Web.Administration.BindingCollection bindingCollection = serverManager.Sites[i].Bindings;

                // se elimina el binding
                Microsoft.Web.Administration.Binding bindingTmp = null;
                for (int j = 0; j < bindingCollection.Count; j++)
                {
                    if (bindingCollection[j].Host == bindingNameBase[keyName].ToString())
                    {
                        bindingTmp = bindingCollection[j];                                
                        break;
                    }
                }

                if (bindingTmp != null)
                {
                    bindingCollection.Remove(bindingTmp);

                    //se crea de nuevo
                    Microsoft.Web.Administration.Binding binding = serverManager.Sites[i].Bindings.CreateElement("binding");
                    binding["protocol"] = siteBinding.Protocol;
                    binding["bindingInformation"] = string.Format(@"{0}:{1}:{2}", siteBinding.IPAddress, siteBinding.Port.ToString(), siteBinding.HostName);

                    bool existe = false;
                    for (int j = 0; j < bindingCollection.Count; j++)
                    {
                        if (bindingCollection[j].Host == siteBinding.HostName)
                        {
                            existe = true;
                            break;
                        }
                    }

                    if (existe == false)
                    {
                        bindingCollection.Add(binding);
                        serverManager.CommitChanges();

                        bindingNameBase[keyName] = siteBinding.HostName;
                    }
                }                        
            }
        }
    }
}
Este código se puede refactorizar, (para los más quisquillosos), es solo una muestra de la capacidad de la clase ServerManager.

En resumen, tenemos:

Con IIS Manager:
  • Se puede agregar un binding.
  • Se puede modificar un binding.
  • Se puede eliminar un binding.

Con AppCmd.exe desde Command Promt:
  • Se puede agregar un binding.
  • No se puede modificar un binding de forma fácil (se debe poder pero no lo encontré, si alguien conoce la solución, que lo coloque en los comentarios).
  • Se puede eliminar un binding.

Con AppCmd.exe desde C#:
  • No se puede hacer nada, da error de permisos. Si alguien conoce la solución, que lo coloque en los comentarios.
Con ServerManager desde C#:
  • Se puede agregar un binding.
  • Se puede modificar un binding.
  • Se puede eliminar un binding.
Espero les haya servido. Nos vemos!

Links útiles: