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

miércoles, 18 de febrero de 2015

Claims Series 5: Autorizando!

Vamos a terminar esta serie dedicada a Claims autorizando, que es el objetivo final: Ver si un usuario puede o no realizar una operación en base al conjunto de claims que presenta.

Podemos utilizar atributos decorando métodos para indicar operaciones sobre recursos:

   1:  [ClaimsPrincipalPermission(SecurityAction.Demand, Operation="operacion", Resource="recurso")]

O bien podemos comprobar la seguridad programáticamente:

   1:   ClaimsPrincipalPermission.CheckAccess("operacion", "recurso");

Pero claro, ¿como realizamos la autorización?  Debemos codificar una clase que heredando de ClaimsAuthorizationManager sobrescriba el método CheckAccess:

   1:  public override bool CheckAccess(AuthorizationContext context)
   2:  {
   3:      return base.CheckAccess(context);
   4:  }

Aquí implementaremos la lógica de autorización en base a las claims presentadas por el usuario.

Vamos con un ejemplo

 
Supongamos que queremos autorizar dos operaciones, una se encarga de formatear mi laptop y otra mi desktop (vivo al limite!). Dichas operaciones quedarían así decoradas:

   1:  [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = "Format", Resource = "Desktop")]
   2:  protected static void Operation1 ()
   3:  {
   4:        Console.WriteLine("Operation1: Format Desktop");
   5:  }
   6:   
   7:  [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = "Format", Resource = "Laptop")]
   8:  protected static void Operation2()
   9:  {
  10:        Console.WriteLine("Operation2: Format Laptop");
  11:  }

Bien, para realizar la autorización vamos a basarnos en dos claims:

Claim
Valor
Significado
http://serginet.com/isUserUsingMyLaptop
true/false
Indica si el usuario puede formatear el laptop
http://serginet.com/isUserUsingMyDesktop
true/false
Indica si el usuario puede formatear el desktop


Tenemos, como he comentado antes, que crear una clase que herede de ClaimsAuthorizationManager  y sobrescriba el método CheckAccess:

   1:  public override bool CheckAccess(AuthorizationContext context)
   2:  {
   3:      string resource = context.Resource.First().Value;
   4:      string action = context.Action.First().Value;
   5:   
   6:      if (action == "Format" && resource == "Laptop")
   7:      {
   8:          bool isLaptopUser = context.Principal.HasClaim("http://serginet.com/isUserUsingMyLaptop", "true");
   9:          return isLaptopUser;
  10:      }
  11:      else if (action == "Format" && resource == "Desktop")
  12:      {
  13:          bool isDesktopUser = context.Principal.HasClaim("http://serginet.com/isUserUsingMyDesktop", "true");
  14:          return isDesktopUser;
  15:      } 
  16:      return false;
  17:  }

 
Como veis se comprueban los claims en base al par operación/recurso y se retorna un booleano indicando si la operación está permitida.
 
A continuación, y de modo similar a como vimos en la entrada anterior, debemos configurar nuestro manager de autorización en el archivo de configuración de la aplicación. Veréis que he configurado también un transformador, para que incluya las claims necesarias en el momento de la autenticación (si no lo hacemos el usuario nunca va a tener las claims necesarias):

   1:  <?xml version="1.0" encoding="utf-8" ?>
   2:  <configuration>
   3:    <configSections>
   4:      <section name="system.identityModel"
   5:          type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
   6:    </configSections>
   7:   
   8:    <system.identityModel>
   9:      <identityConfiguration>
  10:        <claimsAuthenticationManager type="Claims5_Autorizando.CustomClaimsTransformer,Claims5_Autorizando"/>
  11:        <claimsAuthorizationManager type="Claims5_Autorizando.CustomClaimsAuth,Claims5_Autorizando"/>
  12:      </identityConfiguration>
  13:    </system.identityModel>
  14:   
  15:    <startup>
  16:      <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
  17:    </startup>
  18:   
  19:  </configuration>

He utilizado el mismo transformador de la entrada anterior: Claims Series 4: Transformaciones
Se encarga de añadir una claim al usuario que ejecuta la aplicación siempre que dicho usuario sea un usuario de mi equipo (cambiadlo vosotros o no os funcionará). Es decir, en mi caso añade la siguiente claim: "http://serginet.com/isUserUsingMyDesktop" con el valor true
 
