Seguridad en .NET 2.0 con Active Directory y AzMan

A Seguro se lo llevaron preso
(del refranero popular argentino)
 
 Alguna  vez comentábamos, en un curso de Desarrollo .NET Avanzado, lo que pasaría si nos tomaban a todos los (auto-denominados) arquitectos de software un examen sobre Seguridad. Sobre vulnerabilidades, ataques y mecanismos de defensa. Seguramente nos terminarían quitando cualquier responsabilidad de aplicación en producción al evidenciarse lo debilmente preparados para afrontar ataques en ese aspecto que varios de nosotros estaríamos

Ultimamente me tocó estudiar el tema, debido a demandas de clientes. Y quise partir con el pie derecho mirando toda la arquitectura de seguridad disponible en la plataforma .NET 2.0. Me resultó súper interesante y ahora quiero volcar acá lo que aprendí, esperando que le ayude a ganar tiempo al que tenga que planear una arquitectura de seguridad y le interese saber qué hay de nuevo en la propuesta de Microsoft

.NET 2.0 cumple la palabra del comité de Patterns & Practices, de plasmar como parte de la plataforma de ejecución aquellas mejores prácticas de diseño que hayan sido partes de bloques de aplicación liberados en versiones anteriores (.NET 1.1). De esta manera, así como el Updater Application Block, para roll out de versiones en puestos cliente, quedó transformado en la tecnología de distribución ClickOnce (tanto en ejecución como en desarrollo con Visual Studio 2005); el Security Application Block traía un esquema basado en proveedores (providers) intercambiables, que permitían desacoplar el acceso a la API de Seguridad de su implementación. Esa misma implementación del patrón Estrategia (Strategy pattern) hoy es la filosofía del esquema de Seguridad presente en ASP.NET 2.0
Voy a comenzar contando cómo es el nuevo esquema de Autentificación basada en Credenciales. Luego sigo por Autorización según roles. Como tercer punto, destacar el avance alcanzado en Administración de Perfiles. Y finalmente quiero hacer mi propuesta de Arquitectura de aplicación que garantice un esquema de Seguridad Inviolable

Verificación de Autenticidad
En aplicaciones en general, y especialmente en las empresariales, es imperativo confirmar la identidad que un usuario afirma tener, para la cual ofrece una credencial (generalmente una contraseña). Dicha credencial se verifica en un almacén de identidades (identity storage). Este almacén puede ser un producto servidor de directorios, por ejemplo Active Directory, o puede ser una pieza desarrollada por nosotros mismos
Me atrevo a arriesgar que más del 50% de las aplicaciones empresariales (sean o no de misión crítica) usan un esquema de seguridad fatto in casa. Me equivoco?
Tiene una ventaja hacerse uno mismo el sistema de seguridad

  • Confiabilidad. Es el de uno, y por ende uno lo conoce. Lo puede hacer como uno quiera, con el nivel de sofisticación que desée ya que es para uno

Pero esa misma virtud conlleva también sus riesgos. Veamos

  • Complejidad. Cuanto más sofisticado sea lo que uno quiere hacer, mayor complejidad de las rutinas de acceso que uno mismo va a tener que construir (dedicando para ello tiempo y recursos tanto humanos como económicos que se suponían destinados a la aplicación de negocio, no a una parte de su infraestructura)
  • Vulnerabilidad. Es probable que al presupuestarse el costo de la aplicación, ni siquiera nos hayamos detenido a pensar en cómo íbamos a manejar las identidades. Cuando llega el momento de abordar esta problemática se termina desarrollando un mecanismo rudimentario (similar a los otros mecanismos rudimentarios que se desarrollaron para las restantes aplicaciones). Como consecuencia tendremos que conformarnos en realidad con un sistema de seguridad posiblemente pobre (con vulnerabilidades) ya que será construído por el mismo equipo de proyecto que está construyendo lógica de aplicación, y que por ende es muy posible que no sean expertos en disciplinas de seguridad (ataques y contramedidas). Qué loco, no? Mencionábamos como virtud la confiabilidad y ahora como defecto la vulnerabilidad. Parece una contradicción que algo confiable sea vulnerable. Pero esa contradicción es aparente: confiable era lo que esperábamos lograr, vulnerable es lo que probablemente obtengamos
  • Alto costo de desarrollo. Cuanto más conscientes seamos del riesgo al que nos estamos exponiendo es posible que decidamos potenciar (empowering) el expertise de los técnicos que vayan a trabajar en el módulo de seguridad. Cualquiera que vaya a enseñarles a ellos va a querer cobrar por su transferencia de conocimientos (skill transfer). Insisto: se tuvo en cuenta ese costo al presupuestar el proyecto? Voy a tirar a modo de ejemplo algunos servicios que el módulo debería proveer: hashing de las contraseñas, reset de contraseña, deshabilitación de cuenta, política de permisos (lista de control de accesos, access control list o ACL). Y una bomba: aplicación de la política de permisos en el mismo módulo de seguridad (no sea cosa de que la aplicación esté asegurada pero al módulo de seguridad pueda entrar cualquiera a hacer lo que se le de la gana). Dicho en otros términos: por seguridad la llave del auto hay que dejarsela al encargado del garage para que luego éste las deje en un tablero al alcance de todo el mundo
  • Alto costo de mantenimiento. Con una mano en el corazón, aquellos que hayan desarrollado el sistema de seguridad de la aplicación para la cual trabajaron, lo hicieron abierto y compartido para el resto de las aplicaciones? Dicho en otros términos: ese módulo de seguridad participa de un esquema de single sign-on (SSO)? En caso negativo, las restantes aplicaciones tienen sus propios sistemas de seguridad con lo cual mantener el sistema de seguridad de nuestra aplicación es un costo extra de nuestra aplicación. Sumado a los costos extras de todas las aplicaciones que tienen sus sistemas de seguridad individuales, sacá la calculadora y hacé la cuenta de cuánta plata está tirando tu organización a la calle. Es anecdótico pero lo cuento: este artículo está inspirado en un trabajo que hice para un cliente que me confesó que, si bien a sus proveedores les exigía como premisa contractual que entregasen productos cuya seguridad se basase en el servidor de directorios corporativo, ellos mismos desarrollaban sistemas de seguridad en una base por aplicación. En casa de herrero, cuchillo de palo