Una vez tenemos todo podemos probar a llamar a las operaciones. La primera funciona correctamente ya que el usuario tiene el claim necesario,. Sin embargo al ejecutar la segunda obtenemos una excepción:

   1:  class Program
   2:  {
   3:      static void Main(string[] args)
   4:      {
   5:   
   6:          SetCurrentPrincipal();
   7:   
   8:          Operation1();
   9:          Operation2();
  10:      }
  11:   
  12:      [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = "Format", Resource = "Desktop")]
  13:      protected static void Operation1 ()
  14:      {
  15:          Console.WriteLine("Operation1: Format Desktop");
  16:      }
  17:   
  18:      [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = "Format", Resource = "Laptop")]
  19:      protected static void Operation2()
  20:      {
  21:          Console.WriteLine("Operation2: Format Laptop");
  22:      }
  23:   
  24:      private static void SetCurrentPrincipal()
  25:      {
  26:          WindowsPrincipal incomingPrincipal = new WindowsPrincipal(WindowsIdentity.GetCurrent());
  27:          Thread.CurrentPrincipal = FederatedAuthentication.FederationConfiguration.IdentityConfiguration
  28:              .ClaimsAuthenticationManager.Authenticate("none", incomingPrincipal);
  29:      }
  30:  }
 
 
 
Con esto finalizamos la serie dedicada a claims. No es más que una introducción... habrá que profundizar!

Como siempre os dejo el código

Posts publicados en Claims Series:

martes, 17 de febrero de 2015

Claims Series 4: Transformaciones

Después de bastante tiempo continuo con esta serie dedicada a la seguridad basada en claims.

Autenticación y Tokens


Una de los principales "mantras" que debemos asumir es que las aplicaciones no deben preocuparse de como los claims han sido generados, por ejemplo que tipo de autenticación se ha utilizado para determinar que el username es "Tancredo" (existe un claim que dice eso).

Tenemos en .NET 4.5 un mecanismo basado en plugins que nos aísla de cada tipo de autenticación (de su implementación concreta) y que se encarga de generar los claims necesarios. Los mecanismo de autenticación soportados son varios: Autenticación tipo Forms, Windows integrada, SAML, etc... 

Estos mecanismo emiten tokens de seguridad en diferentes formatos, de los cuales podemos extraer los claims utilizando un objeto de tipo SecurityTokenHandler, cuyas funciones son:

  • Leer tokens
  • Escribir tokens
  • Validar tokens
  • Determinar el tipo de un token
Para ello en el framework disponemos de clases especializadas para cada tipo de credenciales:

  • KerberosSecurityTokenHandler
  • RsaSecurityTokenHandler
  • SamlSecurityTokenHandler
  • UserNameSecurityTokenHandler
  • Otras
 
El pipeline utilizado es el siguiente:
 
  1. La aplicación recibe un token en un formato dado (XML, binario, etc...)
  2. El token es deserializado (Método ReadToken)
  3. El token es validado (Método ValidateToken). El resultado de una validación afirmativa es la generación de un ClaimsPrincipal
  4. El objeto ClaimsPrincipal  es transformado de acuerdo a las necesidades (opcional)
La autenticación es algo que se escapa del ámbito de lo que estamos tratando, así que vamos a centrarnos en el punto 4. Una vez que el mecanismo de autenticación ha generado un ClaimsPrincipal, puede que no lleve la información que espero.  ¿Cómo lo transformo según necesite?


Transformando Claims


Para transformar, lo que debemos hacer es generar una nueva clase, que heredando de ClaimsAuthenticationManager sobrescriba el método Authenticate.

   1:      public class CustomClaimsTransformer : ClaimsAuthenticationManager
   2:      {
   3:          public override ClaimsPrincipal Authenticate(string resourceName, ClaimsPrincipal incomingPrincipal)
   4:          {
   5:              throw new NotImplementedException();
   6:          }
   7:      }


Antes de empezar hay que añadir las referencias pertinentes:
 
 
 