Mi recomendación? El manejo de la seguridad es comparable a la base de datos: si querés hacete una vos mismo, vale pero… para qué dilapidar esfuerzos en algo que ya está creado, madurado en la forma de productos y probado exitosamente en terreno? Seguro que pagar una licencia de este tipo de productos es más caro? Beneficios de usar un producto servidor de directorios

  • Robustez. Incorporan características de seguridad probadas y promueven el uso de las mejores prácticas para evitar o repeler ataques
  • Bajo costo de uso. Los mecanismos de acceso al almacén de identidades ya están implementados. Lo único que se debe pagar aquí es la curva de aprendizaje de sus API‘s (en la plataforma .NET tenemos (System.DirectoryServices y System.DirectoryServices.ActiveDirectory). En la última parte de este artículo, al proponer una arquitectura de referencia, voy a ofrecer una forma de atenuar este costo para el proyecto global por la vía del encapsulamiento y re-uso
  • Bajo o nulo costo de mantención. Nulo si usamos el sistema de seguridad como viene, bajo si eventualmente creamos cierta API que permita desacoplar la parte cliente de la seguridad de las API‘s del producto (recomendable)

En este último punto hay una excelente noticia: ASP.NET 2.0 provée una API sencilla para acceso a un almacén de identidades basada en un esquema de Proveedor de Membrecía (Membership Provider). La idea es configurar en el archivo de configuración de la aplicación (por ejemplo el web.config) cuál va a ser el proveedor de membrecía que vamos a utilizar. En la aplicación sólo hay que hablar con la clase estática System.Web.Security.Membership, que ya luego ésta se encarga de hablar con el proveedor de membrecía que hayamos configurado

ASP.NET 2.0 viene ya con dos proveedores de membrecía implementados

  • Active Directory
  • SQL Server 2005 (que maneja un esquema de tablas propio)

Aún así, es posible crearnos nuestro propio proveedor de membrecía extendiendo la clase abstracta System.Web.Security.MembershipProvider

Veamos un ejemplo hipotético de deployment basado en Active Directory. El archivo web.config podría contener
 
<connectionStrings>
 <add name="ADConnectionString" connectionString="
LDAP://dominio.cl/CN=Usuarios,DC=dominio,DC=cl" />
</connectionStrings>

<membership defaultProvider="MyADMembershipProvider">
  <providers>
    <add
       name="MyADMembershipProvider"
       type="System.Web.Security.ActiveDirectoryMembershipProvider, System.Web, Version=2.0.0.0,
             Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
       connectionStringName="ADConnectionString"
       connectionUsername="dominioadministrador"
       connectionPassword="password"/>
  </providers>
</membership>

Antes de que algún purista me salte a la yugular por dejar credenciales disponibles en el archivo de configuración de la aplicación, una evidente mala práctica, dejenme como última voluntad la posibilidad de aclarar que es posible encriptar secciones de archivos de configuración. De modo que cualquiera que gane acceso de edición del archivo no pueda, empero, hacerse de las credenciales. Con DPAPI acá y con RSA acá

Este enfoque basado en proveedores no hace más que implementar el patrón de diseño Estrategia (Strategy pattern) del legendario Gang of Four (GoF). El beneficio es que desacopla los accesos de seguridad de la aplicación del back-end que implemente el almacén de identidades. Nuestra aplicación sólo habla con la clase Membership, como en estos ejemplos (aviso que uso Visual Basic.NET porque sé que los amantes de Java o C# lo entienden, más allá de que no les guste)

Sub Logon_Click(sender As Object, e As EventArgs)
   Dim user As String = userName.Text
   Dim password As String = passWord.Text

   ‘ Valida el usuario y en caso de ser correcto, queda logoneado
   If (Membership.ValidateUser(user, password))
      FormsAuthentication.RedirectFromLoginPage(user, false)
   Else
      errorMsg.Text = "Usuario o contraseña incorrecta"
   End If
End Sub



   ‘ Creacion de usuario
   Membership.CreateUser(NewUserName.Text, NewUserPassword.Text)


   ‘ Eliminar usuario
   Membership.DeleteUser(userName)


   ‘ Cambio de contraseña
   Dim user As MembershipUser = Membership.GetUser(userName)
   user.ChangePassword(user.GetPassword(), newPassword)

Es más simple de asimilar que la API original de, por caso, Active Directory, si bien se apoya en la potencia de éste

En aplicaciones web, se habla de varios modos de autentificación, varios mecanismos de hacer conocer al lado servidor la identidad del usuario. Me voy a referir principalmente a dos: Windows y Forms

El modo Windows debería ser preferido siempre que esté disponible ya que

  • Habilita single sign-on (SSO) dentro de un dominio. Esto es, evitar tener que volver a ingresar / enviar las credenciales de un usuario ya logueado al dominio
  • Es más seguro dado que no envía la contraseña a través de la red (como si ocurre en los otros modos de autentificación) sino que lo que se envía es la identidad Windows (rescatable en la propiedad HttpContext.Current.User.Identity)
  • Se impregna de las políticas de seguridad en contraseñas que ya tenga nuestro dominio (complejidad, expiración, máscara de contraseña, etc)

El siguiente artículo explica cómo configurar nuestra aplicación web para que se personifique como el usuario original del dominio tanto en forma general (para cualquier página de la aplicación), como bajo demanda. En este último caso y, por defecto, el usuario es el genérico que se usó para arrancar el proceso ASP.NET

Cuando no esté disponible el modo Windows, la alternativa es el modo Forms. Basicamente consiste en tener una página a modo de formulario HTML. La misma recibe las credenciales y las envía al servidor para, en forma programática, acceder al almacén de identidades e intentar loguear al usuario

Este caso es típicamente aplicable a escenarios de extranets. Se debe considerar especialmente

  • Usar SSL para proteger credenciales y cookies de autentificación. Cómo hacerlo? Ver acá
  • Si el almacén de identidades es una base de datos, otorgar especial atención a la posibilidad de inyección de SQL (SQL injection). Este artículo lo explica mejor, indicando cómo evitarlo
  • No encriptar las contraseñas sino hashearlas (es decir, encriptarlas en un sentido unicamente: que no sea reversible el proceso)
  • Limitar los accesos al almacén de identidades
  • Dividir el sitio en áreas públicas (donde se pueda navegar en forma anónima) y áreas privadas a las cuales cualquier usuario anónimo sea redirigido a la página que contiene el formulario de sign-on

Visual Studio 2005 tiene, como las versiones anteriores, una finalidad primaria que es la de acelerar tanto como se pueda los procesos de desarrollo de aplicaciones. En este caso, esto se pone en práctica con una categoría de controles configurables relacionados con el manejo de identidades. Estos controles son completamente personalizables en su aspecto visual, en tanto que en su comportamiento encapsulan las llamadas al proveedor de membrecía. En otras palabras, se los arrastra y suelta en la página, se los personaliza y se compila ésta. Punto. 0 (cero) líneas de código

Algunos de estos controles:


Login


CreateUserWizard (asistente de creación de usuario)


PasswordRecovery (recupero de contraseña)


ChangePassword (cambio de contraseña)

Como decía, estos controles son sólo algunos de los disponibles. Son personalizables estéticamente y los eventos son interceptables. Especialmente el control Asistente de Creación de Usuarios (CreateUserWizard) es personalizable incluso en cuanto a las etapas del asistente, los campos a solicitar y lo que hacer con ellos al pasar a la etapa siguiente o anterior. Grosso

Autorización según Roles
Tener garantías de autenticidad de la identidad es necesario pero no suficiente: aún hay que constatar si al usuario le está permitida realizar la operación para la cual se le había requerido acreditarse. La lista de operaciones incluye, pero no se restringe a

  • Partes de un sitio (subdirectorios, páginas, servicios web, otros tipos de archivo, …)
  • Operaciones de negocio, de infraestructura, …
  • Consultas, reportes
  • Pantallas, campos de un formulario (sea para verlo o no, y en el primer caso para poder, además, modificarlo o no)

ASP.NET 2.0 presenta nuevamente un enfoque basado, esta vez, en un Proveedor de Roles (Role Provider). Esta vez, se provéen tres implementaciones

  • Active Directory
  • SQL Server 2005
  • Windows (sólo lectura, no se puede alterar el esquema)

Por supuesto, como con el proveedor de membrecía, es posible hacer proveedores de rol personalizados mediante la extensión de la clase abstracta System.Web.Security.RoleProvider

El mecanismo basado en Active Directory se apoya en el Administrador de Autorizaciones de Windows (Authorization Manager o AzMan). AzMan es parte de Windows 2003 SP1, aunque está disponible también para Windows XP y Windows 2000 en forma gratuita

AzMan provée una GUI para definir roles y operaciones. Los roles se pueden anidar jerarquicamente (para modelar, por ejemplo, el organigrama de una organización) y se les pueden asignar usuarios (tanto de Windows como de Active Directory)

A los roles, se les asignan las operaciones que pasan así a tener habilitadas

Toda esta información se puede persistir tanto en Active Directory, como en Active Directory Application Mode (ADAM, del que ahora hablaremos) o bien en un archivo XML

Esto representa un beneficio a la hora de desarrollar: no hace falta tener un Active Directory disponible en el ambiente de desarrollo. Con un archivo XML el desarrollador puede arreglarse. Esto es nuevamente una implementación del patrón de diseño Estrategia en el que también habíamos visto que se basa el esquema de Proveedores de Seguridad de ASP.NET 2.0

La configuración en el web.config no va a diferir conceptualmente

<connectionStrings>
 <connectionStrings>
    <add name="AzManPolicyStoreConnectionString"
       connectionString= "msldap://servidor:puerto/CN=AzManADAMStore,
        OU=SecNetPartition,O=SecNet,C=CL
"/>
  </connectionStrings>
</connectionStrings>

<roleManager
    enabled="true"
    cacheRolesInCookie="true"
    defaultProvider="RoleManagerAzManADAMProvider"
    cookieName=".ASPXROLES"
    cookiePath="/"
    cookieTimeout="30"
    cookieRequireSSL="false"
    cookieSlidingExpiration="true"
    createPersistentCookie="false"
    cookieProtection="All">
    <providers>
        <add name="RoleManagerAzManADAMProvider"
     type="System.Web.Security.AuthorizationStoreRoleProvider, System.Web, Version=2.0.0.0, Culture=neutral, publicKeyToken=b03f5f7f11d50a3a"
             connectionStringName="AzManPolicyStoreConnectionString"
             applicationName="aplicacion"/>
    </providers>
</roleManager>

Notar especialmente que dentro del tag providers se agrega un nodo que tiene un atributo llamado applicationName. Esto es porque AzMan permite definir roles en subniveles que bien se pueden hacer concordar con aplicaciones. No tienen por qué ser transversales a la organización. Esta filosofía también va a regir una herramienta de la que ya hablaremos: ADAM

El uso de la API de Roles es tan versatil como el visto previamente de Membrecía (okey: toca C# ahora)

   // Si el usuario no está en el rol se lo añade
   if (!Roles.IsUserInRole("Aprobadores"))
   {
       Roles.AddUserToRole("jperez", "Aprobadores");
       // Se deben tener privilegios de modificación de roles para que
       // la línea anterior no arroje excepción
   }

   // Si el usuario está en el rol se lo quita
   if (Roles.IsUserInRole("Aprobadores"))
   {
       Roles.RemoveUserFromRole("jperez", "Aprobadores");
       // Se deben tener privilegios de modificación de roles para que
       // la línea anterior no arroje excepción
   }

Esta API no necesariamente trabaja a nivel 1:1 ("un usuario, un rol" por vez). Es posible agregar o remover más de usuario a más de un rol en una sola llamada a método

Existe dos características de AzMan que no han sido modeladas en la API de Roles de ASP.NET 2.0, y que seguramente nos van a forzar a invocar a la API de AzMan directamente (en la biblioteca AZROLESLib). La primera de ellas tiene que ver con un caso típico que se prefiera no estar preguntando en aplicaciones si el usuario que está logueado tiene determinado rol porque esto deja acoplamientos en la aplicación respecto de los roles definidos (que algún día la organización puede determinar aggiornar). Para evitarlo, AzMan provée un mecanismo basado en reglas expresables en la forma de scripts. De manera tal que la aplicación sólo debe preguntar si una determinada regla de AzMan se satisface, lo que puede hacer enviando parámetros de la aplicación al script

La otra característica tiene que ver con poder consultar todas las operaciones que un usuario tiene habilitadas (infiriéndolas de los roles que el usuario tenga). Ejemplo típico es el menú de opciones donde queremos mostrar deshabilitadas las opciones a las que el usuario no tiene permiso, pero conocer todas ellas en un sólo roundtrip. Esto tampoco está modelado en la API de Roles por lo que habrá que ir por AzMan directamente. Una pena. Al menos el código de ejemplo está acá

Finalmente, antes de pasar al tercer tema, quería destacar otra característica de ASP.NET 2.0 que nos ahorra tiempo al desarrollar y tiene que ver con que el control de acceso a partes del sitio web se pueden restringir según roles desde la misma configuración de la aplicación (web.config). 0 (cero) líneas de código nuevamente porque si lo hacemos desde la configuración, las llamadas a la API de Roles las hace ASP.NET 2.0 por nosotros. Veamoslo en este ejemplo

<configuration>
   <!– carpeta paginasMiembros –>
   <location path="paginasMiembros">
       <system.web>
            <authorization>
               <!-– solo usuarios del rol Manager –>
               <allow roles="Manager" />
               <!-–usuarios anonimos denegados –>
               <deny users="?" />
            </authorization>
          </system.web>
        </location>
   …
</configuration>

Perfil de Usuario por Aplicación
Active Directory tiene metadatos por defecto para modelar el usuario, no obstante ciertas aplicaciones podrían querer guardar información adicional de los mismos, como ser

  • Segmento de cliente
  • Cantidad de visitas al portal en el último mes
  • Información de contabilidad

Esta información extra no es relevante para todos los usuarios, sino sólo para aquellos que vayan a usar determinada aplicación. Incluso, esos usuarios pueden ser ajenos a la organización: clientes, proveedores, etc. Por ende, hacer modificaciones al almacén universal de identidades probablemente sería inviable, ya que no todas las entradas del almacén usarían todas las propiedades. Además, los metadatos (la estructura) de cada identidad tendería a ser gigantezca con el tiempo

Existen mecanismos para abordar este problema

  • Active Directory Application Mode (ADAM)
  • El Proveedor de Perfilado (Profile Provider) de ASP.NET 2.0

ADAM es un Active Directory simplificado, accesible vía LDAP (lo que lo plantea como solución para entornos Microsoft y no Microsoft), y exponiendo la misma API: System.DirectoryServices
En cambio, al contrario que Active Directory, no está orientado a dominios sino a instancias (una para cada aplicación). En cada instancia permite redefinir metadatos de objetos (usuarios, etc). Estos esquemas habilitan el modelado del perfil de usuario que la aplicación requiere. Esto implica una ventaja frente a Active Directory. Es común en varias organizaciones, aprovechando que ADAM y Active Directory se pueden sincronizar, contener las identidades en el segundo y las propiedades adiciones de cada usuario por aplicación en ADAM

Sin embargo, presenta algunas contras

  • El modelo de datos, si bien es potente, no es trivial
  • No se corresponde directamente con el modelo de objetos de .NET y eso tiene una consecuencia: el desarrollo más caro, pues hay que mapear a mano propiedades de objetos .NET a propiedades del perfil en ADAM. Hablábamos de no usar horas-hombre ni recursos del proyecto para desarrollar tareas de infraestructura

El Proveedor de Perfilado surgió como una forma versátil de mantener las preferencias de un usuario en un sitio web (similar a los datos de una sesión, pero haciendo que sobrevivan a distintas sesiones). Como siempre, con la premisa de ahorrarle al desarrollador el tener que generar código adicional para restaurar el perfil de usuario al cargar una página y guardar el nuevo estado al descargarla, toda esa plomería (plumbing) la hace ASP.NET 2.0. El objeto Profile tiene el perfil del usuario siempre disponible

Una primera característica de este framework es que los perfiles se basan en objetos de la plataforma .NET. En el ejemplo a continuación, definido nuevamente en el web.config (veamos)

<profile>
   <properties>
      <!– por defecto las propiedades son de tipo string –>
      <add name="Nombre" defaultValue="??" allowAnonymous="true" />
      <add name="Apellido" defaultValue="??" allowAnonymous="true" />
      <!– los tipos de las propiedades son clases .NET –>
      <add name="Carrito" type="Carrito" serializeAs="Binary"
       allowAnonymous="true" />
      <!– se pueden definir grupos, similares a struct en C#: clases sin comportamiento –>
      <group name="Direccion">
         <add name="Calle" allowAnonymous="true" />
         <add name="Ciudad" allowAnonymous="true" />
      </group>
   </properties>
</profile>

Esta definición de perfil es automáticamente compilada en una clase .NET que queda en la misma carpeta App_config del directorio virtual de la aplicación a donde van a parar las páginas ASP.NET que se compilan. Por consiguiente, nuestro objeto Profile queda integrado al proyecto en Visual Studio 2005 y sus propiedades están disponibles vía IntelliSense

   Profile.Nombre = txtNombre.Text

   ‘ Acceso a propiedades agrupadas en Direccion
   Dim calle as String = Profile.Direccion.Calle

   ‘ La propiedad Carrito tiene conducta!!
   Dim totalCompra As Decimal = Profile.Carrito.CalcularTotal()
   ‘ Tratá de hacer esto mismo con los objetos de ADAM!
   Profile.Carrito.AgregarItem(id, nombreItem, precio)

Por defecto, los perfiles de los usuarios del sitio se serializan y quedan almacenados en la carpeta App_data del directorio virtual de la aplicación. No obstante, para escenarios de granjas multiservidores, está implementado un proveedor de perfilado que persiste los perfiles en SQL Server 2005

ASP.NET 2.0 se completa con otros proveedores de los que no vamos a hacer mención acá. No obstante para aquel que esté interesado, éste puede ser un buen artículo

Arquitectura de Referencia
Hasta ahora hemos hecho una descripción de las características de seguridad incluídas en ASP.NET 2.0. Cómo montarlas en forma eficiente en nuestra aplicación? Dónde se debe ubicar la lógica de seguridad de modo de no estar repitiendo por cada miembro de cada clase de negocio qué roles deberían tener acceso a ella?

Hay una primera regla que dice que si bien poner políticas de seguridad en el cliente de la aplicación no es malo, e incluso puede ser bastante oportuno para evitar roundtrips innecesarios, indefectiblemente es en el servidor donde deben estar las políticas de seguridad como línea de fondo. La explicación es obvia: el cliente y el servidor se comunican mediante un protocolo determinado (XML sobre HTTP, etc). Si las políticas de seguridad sólo se implementasen en el cliente, cualquier cliente extraoficial que implemente el protocolo de comunicación entra al servidor a hacer lo que se le de la gana: nadie nos garantiza una vez en el servidor que venimos desde la aplicación cliente que creíamos haber venido

Entonces ponemos las políticas del lado del servidor, y para ello lo ideal es centralizarlas en un componente que sea la puerta de entrada de cualquier requerimiento de los clientes (a lo sumo, uno por tecnología de canal). Existe un patrón de diseño que cumple esas cualidades y se llama Controlador Frontal (Front Controller). Aporta un beneficio fundamental que es el de centralizar el control de modo que cualquier modificación al controlador queda disponible en todo lo que quedaba bajo su ámbito

Nuestro controlador debe incluir al menos dos componentes: el componente de seguridad, que implemente lo que se comentó a lo largo del artículo, y un componente de despacho (dispatch) al componente de lógica de negocio que se pretendía acceder

Un problema, no obstante, que prevalece es que quien gane acceso a nuestro servidor aún pone en riesgo el saltearse el Controlador Frontal, entrando por una puerta paralela, luego instanciar un componente de negocio y realizar desde allí operaciones ilegales burlando la seguridad que pusimos en lo que creíamos que iba a ser la única entrada. Si al menos tuvieramos una forma de forzar que a nuestros componentes de lógica de negocio sólo se los pueda invocar desde el Controlador Frontal

Existe esa posibilidad! Es una característica de .NET presente desde las primeras versiones y se llama Seguridad de Acceso al Código (Code-Access Security o CAS). Es una forma de limitarle al código el contacto con recursos variados, como ser

  • Archivos o servidores a los que pueden acceder y con qué objetivo
  • Ensamblados o direcciones desde los que pueden aceptar invocaciones
  • Usuarios o Roles que pueden ejecutar ciertos métodos
  • Y muchísimos "etc” más

Estas políticas se basan en atributos y se pueden fijar para todo un ensamblado, para toda una clase o para algún miembro en particular de una clase

La idea sería que los ensamblados con lógica de negocio sólo puedan ser accedidos desde el ensamblado del Controlador Frontal. Este atributo se fija, en el archivo assembly_info, de la siguiente manera

StrongNameIdentityPermission(SecurityAction.LinkDemand,
                                PublicKey="00240000048…97e85d098615")

La clave pública será aquella que se extraiga del ensamblado del Controlador Frontal. La clave privada para firmar este ensamblado sólo debe poseerla el equipo a cargo de la seguridad. Esta es sólo una de las tantas maneras de fijar la Seguridad de Acceso al Código (CAS)

En la figura adjunta (ver más abajo) se ofrece un diagrama de componentes de esta arquitectura propuesta

Termino acá un extenso artículo que espero que aporte conocimiento a aquellos que quisieran conocer novedades en Manejo de Identidades en ASP.NET 2.0. Para los que deséen continuar propongo ir directo a revisar los trabajos del comité de Patterns & Practices de Microsoft. Especialmente hay un libro interesante llamado Mejoras a la Seguridad de la Aplicación: Ataques y Contramedidas (Improving Web Application Security: Threats and Countermeasures). El mismo se puede descargar haciendo click aquí

En un artículo posterior y especialmente a pedido de Germán, voy a cubrir la versión del Security Application Block disponible en Enterprise Library 2.0, a liberarse en diciembre aunque ya existe un CTP para descargar (click acá)

Esta entrada fue publicada en Software Architecture. Guarda el enlace permanente.

18 respuestas a Seguridad en .NET 2.0 con Active Directory y AzMan

  1. German dijo:

    Buen articulo Diego! Sería muy interesante que cuentes como se complementa con el Security App.Block de EntLib 2.0.

  2. Diego dijo:

    Muchas gracias Germán por tu pronto feedback!! Premio al mejor lector!!!! Prometo dedicarme a toda la Enterprise Library 2.0 en breve. Te doy una primera impresión del Security Application Block según el CTP liberado en Noviembre. El mismo comité destaca que le han podado mucha de la funcionalidad basada en proveedores que traía la versión 1.0 (para la plataforma .NET 1.1, liberada en Enero pasado y actualizada en Junio) ya que el modelo de Seguridad de ASP.NET 2.0 iba a adquirir este esquema (como efectivamente pasó). Ha quedado tan menguado el Security Application Block en el CTP de Noviembre que, honestamente y hablo por mi caso personal, me veo usando la Enterprise Library 2.0 pero no me veo usando especialmente ese bloque (tiene algo útil que es la emisión de certificados de logon que te puede a ayudar a implementar a un costo menor -no gratis- un Single Sign-On entre aplicaciones MS y no MS)Quiero mostrar el comentario del comité respecto de este bloque, disponible en http://msdn.microsoft.com/library/en-us/dnpag2/html/EntLib2.asp):Security Application BlockMuch of the functionality from the previous release of the Security Application Block has been removed because new features in the .NET Framework 2.0 provide equivalent functionality. Specifically, the factories, interfaces and providers for authentication, roles and profile have been removed. Equivalent functionality is provided by the new System.Web.Security.Membership class and System.Web.Profile namespace. (…) The new Security Application Block still includes the factories, interfaces and providers for authorization and security caching.

  3. Unknown dijo:

    Excelente artículo Diego!!!

  4. Unknown dijo:

    Diego esta muy bueno tu artículo, pero me quedan algunas dudas, por ejemplo:- En el connection string al escribirlo de esta manera:<connectionStrings> <add name="ADConnectionString" connectionString="LDAP://dominio.cl/CN=Usuarios,DC=dominio,DC=cl" /></connectionStrings>A que te refieres con dominio.cl ??? Que es lo que va escrito ahi ???- El type que escribes en la etiqueta add de membership:type="System.Web.Security.ActiveDirectoryMembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"Para que es usado??? Cual es su funcion???muchas gracias de antemanoedgar

  5. Unknown dijo:

    se me habia olvidado comentar que estoy trabajando con ADAM y Visual Estudio 2005 C#

  6. Diego dijo:

    Estimado Edgar, el string de conexión al que haces referencia"LDAP://dominio.cl/CN=Usuarios,DC=dominio,DC=cl"
    es la entrada en protocolo LDAP al contexto en el servidor de directorio de la organización. dominio.cl debe ser sustituido por el nombre con que el servidor esté publicado en el servidor de nombres de dominio (DNS). En concreto, el nombre que el DNS usa para devolver la dirección IP del servidor de directorioRespecto detype="System.Web.Security.ActiveDirectoryMembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"la respuesta corta sería "es el nombre de la clase que hace de proveedor de membrecía" (en ese ejemplo, la clase que va contra Active Directory). La respuesta larga sería "es el nombre completamente calificado -fully qualified name- de la clase que hace de proveedor de membrecía"La cosa es así: una de las premisas con las que se fundó .NET era la de terminar con el infierno de las DLL’s (DLL hell). Este problema se presentaba cuando una aplicación requería modificar una dll que estaba siendo compartida por otras aplicaciones. Registrar la versión nueva podía traer cambios de conducta (breaking changes) que en casos extremos implicaban la interrupción de viejas aplicacionesPara evitar esto, .NET habilita la posibilidad de registrar más de una versión de una misma DLL, a efectos de evitar en estos casos extremos la interrupción de aplicaciones que por x motivos no puedan actualizarse a la última versión de la DLLEse string tan raro que veías allí especifica, hasta la primera coma, el nombre de la clase; luego el nombre del ensamblado, luego la etiqueta de versión con que se registró y otros parámetros más como el idioma y la clave pública si la DLL está firmada (strong-named assemblies)Puedes encontrar más info al respecto en este link http://msdn2.microsoft.com/en-us/library/k8xx4k69(en-US,VS.80).aspx

  7. Unknown dijo:

    Diego muchas gracias por responderme, es la primera vez que lo hacen desde que estoy buscando respuestas sobre este tema.
    Aunque llego un poco tarde no importa lo que vale es la intencion.
    Llego un poco tarde porque yo segui investigando y encontre las
    respuestas a casi todas mis dudas. En la pagina que pongo a
    continuacion
    (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnaspp/html/bucupro.asp)
    esta descrito como hacer un proevedor de membresia para ADAM y ademas
    esta el codigo de un ejemplo ya desarrolado, aunque hay que hacerle
    algunos arreglitos funciona,  por si te interesa abordar sobre ese
    tema.
    Sigue escribiendo articulos  interesantes como este que no mucha gente lo hace en español. Gracias nuevamente.

    • Ronald Renteria Hinestroza dijo:

      Genial el articulo pero tengo un par de problemas cuando se intenta acceder al repositorio de Usuarios.

      Descripción: Excepción no controlada al ejecutar la solicitud Web actual. Revise el seguimiento de la pila para obtener más información acerca del error y dónde se originó en el código.

      Detalles de la excepción: System.DirectoryServices.DirectoryServicesCOMException: Error de operación.

      Error de código fuente:

      Línea 383: dsUser.SearchScope = SearchScope.OneLevel;
      Línea 384:
      Línea 385: if ((dsUserSearchResult = dsUser.FindAll()) != null)
      Línea 386: {
      Línea 387: Count = dsUserSearchResult.Count;

      Archivo de origen: C:\Users\ronald.renteria\Documents\Visual Studio 2008\Projects\H614SolutionBase\MyMembership\MyMembershipProvider.cs Línea: 385

      Seguimiento de la pila:

      [DirectoryServicesCOMException (0x80072020): Error de operación.
      ]
      System.DirectoryServices.DirectoryEntry.Bind(Boolean throwIfFail) +378094
      System.DirectoryServices.DirectoryEntry.Bind() +36
      System.DirectoryServices.DirectoryEntry.get_AdsObject() +31
      System.DirectoryServices.DirectorySearcher.FindAll(Boolean findMoreThanOne) +78
      System.DirectoryServices.DirectorySearcher.FindAll() +9
      SIFAMembership.SIFAMembershipProvider.SIFAFindUsers(String Filter, DirectoryEntry SearchRoot, Int32 PageIndex, Int32 PageSize, Int32& Count) in C:\Users\ronald.renteria\Documents\Visual Studio 2008\Projects\H614SolutionBase\MyMembership\MyMembershipProvider.cs:385

      [ApplicationException: Error retrieving users]
      SIFAMembership.SIFAMembershipProvider.GetAllUsers(Int32 pageIndex, Int32 pageSize, Int32& totalRecords) in C:\Users\ronald.renteria\Documents\Visual Studio 2008\Projects\H614SolutionBase\MyMembership\MyMembershipProvider.cs:264
      System.Web.Security.Membership.GetAllUsers(Int32 pageIndex, Int32 pageSize, Int32& totalRecords) +65
      System.Web.Security.Membership.GetAllUsers() +26
      SIFAMembership.MembershipObjectDataSource.GetAllUsers() in C:\Users\ronald.renteria\Documents\Visual Studio 2008\Projects\H614SolutionBase\MyMembership\MembershipObjectDataSource.cs:15

      [TargetInvocationException: Se produjo una excepción en el destino de la invocación.]
      System.RuntimeMethodHandle._InvokeMethodFast(Object target, Object[] arguments, SignatureStruct& sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner) +0
      System.RuntimeMethodHandle.InvokeMethodFast(Object target, Object[] arguments, Signature sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner) +71
      System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks) +350
      System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) +29
      System.Web.UI.WebControls.ObjectDataSourceView.InvokeMethod(ObjectDataSourceMethod method, Boolean disposeInstance, Object& instance) +488
      System.Web.UI.WebControls.ObjectDataSourceView.ExecuteSelect(DataSourceSelectArguments arguments) +1247
      System.Web.UI.DataSourceView.Select(DataSourceSelectArguments arguments, DataSourceViewSelectCallback callback) +19
      System.Web.UI.WebControls.DataBoundControl.PerformSelect() +142
      System.Web.UI.WebControls.BaseDataBoundControl.DataBind() +73
      System.Web.UI.WebControls.GridView.DataBind() +4
      TestAdam._Default.Page_Load(Object sender, EventArgs e) in C:\Users\ronald.renteria\Documents\Visual Studio 2008\Projects\H614SolutionBase\TestAdam\Default.aspx.cs:14
      System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr fp, Object o, Object t, EventArgs e) +14
      System.Web.Util.CalliEventHandlerDelegateProxy.Callback(Object sender, EventArgs e) +35
      System.Web.UI.Control.OnLoad(EventArgs e) +99
      System.Web.UI.Control.LoadRecursive() +50
      System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +627

  8. Juan dijo:

    Hola buen día, espero que aún pueda leer mi comentario. El punto es que justo estoy en la búsqueda de la información expuesta en su artículo, no obstante mi gran problema es que todo lo que encuentro es para Web (ASP.NET) y lo que deseo es aplicarlo pero a Windows, sobretodo la parte de roles (y si es con el Enterprise Library, mejor). ¿Alguna ayuda al respecto? Gracias de antemano.

  9. Diego dijo:

    Fijate si ayuda este artículo, http://msdn.microsoft.com/msdnmag/issues/05/04/Security/default.aspx, publicado en MSDN Magazine en Abril del año pasado

  10. Manolo dijo:

    Enhora buena por el artículo, llevaba tiempo buscando y es perfecto, gente como vos son las que hacen grandes la web!Gracias 😀

  11. Diego dijo:

    Gracias JIMAN!!   😀
     
    Gracias por los elogios que ayudan!!
     
    Espero que te guste éste, mi último post, sobre frameworks de arquitectura: Cómo Construir Frameworks (Sí, Leíste Bien: "Cómo")

  12. Ronald Renteria Hinestroza dijo:

    Saludos..
    Esta muy interesante tu articulo, pero tengo un pregunta, es posible que tengas algún ejemplo de la implementación de ADAM

  13. Salustiano dijo:

    Hola,
    Muy buen articulo, pero tengo una consulta, como se manejaría para el caso de un cliente externo(ver sus movimientos, saldos, etc.), también lo ingresaría al AC, o como lo plantearías??

    Gracias,

  14. Gabriela L. dijo:

    Hola. Tengo una consulta respecto de la clase Membership, para el manejo de la seguridad.
    Baje el codigo fuente de PeterKellner.net, pero no estan definidos GetAllUsers y GetUser()
    Tengo el using System.Web.Security, estoy usando SqlMembershipProvider
    Alguien puede indicarme de donde saco este codigo?
    Gracias

  15. Oh my goodness! Incredible article dude! Thank you so much, However
    I am experiencing issues with your RSS. I don’t understand the reason why I am unable to join it. Is there anybody getting identical RSS problems? Anybody who knows the solution will you kindly respond? Thanx!!

  16. I am genuinely happy to glance at this blog posts which includes plenty of helpful facts, thanks for providing these kinds of information.

  17. blogspot.com dijo:

    Fantastic website you have here but I was wondering if you knew of any message boards that cover
    the same topics talked about in this article? I’d really like to be a part of online community where I can get feedback from other knowledgeable people that share the same interest. If you have any recommendations, please let me know. Appreciate it!

Replica a Unknown Cancelar la respuesta