Pues bien, vamos a mi implementación de ejemplo. Lo que voy a hacer es analizar el username que recibo para ver si es un usuario local a un equipo concreto que tengo en casa (mi desktop). Si es así emitiré un claim indicándolo.
Asumimos que este claim será mas tarde utilizado por la aplicación (¿no es muy buen ejemplo verdad?)

   1:   public override ClaimsPrincipal Authenticate(string resourceName, ClaimsPrincipal incomingPrincipal)
   2:          {
   3:              string nameClaimValue = incomingPrincipal.Identity.Name;
   4:              List<Claim> claimsCollection = new List<Claim>();
   5:              claimsCollection.Add(new Claim(ClaimTypes.Name, nameClaimValue));
   6:                          
   7:              if(nameClaimValue.IndexOf("RAISTLIN-DT") >=0)
   8:              {
   9:                  //We add claim indicating user is working on my desktop
  10:                  claimsCollection.Add(new Claim("http://serginet.com/isUserUsingMyDesktop", "true"));
  11:   
  12:              }
  13:              else
  14:              {
  15:                  //We add claim indicating user is not working on my desktop
  16:                  claimsCollection.Add(new Claim("http://serginet.com/isUserUsingMyDesktop", "true"));
  17:              }
  18:   
  19:              return new ClaimsPrincipal(new ClaimsIdentity(claimsCollection, "MyCustomAuth"));
  20:          }       

El siguiente paso es configurar la aplicación para que se utilice el nuevo manager:

   1:  <?xml version="1.0" encoding="utf-8" ?>
   2:  <configuration>
   3:    <configSections>
   4:      <section name="system.identityModel" 
   5:          type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
   6:    </configSections>
   7:     
   8:  <system.identityModel>
   9:    <identityConfiguration>
  10:      <claimsAuthenticationManager type="Claims4_Transformaciones.CustomClaimsTransformer,Claims4_Transformaciones"/>
  11:    </identityConfiguration>
  12:  </system.identityModel>
  13:    
  14:   <startup> 
  15:          <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
  16:      </startup>
  17:   
  18:  </configuration>

Y para acabar, podemos hacer uso del nuevo manager:

   1:   class Program
   2:      {
   3:          static void Main(string[] args)
   4:          {
   5:              SetCurrentPrincipal();
   6:          }
   7:   
   8:          private static void SetCurrentPrincipal()
   9:          {
  10:              WindowsPrincipal incomingPrincipal = new WindowsPrincipal(WindowsIdentity.GetCurrent());
  11:              Thread.CurrentPrincipal = FederatedAuthentication.FederationConfiguration.IdentityConfiguration
  12:                  .ClaimsAuthenticationManager.Authenticate("none", incomingPrincipal);
  13:          }
  14:   
  15:      }

Si depuramos veremos que obtenemos un ClaimsPrincipal que contiene los 2 claims que hemos emitido:
 
 
 
Espero que os haya gustado!  En el próximo post "cerraremos el circulo" para explicar como permitir que se ejecuten acciones o no en una aplicación (autorización) en base a los claims del usuario.
 
Como siempre os dejo el código

Posts publicados en Claims Series:

domingo, 20 de octubre de 2013

Claims Series 3: Herencia y compatibilidad

Continuamos el estudio de todo lo relacionado a las claims respondiendo una pregunta clave: Si en .NET 4.5 todo el modelo de seguridad se ha movido hacia un esquema basado en claims, ¿Qué pasa con mis viejas aplicaciones que hacían uso de Identity y Principal?

Para responder una imagen vale más que mil palabras. Así está la jerarquía de clases del Framework relativa a identidades (como veis he añadido una clase de mi cosecha, hablamos de ella luego):




¿Qué quiere decir todo esto? Pues que efectivamente todo son claims, como veis, un GenericIdentity de los de antes ahora a pasado a heredar de ClaimsIdentity, mientras que en versiones previas del framework, esta clase implementaba directamente IIdentity.

Si instanciamos una identidad genérica, por debajo estamos instanciando una identidad basada en claims. ¿Cuál será la salida del siguiente código?
   1:  static void Main(string[] args)
   2:  {
   3:     var oldIdentity = new GenericIdentity("Sergio");
   4:   
   5:     foreach(var cl in oldIdentity.Claims)
   6:     {
   7:         Console.WriteLine("Tipo:{0} Valor:{1}", cl.Type, cl.Value);
   8:     }
   9:  }

Aquí la teneis, la identidad almacena un único claim, cuyo tipo es http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name



Parece claro que la compatibilidad hacia atrás está garantizada, ya que este esquema que os acabo de presentar se sigue también para los objetos tipo principal.

 

Generando nuestra propias identidades


En la entrada anterior (Claims Series 2: Un poco de código por favor) veíamos como podíamos crear identidades generando una colección de claims. Es sencillo, pero desde luego no es muy elegante:

   1:  var cl1=new Claim("http://serginet.com/categoria","senior");
   2:  var cl2=new Claim("http://serginet.com/edad","36");
   3:  var cl3=new Claim("http://serginet.com/ciudad","Arroyomolinos");
   4:   
   5:  var identidad = new ClaimsIdentity(new List<Claim>() { cl1, cl2, cl3 });

Vemos que en el Framework han usado herencia... ¿por que no usarla nosotros también?
Creemos nuestra propia identidad, heredando de ClaimsIdentity y aprovechemos a exponer propiedades para las claims almacenadas.

   1:  public class SerginetIdentity: ClaimsIdentity
   2:  {
   3:      public const string CTE_CategoriaProfesional = "http://serginet.com/categoriaProfesional";
   4:      public const string CTE_Edad = "http://serginet.com/edad";
   5:      public const string CTE_AccesoDocumentosSecretos = "http://serginet.com/accesoDocumentosSecretos";
   6:   
   7:      public SerginetIdentity(string categoriaProfesional, int edad, bool accesoDocumentosSecretos)
   8:      {
   9:          var cl1=new Claim(CTE_CategoriaProfesional,categoriaProfesional);
  10:          var cl2=new Claim(CTE_Edad,edad.ToString());
  11:          var cl3=new Claim(CTE_AccesoDocumentosSecretos,accesoDocumentosSecretos.ToString());
  12:   
  13:   
  14:          this.AddClaims(new List<Claim>() { cl1, cl2, cl3 });
  15:      }
  16:   
  17:      public string CategoriaProfesional
  18:      {
  19:          get
  20:          {
  21:              return this.FindFirst(CTE_CategoriaProfesional).Value;
  22:          }
  23:      }
  24:   
  25:      public int Edad
  26:      {
  27:          get
  28:          {
  29:              return int.Parse(this.FindFirst(CTE_Edad).Value);
  30:          }
  31:      }
  32:          
  33:      public bool AccesoDocumentosSecretos
  34:      {
  35:          get
  36:          {
  37:              return Boolean.Parse(this.FindFirst(CTE_AccesoDocumentosSecretos).Value);
  38:          }
  39:      }
  40:   
  41:  }

Como veis es sencillo, fácil de usar desde fuera, y será la base de la próxima entrada: Transformando claims.

Como siempre os dejo el código

Posts publicados en Claims Series:


viernes, 18 de octubre de 2013

Claims Series 2: Un poco de código por favor

Continuando la serie de post sobre claims... vamos al grano! Empecemos con algo de código:

Creando claims


¿Cómo creamos claims? Muy sencillo, como veíamos en el primer post sólo necesitamos un tipo y un valor

var primerClaim = new Claim("Nombre", "Tobías");

¿No parece muy elegante trabajar con cadenas "hardcodeadas"? Pues disponemos de enumerados que nos echarán una mano con tipos de claim prefedinidos:

var segundoClaim = new Claim(ClaimTypes.Country, "Antigua y Barbuda")

Si echamos un ojo a los valores disponibles, vemos que hay bastantes, y que todos están determinados con namespaces completos (buena práctica para evitar colisiones).



En cualquier caso, si lo predefinido no basta, definiremos nuestros propios tipos, siempre utilizando namespaces (y constantes o enumerados por favor):

var tercerClaim = new Claim("http://serginet.com/AficionPrincipal", "Tecnología");

Lo normal es que un usuario disponga de más de un claim, así que lo habitual será crear colecciones de claims:

IList<Claim> coleccionClaims = new List<Claim>
{
primerClaim, segundoClaim, tercerClaim
};

Identidades y principals


Una vez que disponemos de una colecciónd de claims, podemos crear una identidad:


var identidad = new ClaimsIdentity(coleccionClaims);

Y comprobar si dicha entidad está o no autenticada:

Console.WriteLine("Identidad autenticada:" + identidad.IsAuthenticated);

Si lo ejecutas verás que no... para hacerlo, tan solo tenemos que crear la identidad estableciendo un tipo de autenticación:

var identidadAutenticada = new ClaimsIdentity(coleccionClaims,"Autenticacion basica serginet.com");

Y finalmente con una identidad podemos crear un principal (el contexto de seguridad bajo el que se ejecuta nuestra código) y asignarlo a nuestro thread de ejecución::

var principal = new ClaimsPrincipal(identidadAutenticada);
Thread.CurrentPrincipal = principal;

Como veis todo parece bastante sencillo... continuaremos en la siguiente entrega haciendo uso de todos estos conceptos para lo que realmente importa: Hacer uso en nuestra aplicación de los claims para tareas de autenticación y autorización.

Aquí os dejo el código

Post publicados en Claims Series:



jueves, 17 de octubre de 2013

Claims Series 1: Una introducción a claims para profanos, como yo :-)

Desde hace varios años venimos escuchando que la "nueva tendencia" para seguridad (autenticación y autorización) está basada en claims.

¿Qué son los claims (o demandas /reclamaciones)?
¿Qué relación tienen con otros conceptos como SAML, proveedores de tokens, federación, etc...?

A todo esto voy a tratar de contestar en varios post (espero no se demoren demasiado).
Empecemos por el principio:

Como se ha manejado la seguridad tradicionalmente (en entornos .NET)


Desde los inicios del Framework, hemos venido trabajado con 2 conceptos: Identidad y Principal

La identidad, representada con clases que implementan IIdentity, nos dice quien: El usuario es Efraín
El principal, representado con clases que implementan IPrincipal, nos dice quien y que puede hacer: El usuario es Efraín y es un vendedor.

Todo lo que puede hacer un usuario se ha encapsulado en diversos roles: Vendedor es un rol, administrador otro... y en el código, se ha preguntado por los roles para permitir a un usuario realizar una acción o no.

Pues bien, parece que esto ha funcionado razonablemente bien durante años, pero hoy en día, la granularidad de permisos que requieren las aplicaciones modernas ha hecho que el trabajo con roles se haya vuelto muy tedioso:

  • Aparecen multitud de roles a mantener
  • En nuestras aplicaciones aparecen eternos if/then/else del siguiente tipo:

   1:  static void Main(string[] args)
   2:  {
   3:        IPrincipal currentPrincipal = Thread.CurrentPrincipal;
   4:   
   5:        if (currentPrincipal.IsInRole("ADM"))
   6:        {
   7:           //Habilitar todos los botones
   8:        }
   9:        else if (currentPrincipal.IsInRole("Ventas"))
  10:        {
  11:           //Habilitar los botones rfelativos a ventas
  12:        }
  13:        else if (currentPrincipal.IsInRole("opoeraciones"))
  14:        {
  15:           //Lo que sea
  16:        }
  17:  }

  • Las gran cantidad de roles nos lleva a crear un rol de administrador... y lo que es peor, a veces no es suficiente y creamos al superadministrador.

Deshaciendo el entuerto: claims al rescate


Parece lógico huir de los roles...  vamos a expresar la información acerca de un usuario (quien es y qué puede hacer) en base a sencillas afirmaciones:
  • Evaristo es un administrador.
  • Evaristo vive en Villarriba.
  • Liborio puede ver las estadísticas de venta.
  • Tobías también, pero solo las del último año.
Como veis, estas afirmaciones expresan mucho más que simplemente poner a un usuario en un rol... ¡parece que así salimos ganando!

Por otro lado, queda algo con contestar: ¿Quién emite estas afirmaciones? Normalmente un tercero, en el que se confía, y que llamaremos issuer. Ejemplo: El active directory de mi compañía dice que Pedro es un vendedor.

Ya tenemos los 3 elementos que necesitamos para definir un claim:
  • Tipo (type)
  • Valor (value)
  • Emisor (issuer)
Algunos ejemplos:

Type Value Issuer
Nombre Tobias serginet.com
email Tobias@serginet.com serginet.com
Visibilidad de ventas Total serginet.com

Además de granularidad ganamos en compatibilidad: diferentes sistemas pueden compartir claims. No parece muy "inmediato" enviar  una identidad Windows desde una aplicación .NET a otra SAP... sin embargo si pensamos en claims, todo se nos antoja más sencillo



Espero haber aclarado el concepto... continuaremos por lo que nos gusta: algo de código, pero en la siguiente entrega.