Mostrando entradas con la etiqueta articulos. Mostrar todas las entradas
Mostrando entradas con la etiqueta articulos. Mostrar todas las entradas
Puppet Types y Providers
Todos los que utilizamos puppet en algún momento nos encontraremos con la necesidad de crear un módulo que no se puede implementar sólo utilizando los recursos existentes. Es en ese momento donde surge la necesidad de crear nuestro propio tipo.

Puppet provee la posibilidad de crear nuestros propios tipos, desarrollados en ruby. Contar con la posibilidad de programar expande muchísimo nuestro horizonte de cosas que se pueden hacer con puppet.

Desde mi punto de vista, ruby es horrible para programar, pero es la única opción que existe actualmente en puppet, así que no nos quedará otra que aprender aunque sea las bases de este lenguaje.

Puppet separa la definición de un recurso en dos partes: types y providers
  • El archivo type define todas las propiedades y parámetros que se pueden usar en nuestro recurso. Es decir, define la estructura del recurso y como interactuaremos desde un manifest con él. 
  • Por otra parte, el provider se encarga de implementar el type que definimos.

Algo interesante es que podemos tener múltiples providers para el mismo type. El ejemplo más claro de esto es la implementación del type package. Existen varios providers para este tipo: apt, yum, pacman, etc. La forma en que interactuamos con el type es independiente de cómo luego se implemente.

Como todo en puppet, es importante la estructura y los nombres de nuestros directorios y archivos. Para definir nuestro type y provider, hay que seguir el siguiente árbol:
module
  |-- metadata.json
  |-- README.md
  |-- manifests
  |-- lib
       |-- puppet
             |-- provider
             |     |-- configjs
             |           |-- configjs.rb
             |-- type
                   |-- configjs.rb


La idea es que la explicación sea bastante dinámica, así que plantearé un ejemplo que seguiremos a lo largo del artículo.


Crear nuestro type

Como mencioné, los types deben ubicarse en el path <module name>/lib/puppet/type/<nombre del tipo>.rb para satisfacer la nomenclatura de puppet.

Por ejemplo, voy a definir un tipo para administrar configuraciones en formato JSON. Llamaré al tipo configjs:
  #configjs/lib/puppet/type/configjs.rb
  Puppet::Type.newtype(:configjs) do
    @doc = "Manage json configs"

    ensurable

    newparam(:path, :namevar => true) do
      desc "Path to the JSON File"

      newvalues(/\A\/\w+/)
    end

    newparam(:showdiff) do
      desc "Show a diff when changing values"

      defaultto :true
      newvalues(:true, :false)
    end

    newproperty(:config) do
      desc "The configuration to set"

      isrequired

      def insync?(is)
        is.sort == should.sort
      end
    end
  end
  
La forma de invocar un tipo es la misma que cuando lo definimos con define en un .pp. Este tipo se utilizaría de la siguiente manera en un manifest:
  $myconfig = {
    'name' => 'itfreek',
    'url' => 'http://itfreekzone.blogspot.com'
  }
  configjs {'/etc/myconfig.js':
    config => $myconfig
  }
  
Veamos un poco qué significa cada parte del tipo configjs.


Propiedades y parámetros

Para empezar declaramos un parámetro (path) y una propiedad (config). Si bien lucen similares, la diferencia entre ambos es muy importante:
  • El parámetro se utitliza para designar un valor desde puppet. En el ejemplo, path define la ubicación del archivo en el servidor. 
  • Una propiedad es algo que se puede inspeccionar y cambiar. La misma cuenta con dos valores. Por un lado, tenemos el valor que se indica desde puppet, denominado "should", y por otro el valor que tiene actualmente en el sistema, denominado "is". En el ejemplo, una config en el sistema será el JSON contenido en el file, que pudo haber sido modificado manualmente (esto es el "is"). Si puppet setea un JSON, este es el valor que esa propiedad debe tener, y por eso se denomina "should". La diferencia entre ambos valores es lo que puppet muestra al momento de ejecutar una corrida.


Namevar

Otro punto destacable es el uso de ":namevar => true" en el parámetro path. Como saben puppet usa el concepto namevar para distinguir un recurso en el sistema, y es el valor que asignamos al crear el recurso. En nuestro caso, el path es el nombre que nos permite distinguir un file de otro, lo mismo que sucede con el recurso file. En dicho ejemplo, el valor que tendrá el path es /etc/myconfig.js y será el nombre de nuestro recurso.


Otras opciones

Finalmente también encontramos "isrequired" y "defaultsto". Con el primero indicamos que la propiedad es requerida, es decir, si no se setea puppet dará error. El segundo indica cuál es el valor default que tomará el parámetro si no se especifica ninguno.


Validación y munging

Veamos el uso de newvalues. Con este método podemos restringir qué valores son aceptables para el parámetro. Podríamos incluir una lista de valores (newvalues(:true, :false)), o, como hice yo, una expresión regular a matchear. Más adelante veremos cómo invocar funciones a partir de valores con ensurable. Si el valor otorgado no cumple con esta restricción, puppet dará error.

Las otras opciones que tenemos son validar el input (validate) y transformar (o sanitizar) el valor (munge).

Por ejemplo, si queremos validar que el path sea un path absoluto, podemos agregar lo siguiente:
  newparam(:path, :namevar => true) do
    ...
    validate do |value|
      unless Puppet::Util.absolute_path? value
        raise ArgumentError, "archive path must be absolute: #{value}"
      end
    end
  end
  
Por otra parte, si quisieramos setear los permisos del file, podríamos agregar un parámetro mode. Mode debería ser un string en la definición del recurso, pero nosotros necesitamos que sea un integer en notación octal para poder setearlo en el filesystem (ej: 0644). Podríamos hacer esto con munge:
  newparam(:mode) do
    munge do |value|
      value.to_i(8)
    end
  end
  


Providers

Ahora que contamos con la definición del tipo, pasemos al provider. El mismo debe ubicarse en /lib/puppet/provider//.rb. Un tipo puede tener varios providers (como package, o vcsrepo).

En nuestro ejemplo, el provider se llamará igual al tipo (aunque podría llamarse diferente) y lo definimos así:

  #configjs/lib/puppet/provider/configjs/configjs.rb
  Puppet::Type.type(:configjs).provide(:configjs) do
    def config
      if File.exists?(resource[:path])
        content = File.read(resource[:path])
        PSON.load(content)
      else
        {}
      end
    end

    def config=(value)
      create
    end

    def exists?
      File.exists?(resource[:path])
    end

    def create
      config_file = File.new(resource[:path], 'w+')
      config_file.write(JSON.pretty_generate(resource[:config]))
      config_file.close
    end

    def destroy
      FileUtils.rm(resource[:path])
    end
  end
  
Acá aparece el hash 'resource'. Este hash contiene los valores para cada uno de los parámetros y las propiedades que se setearon. Dentro del type también se puede acceder a este hash, pero como self (ejemplo: self[:path]).


Definiendo la funcionalidad

Ensurable

Si prestaron atención, el tipo configjs invoca el método ensurable. Un recurso se considera 'ensurable' cuando su presencia se puede verificar. Esto es, si no existe y debería existir (ensure => present), hay que crearlo, mientras que si existe y no debería existir (ensure => absent), eliminarlo. Al realizar esta invocación, deberemos definir tres funciones:
  •  exists? debe retornar true si nuestro recurso existe en el sistema y falso sino.
  • create: crear el recurso si no existe y debería existir.
  • destroy: destruir el recurso si existe y no debería.

En el ejemplo anterior, exists? nos dice si el file existe o no. Si no existe, creamos el archivo con el contenido indicado en la propiedad config. Si queremos destruirlo, simplemente hacemos un rm del file.

Es posible redefinir los métodos invocados al utilizar "ensure => present/absent", e incluso agregar más valores. La forma de hacerlo es la siguiente:
  #configjs/lib/puppet/type/configjs.rb
  Puppet::Type.newtype(:configjs) do
    ...

    ensurable do
      newvalue(:present) do
        provider.generate
      end

      newvalue(:absent) do
        provider.remove
      end

      newvalue(:empty) do
        provider.empty
      end
    end
  end
  

Esto es, indicamos que si ensure es present invoque la función generate en lugar de create, mientras que si es absent invoque remove en lugar de destroy. Además, agregamos el valor emtpy que nos permite invocar la función emtpy en el provider.

Como habrán notado, es posible invocar funciones del provider desde la definición del type.


Refresheable

Ahora, qué pasa si realizamos un cambio en el file desde fuera del resource, como por ejemplo, un exec en un manifest? La simple lógica anterior nos indica que si el file existe en el filesystem, entonces no hay que realizar ninguna acción, no verifica si el contenido del file es el correcto.

Este comportamiento lo cambiaremos con los setters y getters (explicado a continuación), pero hay otra forma si no queremos utilizar propiedades, podemos hacerlo indicando que el recurso es refresheable.

Que nuestro tipo sea refresheable significa que se ejecutará algo con un cambio en la propiedad, o si tenemos una relación notify/subscribe. En el ejemplo podríamos realizar estos cambios:
  #configjs/lib/puppet/type/configjs.rb
  Puppet::Type.newtype(:configjs, :self_refresh => true)) do
    ...
    ...
    def refresh
      provider.create
    end
  end
  
Lo que hicimos es, primero indicar que nuestro tipo es refresheable con ":self_refresh => true". Luego definimos la función refresh, donde indicamos qué se debe ejecutar en un refresh. En nuestro caso lo que hacemos es invocar el método create del provider.

Con esta nueva funcionalidad, además de crearse el file si no existe en el filesystem, también se pisará el contenido si por alguna razón cambia una propiedad o algún otro recurso le notifica que debe hacer un refresh (notify).


Setters y Getters

Siempre que definamos una propiedad, puppet utilizará distintas funciones para determinar si el valor de la propiedad difiere de la que está actualmente en el host. Si este es el caso, seteará el valor que corresponda. Esto es, /etc/myconfig.js tiene el contenido que se indicó por puppet?

Para saber cuál es el valor actual de la propiedad y setear el que corresponde, puppet invoca métodos cuyos nombres son igual a la propiedad. Estos métodos se definen en el provider.

Para el ejemplo anterior, la propiedad se llama config, entonces las funciones get y set se definen de la siguiente manera:
  #Getter: decime qué valor tiene la config ahora en el server
  def config
  end

  #Setter: setea este value como contenido de la config
  def config=(value)
  end
  
Puppet determina si el valor actual es el que debería tener invocando el método "insync?", es decir, esta propiedad está sincronizada? Por defecto esta función realiza una comparación entre el valor actual y el que debería mediante el operador "==". Para comparar strgins va genial, pero si, por ejemplo, estamos comparando hashes, la comparación podría no resultar como esperamos. Para estos casos, podemos redefinir el método insync? en la definición de la propiedad (en el type).

Siguiendo nuestro ejemplo, vemos esta definición:
  #configjs/lib/puppet/type/configjs.rb
  Puppet::Type.newtype(:configjs) do
    ...
    newproperty(:config) do
      ...

      def insync?
        is.sort == should.sort
      end
    end
    ...
  end
  


Prefetch y flush

En ciertos casos no queremos definir setters y getters para todas las propiedades de nuestro tipo. Si estamos en esa situación, nuestro approach debería ser utilizar un esquema prefetch/flush.

Un provider prefetch/flush implementa sólo dos métodos:
  • prefetch: dada una lista de recursos, retornará y seteará las instancias con los valores obtenidos. 
  • flush: se invoca luego de que todos los valores se setearon.

Dado que puppet intentará invocar los setter/getters para cada una de las propiedades, podemos evitar la definición de todos estos métodos invocando mkresource_method, que define setter/getters default para todas las propiedades.

Luego definimos el método prefetch para realizar todas las tareas que haríamos con los getters, y flush para todos los setters.

No utilicé este esquema aún, pero el formato básico es:
  #configjs/lib/puppet/provider/configjs/configjs.rb
  Puppet::Type.type(:configjs).provide(:configjs) do
    mkresource_method

    def prefetch
      ...
    end

    def flush
      ...
    end
  end
  


Initialize

Otra opción útil para la definición de types y providers es el uso del método initialize. Este método se ejecuta cuando ruby crea un objeto, por lo que podremos realizar algunas inicializaciones ahí si lo necesitamos.


Ejecutar Comandos

En el ejemplo sólo utilizamos files y no necesitamos ejecutar ningún comando. Pero qué pasa si queremos tener un recurso que utilice comandos? Tomando nuestro ejemplo de referencia, tal vez utilicemos el config json generado como input de un comando.

Sólo para ejemplificar el uso de comandos, redefiniré el método create del provider agregando la creación de un container lxd a partir del json. No probé este comando, por lo que puede tener algún error, pero sirve para ejemplificar.
  #configjs/lib/puppet/provider/configjs/configjs.rb
  Puppet::Type.type(:configjs).provide(:configjs) do
    commands :curl => 'curl'

    def create
      config_file = File.new(resource[:path], 'w+')
      config_file.write(JSON.pretty_generate(resource[:config]))
      config_file.close

      curl("-k", "-L", "--cert", ""~/.config/lxc/client.crt", "--key", ~/.config/lxc/client.key", "-H", "Content-Type: application/json", "-X", "POST", "-d", "@#{resource[:path]}", "https://127.0.0.1:8443/1.0/containers)
    end

    ...
  end
  


Cambiar el output

Cuando puppet cambia un recurso, se registra un evento. Vemos la notificación de que algo cambió de la siguiente forma:
Notice: /Stage[main]/<resource>/Config_js[/etc/myconfig.js]/Configjs[/etc/myconfig.js]/config: config changed <IS VALUE> to <SHOULD VALUE>

La forma en que puppet crea el mensaje es convirtiendo el valor actual y el que debería tener en strings (usando el método to_s de ruby). Dependiendo el valor de la propiedad, la conversión a string puede no ser la deseada y el cambio no resulta claro.

Por suerte podemos customizar tanto la forma en que dichos valores se convierten a string, como el mensaje de lo que va a cambiar. Para ello debemos definir los métodos is_to_s, should_to_s y change_to_s dentro de la propiedad o parámetro:
  • is_to_s: se ejecuta para convertir a string el valor que actualmente tiene el server. 
  • should_to_s: debe convertir el valor a aplicar a string. 
  • change_to_s: se invoca para mostrar lo que va a cambiar. Si no redefinimos esta función puppet mostrará el valor actual devuelto por is_to_s y el aplicable retornado por should_to_s.
En general nos alcanzará con redefinir is_to_s y should_to_s, o sólamente change_to_s, es muy raro que utilicemos las tres, ya que si definimos change_to_s no tiene mucho sentido definir también las otras dos.

Siguiendo el ejemplo anterior, la propiedad config es un hash. La función to_s no nos retorna un string muy lindo de los hashes, así que en su lugar, podríamos usar inspect en is_to_s y should_to_s:

  #configjs/lib/puppet/type/configjs.rb
  Puppet::Type.newtype(:configjs) do
    ...

    newproperty(:config) do
      ...

      def is_to_s(value)
        value.inspect
      end

      def should_to_s(value)
        value.inspect
      end
    end
  end
  
En lugar de lo anterior, podríamos simplemente redefinir change_to_s:
  #configjs/lib/puppet/type/configjs.rb
  Puppet::Type.newtype(:configjs) do
    ...

    newproperty(:config) do
      ...

      def change_to_s(is, should)
        "old value #{(is).inspect},
        new #{(should).inspect}"
    end
  end
  

Update 1: descubrí que cuando ejecutamos puppet con --noop, no se ejecuta change_to_s, sino una función noop que no podemos redefinir y tiene un output predefinido de "current value %s, should be %s (noop), param.is_to_s(current_value), param.should_to_s(param.should)". Ver reporte.

Como no podemos cambiar esta funcionalidad, encontré un workaround en el tipo file. Lo que hace file es imprimir las diferencias entre los files en la función issync? y hacer que is_to_s y should_to_s devuelvan checksums en lugar de printear ambos files.

La forma de implementar esto sería:
  Puppet::Type.newtype(:configjs) do
    ...
    def is_to_s(is)
      '{md5}' + Digest::MD5.hexdigest(is.to_s)
    end

    def should_to_s(should)
      '{md5}' + Digest::MD5.hexdigest(should.to_s)
    end

    def insync?(is)
      return @is_insync unless @is_insync.nil?

      @is_insync = super(is)
      if ! @is_insync && Puppet[:show_diff]
        send @resource[:loglevel], "old value #{(is).inspect},
        new #{(should).inspect}"
      end
      @is_insync
    end
  end

Con esto logramos hacer nuestro propio print de los cambios tanto con --noop como si se van a aplicar de verdad.

Tuve que reescribir las funciones is_to_s y should_to_s para que no retornen el contenido, sino un md5. Ese cambio es necesario porque el método noop de puppet llama estas funciones siempre. Si no modifico su output, obtendremos un print de los cambios dos veces.

También tuve que agregar una variable a la clase para detectar si ya ejecuté insync anteriormente. La clase property llama el método insync? dos veces, así que si no determinamos que ya se ejecutó terminaremos imprimiendo el mismo diff dos veces.


Update 2: Utilizar gems externos en nuestro provider

Algo interesante que me surgió durante la creación de un módulo fue la necesidad de utilizar un gem externo. En mi caso, el gem era inifile.

Ej:
  Puppet::Type.type(:mytype).provide(:mytype) do
    ...
    require 'inifile'
    ini_file = IniFile.load('/etc/config.ini')
    ...
  end
Como muchas cosas en puppet, un poco de magia negra es necesaria, dado que puppet no provee una forma de definir dependencias de librerías ruby en la definición del módulo, así que debemos utilizar un workaround.

Luego de leer en comentarios en reportes de bugs y foros, llegué a esta solución.
Básicamente, lo que podemos hacer es:
  • Instalar la dependencia gem con puppet.
  • Hacer que nuestro recurso dependa de la instalación de este paquete.
  • Agregar una feature y un confine para que el provider no se ejecute si la librería no está instalada.

Vamos por partes, primero instalemos el gem necesario:
  package {'inifile':
    ensure   => 'installed',
    provider => 'puppet_gem',
  }

Agregamos la dependencia a la definición de nuestro tipo:
  mytype { 'nombre':
    ...
    require => Package['inifile'],
  }

Ahora, creemos una feature (lib/puppet/feature/inifilelib.rb) con el siguiente contenido:
  Puppet.features.add(:inifilelib, :libs => ["inifile"])

Por último agreguemos el confine en nuestro provider: 
  require 'inifile' if Puppet.features.inifilelib?
  Puppet::Type.type(:mytype).provide(:mytype) do
    confine :feature => :inifilelib
    ...
    require 'inifile'
    ini_file = IniFile.load('/etc/config.ini')
    ...
  end

Esto hará que el provider no se evalúe hasta que el gem inifile no esté instalado. Pueden encontrar una explicación un poco más detallada en el link.


Final, pero hay más para aprender

Si bien este artículo debería cubrir un buen porcentaje de lo que podemos necesitar al crear un type, surgiran cosas que no están acá. Una de ellas es la herencia de providers para crear nuevos providers, el uso de autorequire, features, etc. Recomiendo dar un vistaso a las referencias para más info.

Me decidí a escribir sobre types porque no encontré información muy buena. Si bien hay artículos interesantes que te introducen a la creación de tipos, a medida que avanzas y necesitas entender cómo funcionan ciertas cosas me costó bastante. La info está muy desparramada y se asumen muchas cosas que cuesta entender de dónde salen. Reconozco que no leí completamente el libro Puppet Types and Providers by Nan Liu, Dan Bode que supuestamente es la referencia más completa al respecto.


Referencias

Puppet Types and Providers

Puppet Custom Types 

Puppet Provider Development

Puppet Complete Resource Example

Fun With Puppet Providers - Part 1 of Whatever

Who Abstracted My Ruby?

Seriously, What Is This Provider Doing?

Puppet Extension Points - Part 2

Chapter 4. Advanced Types and Providers

Writing Custom Puppet Types and Providers to Manage Web-Based Applications (slides)
ejabberd LDAP, certificados y clustering
Después de pelear durante un par de días con ejabberd, me pareció interesante compartir la experiencia ganada en el proceso, ya que no todo es tan directo como parece. La documentación oficial está buena, pero la encontré un poco escueta, por lo que si no usas una configuración similar a la de los ejemplos, no sabes bien qué poner en cada parámetro.
Ejabberd está escrito en lenguaje Erlang, y utiliza el formato de este lenguaje para su archivo de configuración. Si bien no es complicado, no es a lo que uno está acostumbrado.

Comenzaré con lo básico de todos los tutoriales, pero con la idea de que el servidor autenticará con LDAP en lugar de la autenticación interna. Luego pasaré a los topics más interesantes como autopopular rosters con grupos de usuarios LDAP y armar un servicio de alta disponibilidad con dos servidores ejabberd.


Instalación

En debian, Ubuntu y supongo que otros derivados también, ejabberd se encuentra en los repositorios oficiales, por lo que instalarlo es tan fácil como ejecutar lo siguiente:
  # apt-get install ejabberd


Configuración básica

Toda la configuración se realiza desde el archivo /etc/ejabberd/ejabberd.cfg. De base, tendremos que editar lo siguiente:
  {hosts, ["dvpem.org"]}.
  {acl, admin, {user, "vektor", "dvpem.org"}}.
donde:
  • hosts especifica los dominios que ejabberd manejará.
  • acl Indica cuál es el usuario admin. Si utilizan LDAP (ver a continuación) este usuario debe ser uno que exista en el servidor de LDAP.
Como ven, muy poco es necesario para tener ejabberd funcionando. Si no utiliza LDAP deberán cambiar el nombre de usuario por uno local, y luego agregarlo con el comando ejabberctl. Por ejemplo:
  # ejabberdctl register vektor dvpem.org superPASS
Es posible acceder a una interfaz web de administración apuntando a la siguiente URL:
http://<host-o-IP>:5280/admin

Habilitar LDAP

Para habilitar autenticación por LDAP veamos un ejemplo de configuración y qué significa cada valor:
%%{auth_method, internal}.
{auth_method, ldap}.
{ldap_servers, ["ldap.dvpem.org"]}.
{ldap_base, "ou=People,dc=dvpem,dc=org"}.
{ldap_rootdn, "cn=Usuario,dc=dvpem,dc=org"}.
{ldap_password, "PASSusuario"}.
{ldap_port, 636}.
{ldap_encrypt, tls}.
{ldap_uids, [{"uid", "%u"}]}.
Vamos por línea:
  1. Deshabilita (comenta) autenticación interna.
  2. Habilita autenticación por LDAP.
  3. ldap_servers: indica cuáles son los servidores LDAP a los que se conectará ejabberd.
  4. ldap_base: especifica el DN base a partir del cual buscar los usuarios. Esto dependerá si se utiliza AD, OpenLDAP, u schemas propios.
  5. ldap_rootdn: especifica el usuario utilizado para conectar ejabberd con LDAP. El usuario utilizado debe poder listar usuarios y grupos, como mínimo.
  6. ldap_password: password del usuario utilizado en la conexión con LDAP.
  7. ldap_port: puerto del servidor LDAP.
  8. ldap_encrypt: indica que utilice TLS en la conexión.
  9. ldap_uids: indica qué atributo contiene el identificador del usuario. Esto también variará según el schema. En AD podría utilizarse samAccountName.
Reiniciar servidor ejabberd:
# server ejabberd restart
Nota 1: si iniciar ejabberd falla, probar de ejecutarlo en modo debug:
# ejabberd --debug
Nota 2: cuando la configuración está mal (o algo falla), puede que igualmente ejabberd deje un procesos corriendo y que ello les traiga problemas al intentar iniciar ejabberd nuevamente. Es un problema que queda medio oculto porque al iniciar ejabberd no arroja error, pero al mirar la lista de procesos escuchando, vemos que ninguno espera conexiones en el puerto default 5222 o 5280. En este caso, buscar y matar los procesos colgados antes de iniciar ejabberd nuevamente:
# killall -u ejabberd

Usar grupos de LDAP como grupos en los rosters

Es posible tomar los grupos de LDAP y utilizarlos en el roster, de forma que cada cliente que se conecte vea los grupos y los usuarios incluidos. Para ello, se puede utilizar el módulo mod_shared_roster_ldap, que por defecto no viene habilitado. Editar el archivo ejabberd.cfg, y en la sección de módulos agregar mod_shared_roster_ldap:
...
{modules,
 [
  ....
  {mod_shared_roster_ldap, [
        {ldap_base, "ou=Group,dc=dvpem,dc=org"},
        {ldap_rfilter, "(objectClass=posixGroup)"},
        {ldap_ufilter, "(&(objectClass=posixAccount)(uid=%u))},
        {ldap_gfilter, "(&(objectClass=posixGroup)(cn=%g))"},
        {ldap_groupattr, "cn"},
        {ldap_groupdesc, "cn"},
        {ldap_memberattr,"memberUid"},
        {ldap_memberattr_format, "cn=%u,ou=People,dc=dvpem,dc=org"},
        {ldap_useruid, "uid"},
        {ldap_userdesc, "cn"}
        ]},
 ]}.
El ejemplo está armado pensando en un servidor OpenLDAP, pero es fácilmente adaptable a AD, sólo hay que cambiar los nombres de los atributos.
Veamos cada uno de los atributos:
  • ldap_base: indica a partir de donde buscar los grupos para popular el roster.
  • ldap_rfilter: filtro que utilizará para popular el roster, y como los grupos del roster son los mismos de LDAP, pues ahí va. Dado que el base ya apunta a los grupos, no sería estrictamente necesario ya que no debería haber otra cosa que grupos en esa OU, pero por las dudas... En este caso se asume que los grupos son Posix, si usan AD tendrán que cambiar por el fitro que mejor les quede.
  • ldap_ufilter: filtro para obtener el atributo que contiene el nombre "humano" del usuario. Préstese atención que con este filtro obtenemos el nombre del atributo, no el valor del atributo, para esto último está ldap_user_desc. 
  • ldap_gfilter: filtro para obtener el nombre "humano" de los grupos. Misma idea que con ldap_ufilter.
  • ldap_groupattr: nombre del atributo LDAP que tiene el nombre del grupo.
  • ldap_groupdesc: nombre del atributo que tiene el nombre "humano" del grupo. Se usa en conjunto con ldap_gfilter, obteniendo del resultado de este filtro su valor.
  • ldap_memberattr: nombre del atributo LDAP que apunta a los miembros del grupo (member también es común).
  • ldap_memberattr_format: especifica el formato en que se guardan los miembros de un grupo. Por ejemplo, pueden tener cn=vektor,ou=People,dc=dvpem,dc=org. El %u le indica cuál es el nombre del usuario.
  • ldap_useruid: nombre del atributo que contiene el ID de usuario (en AD samAccountName).
  • ldap_user_desc: nombre del atributo que contiene el nombre "humano" del usuario. Se utiliza en conjunto con ldap_ufilter, obteniendo del resultado de este filtro su valor.

Instalar certificado propio

Para habilitar TLS/SSL ejabberd utiliza un sólo archivo que contiene clave privada, certificado y cadena de certificados. Por defecto, se genera uno al instalar ejabberd, denominado ejabberd.pem. Para no tener que editar el archivo de configuración, lo más simple es reemplazar este archivo con uno generado por nosotros a partir de nuestros certificados y clave. Este super archivo debe contener los datos en el siguiente orden:
  1. Clave privada
  2. Certificado
  3. Cadena certificante
De modo que, si por ejemplo tenemos los archivos private.key, certificado.crt y CA.crt, podemos unirlos fácilmente utilizando cat de la siguiente manera:
# cat private.key certificado.key CA.crt > /etc/ejabberd/ejabberd.pem

Timeouts

Un problema que surgió en uno de los servers que instalé, es que después de un rato de inactividad, las conexiones TCP de ejabberd con LDAP mueren. Buscando encontré que si no hay actividad durante un dado período de tiempo, algunos equipos de red pueden "desconectar" las sesiones TCP sin notificar al software que las está usando. En este caso, desde ejabberd se sigue viendo como que la conexión está activa, y al ver la conexión activa la utiliza pero sin obtener resultados. Desde mi punto de vista, esto es un bug en ejabberd, ya que si al realizar consultas no se obtiene respuesta, debería cerrar esa conexión e intentar conectarse nuevamente al servidor LDAP. En lugar de hacer eso, sólo da un authentication failure, sin loguear siquiera un timeout en los logs :S

Lo importánte aquí es la solución a este problema. El kernel de Linux soporta el envío de paquetes keepalive, para mantener activas conexiones TCP o marcar una conexión como "muerta". Esto lo realiza enviando paquetes keepalive a intervalos definidos de tiempo, esperando respuesta del servidor para decidir si la conexión está muerta. Es decir, cumple dos funciones, por un lado envía paquetes generando tráfico de red para que la conexión no se muera, y en el caso de que la conexión ya esté muerta, lo detecta y le avisa a la aplicación que la está usando.
La configuración se realiza a través de tres variables que se encuentran en /proc/sys/net/ipv4/
  • tcp_keepalive_time: default 7200 segundos, es decir, 2 horas. Especifica el intervalo en segundos entre el primer paquete de una secuencia de pruebas keepalive y el primer paquete de la próxima.
  • tcp_keepalive_intvl: default 75 segundos. Indica cada cuántos segundos enviar paquetes keepalive en una secuencia.
  • tcp_keepalive_probes: default 9. Valor numérico que especifica cuántos paquetes enviar en una secuencia.
El mecanismo es el siguiente: el kernel envía un paquete keepalive, espera el tiempo especificado en tcp_keepalive_intvl y envía otro, espera de nuevo y luego envía otro, así hasta alcanzar la cantidad de pruebas indicadas en tcp_keepalive_probes. Es decir, por defecto enviará 9 paquetes con una diferencia de 75 entre sí. Si no hay respuesta del otro lado, marca la conexión como muerta. Mientras tanto, una vez que se envió el primer paquete de esta secuencia, comienzan a contarse los segundos, y cuando se llega al valor de tcp_keepalive_time, comienza de nuevo con la secuencia mencionada.

Para no tener el problema de conexiones muertas podemo acomodar estos valores, ya que 2hs de espera puede ser demasiado. Según este post (http://start.nwt.fhstp.ac.at/blog/?p=307), los valores que mejores resultado les dieron son los siguientes:
tcp_keepalive_time = 600
tcp_keepalive_intvl = 30
tcp_keepalive_probes = 5
Lo cual se puede setear ejecutando:
# echo 600 > /proc/sys/net/ipv4/tcp_keepalive_time
# echo 30 > /proc/sys/net/ipv4/tcp_keepalive_intvl
# echo 5 > /proc/sys/net/ipv4/tcp_keepalive_probes
Al reiniciar el servidor, estos valores se perderán, pero pueden generar un script en bash que se ejecute al inicio.

Así que ya saben, si ven que las conexiones con el servidor LDAP figuran activas (lsof -Pni), pero los clientes dan authentication failure sin razón, prueben esta solución.


Clustering 

La configuración de un cluster ejabberd, es decir, tener más de un servidor ejabberd sirviendo el mismo dominio, es medio críptica, pero no compleja. Digo críptica porque hay que ejecutar comandos Erlang para que funcione. Básicamente configurar un cluster ejabberd es igual a configurar un cluster mnesia. Erlang utiliza mnesia, un manejador de base de datos distribuido, y lo que hay que hacer es configurar mnesia para que funcione en modo replicación.

Si bien la configuración puede ser multimaster, llamaré Master al primer nodo que damos de alta y Slave al segundo.


Configuración Master

Copiar cookie mágica de Erlang que se encuentra en /var/lib/ejabberd/.erlang.cookie al nodo slave. Esta cookie debe ser igual en todos los nodos.
# scp /var/lib/ejabberd/.erlang.cookie vektor@chat2.dvpem.org:~
O bien hacer un cat de .erlang.cookie y pegar el contenido en el nodo destino.

Editar archivo /etc/default/ejabberd y agregar las líneas:
ERLANG_NODE=ejabberd@chat1
INET_DIST_INTERFACE={192,168,1,1}
donde:
  1. ERLANG_NODE especifica el nombre completo del nodo.
  2. INET_DIST_INTERFACE es la IP en la cual esperará conexiones. Utilizar comas para separar los octetos, es decir, en lugar de utilizar los convencionales puntos, separar con comas.
Reiniciar el master:
# service ejabberd restart

Configuración Slave

Detener el servicio de ejabberd:
# service ejabberd stop
# killall -u ejabberd
Editar archivo /etc/default/ejabberd y agregar las líneas:
ERLANG_NODE=ejabberd@chat2
INET_DIST_INTERFACE={192,168,1,2}
Mover la cookie al directorio de ejabberd y cambiar los permisos para que el usuario ejabberd la pueda acceder:
# mv .erlang.cookie /var/lib/ejabberd/
# chown ejabberd:ejabberd /var/lib/ejabberd/.erlang.cookie
# chmod 400 /var/lib/ejabberd/.erlang.cookie
Iniciar ejabberd:
# service ejabberd start
Asegurarse que ejabberd se está ejecutando:
  # ejabberctl status
Abrir una consola Erlang para conectar al nodo 1 y realizar una copia de la base de datos:
# ejabberctl debug
En la consola Erlang, ejecutar lo siguiente:
(ejabberd@chat2)1> mnesia:stop(),
(ejabberd@chat2)1> mnesia:delete_schema([node()]),
(ejabberd@chat2)1> mnesia:start(),
(ejabberd@chat2)1> mnesia:change_config(extra_db_nodes, ['ejabberd@chat1']),
(ejabberd@chat2)1> mnesia:change_table_copy_type(schema, node(), disc_copies).
(ejabberd@chat2)1> mnesia:info().
Cerrar la sesión precionando Ctrl+c Ctrl+c

donde:
  • mnesia:stop() detiene la ejecución de la BD mnesia,
  • mnesia:delete_schema([node()]) elimina el schema actual del nodo,
  • mnesia:start() inicia nuevamente la BD,
  • mnesia:change_config(extra_db_nodes, ['ejabberd@chat1']) apunta la base de datos al nodo 1
  • mnesia:change_table_copy_type(schema, node(), disc_copies) crea una copia local del schema.
  • mnesia:info() imprime información del nodo. Al ejecutar este comando deberían ver ambos nodos en ejecución:
      ...
      running db nodes   = ['ejabberd@chat1','ejabberd@chat2']
      ...
Esto sólo copia el esquema de la base de datos en el slave. Si bien todo funciona correctamente así, si el master cae, el sistema en teoría deja de funcionar, ya que el slave no tiene copia de las tablas.
Ahora, para tener un entorno multi-master hay que realizar una copia de todas las tablas en el nodo slave... que ya no sería más slave, sino otro master. En este caso los writes serán más lentos, pero tendremos un entorno de alta disponibilidad.
El comando para copiar tablas de otro nodo es mnesia:add_table_copy... pero hacerlo tabla por tabla es tedioso. Encontré en un comentario de StackOverflow como hacer una copia de todas las tablas en un comando:
(ejabberd@chat2)1> [{Tb, mnesia:add_table_copy(Tb, node(), Type)} || {Tb, [{'ejabberd@chat1', Type}]} <- [{T, mnesia:table_info(T, where_to_commit)} || T <- mnesia:system_info(tables)]].
Hay que ejecutarlo en una consola Erlang con "ejabberdctl debug".


Referencias



Crear una CA con OpenSSL y firmar/revocar certificados con ella
Ya es el 4to  artículo (o fueron más?) que escribo sobre certificados digitales, y sin embargo siempre me encuentro con algún requerimiento nuevo. Esta vez necesitaba crear una CA propia para firmar mis certificados, y como siempre, decidí utilizar OpenSSL.

En los siguientes pasos describiré cómo crear la susodicha CA y cómo firmar certificados con la misma.

Si les interesa, pueden dar un repaso a los otro artículos del tema:
- Certificados Digitales
- Firmando una CA propia
- Shortcuts: comandos OpenSSL para generar claves, certificados y testear conexiones


1. Configurar OpenSSL

El primero paso es copiar o editar la información en /etc/ssl/openssl.cnf. Son pocos los campos que es importante/requerido editar. El resto pueden modificarlos también, pero  la idea acá es mostrar los requerimientos mínimos.

En el archivo openssl.cnf buscar y editar los siguientes valores:
[ CA_default ]
dir             = /ca
...
new_certs_dir   = $dir/newcerts
certificate     = $dir/ca.crt
...
private_key     = $dir/ca.key
...
default_days    = 365
donde:
  • dir: directorio que alojará la información de la CA, como certificados firmados, base de datos, número de serie, etc.
  • new_certs_dir: donde se alojarán los certificados firmados.
  • certificate: ubicación del certificado de la CA.
  • private_key: la clave de la CA.
  • default_days: cantidad de días de validez de un certificado por default.
También será de mucha utilidad pegarle una mirada y editar lo siguiente:
[ req_distinguished_name ]
countryName                     = Country Name (2 letter code)
countryName_default             = AR
countryName_min                 = 2
countryName_max                 = 2
stateOrProvinceName             = State or Province Name (full name)
stateOrProvinceName_default     = Buenos Aires
localityName                    = Locality Name (eg, city)
0.organizationName              = Organization Name (eg, company)
0.organizationName_default      = Super CA
Estos datos facilitan la vida al generar certificados, ya que se tomarán por default y no habrá que cargarlos cada vez que se genere un nuevo request.

Una vez finalizada la edición, realizar lo siguiente en el directorio default (ver variable dir de la configuración):
  • crear los archivos de texto index.txt y serial:
    # touch /ca/index.txt /ca/serial
    donde:
    index.txt es la base de datos de certificados firmados por la CA.
    serial cotiene el número de serie que debe colocarle al próximo certificado que firme.
  • al archivo serial, agregarle un valor. Este será el número de serie que imprima al primer certificado que genere, luego lo actualizará solo. El valor debe ser de dos dígitos:
    echo "01" > /ca/serial
  • crear el directorio para los certificados nuevos, si es que todavía no existe. Para el ejemplo dado:
    # mkdir /ca/newcerts

2. Generar clave y certificado de CA

Claramente la CA debe contar con su propia clave y certificado. Estos se utilizarán para firmar los certificados que se requieran luego. La forma más rápida es utilizando un sólo comando:
# openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout /ca/ca.key -out /ca/ca.crt -config /ca/openssl.cnf
Generating a 2048 bit RSA private key
...+++
..........................................+++
writing new private key to 'ca.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AR]:
State or Province Name (full name) [Buenos Aires]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Super CA]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:super-ca.com
Email Address []:
Como verán, el archivo de configuración hace que los valores de "Country Name", "State or Province Name", etc se tomen de ahí.


3. Firmar certificados

En los anteriores artículos expliqué cómo generar un request de certificado, por lo que no me explayaré en eso. Les dejo el siguiente comando que crea una clave y CSR, y además utiliza como template el archivo de configuración:
# openssl req -new -newkey rsa:2048 -keyout prueba.key -out prueba.csr -config /ca/openssl.cnf
Generating a 2048 bit RSA private key
......................................+++
....+++
writing new private key to 'prueba.key'
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AR]:
State or Province Name (full name) [Buenos Aires]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Super CA]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:superprueba.com
Email Address []:
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
Ok, ahora si a lo bueno, con este simple comando podremos firmar los request de certificados que deseemos:
# openssl ca -cert /ca/ca.crt -keyfile /ca/ca.key -in prueba.csr -out prueba.crt -config /ca/openssl.cnf
Using configuration from /ca/openssl.cnf
Check that the request matches the signature
Signature ok
Certificate Details:
Serial Number: 2 (0x2)
Validity
   Not Before: Jan  5 15:40:56 2015 GMT
   Not After : Jan  5 15:40:56 2016 GMT
Subject:
   countryName               = AR
   stateOrProvinceName       = Buenos Aires
   organizationName          = Super CA
   commonName                = superprueba.com
X509v3 extensions:
   X509v3 Basic Constraints:
CA:FALSE
   Netscape Comment:
OpenSSL Generated Certificate
   X509v3 Subject Key Identifier:
EE:9C:75:57:66:F6:3E:FA:D9:CF:6F:06:60:E0:97:D1:EE:EC:14:EA
   X509v3 Authority Key Identifier:
keyid:98:7A:32:95:93:72:24:37:B0:16:61:10:8D:E7:51:5F:54:95:C7:62
Certificate is to be certified until Jan  5 15:40:56 2016 GMT (365 days)
Sign the certificate? [y/n]:y

1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated

4. Revocar certificados

Finalmente, y porque seguro les sucederá en algún momento, veamos cómo revocar un certificado. Es tan simple como ejecutar el siguiente comando:
# openssl ca -revoke prueba.crt -config /ca/openssl.cnf
Using configuration from /ca/openssl.cnf
Revoking Certificate 01.
Data Base Updated
Si miran el archivo index.txt, podrán observar una R al principio de la línea del certificado.

Un tip interesante, es qué pasa si no tenemos el certificado que queremos revocar. En este post de stackoverflow explican una forma de hacerlo.
OpenSSL guarda una copia de los certificados que firma en el directorio "newcerts" (o donde lo hayan configurado en openssl.cnf). Los certificados se guardan con el número de serie como nombre, por lo que primero hay que hayar el número de serie del certificado que queremos revocar. Un simple grep puede ayudarnos en este caso:
# grep "prueba" /ca/index.txt
V       160105143331Z           01      unknown /C=AR/ST=Buenos Aires/O=Super CA/CN=prueba
donde vemos que el ID es 01.
Con esta info, ejecutamos el comando anterior de la siguiente manera:
# openssl ca -revoke newcerts/01.pem -config /ca/openssl.cnf

Referencias

How To Setup a CA
Nagios: generar gráficos con PNP4Nagios
Hace tiempo que estoy trabajando con Nagios y MRTG, como pudieron observar en Nagios: monitoreo remoto de dispositivos + agentes Linux + agentes Windows, y  Monitoreo de ancho de banda en routers/switches/etc usando MRTG. Ambas herramientas me resultaron excelentes y me ayudan mucho en el monitoreo diario de la infraestructura de red.
Desde hace un tiempo vengo buscando la forma de integrar más gráficos en Nagios, y me pareció interesante utilizar MRTG para hacerlo. Esto es perfectamente posible y hasta comencé a trabajar en ello, pero luego descubrí PNP4Nagios y cambié la perspectiva.

Tal como cita la página oficinal:
PNP es un addon para Nagios que analiza datos de performance (performance data) provistos por los plugins y los almacena en bases de datos RRD.
Oye, eso suena similar a lo que MRTG hace. Pues claro, el acercamiento es similar, almacenar datos en bases de datos RRD y luego graficar a partir de ellos utilizando rrdtool. De forma similar trabajan Ntop y otras herramientas.

PNP4Nagios tiene una interfaz web muy atractiva y simple de usar, permitiendo seleccionar intervalos de tiempo de forma dinámica, navegar fácilmente entre servicios de hosts, y generar reportes en PDF. Tal vez una de las características más interesantes es la posibilidad de definir templates propios para indicarle la forma de evaluar y generar los gráficos. Dado que trabaja directamente con RRDTool, brinda todas las facilidades de graficación que esta herramienta provee.

Cabe destacar que para poder graficar datos, los plugins deben retornar información en el campo performance_data, y no todos los plugins lo hacen. Por suerte, quienes diseñan buenos plugins si entregan datos de performance. Estos datos deben tener un formato especial para que PNP pueda parsearlos y almacenarlos, pero esa discusión la dejaré para algún artículo sobre creación de plugins.
Para saber fácilmente si un plugin entrega datos de performance, pueden ejecutarlo desde la consola y ver si incluye el caracter pipe "|" en el resultado impreso. Todo lo que venga luego del pipe son datos de performance. Por ejemplo:
  $./check_icmp -H 10.6.143.101
  OK - 10.6.143.101: rta 1,593ms, lost 0%|rta=1,593ms;200,000;500,000;0; pl=0%;40;80;; rtmax=4,541ms;;;; rtmin=0,832ms;;;;
Este plugin entrega 4 datos de performance:
  - rta: tiempo de respuesta,
  - pl: porcentaje de pérdida de paquetes,
  - rtmax: tiempo máximo de respuesta,
  - rtmin: tiempo mínimo de respuesta.
 

Integración con Nagios

PNP4Nagios provee varios modos de integrarse con Nagios, cada uno con sus ventajas y desventajas:

  • Synchronous: cada vez que Nagios ejecuta un plugin, luego llama el script process_perfdata.pl para actualizar los valores de la base de datos RRD y el archivo XML correspondiente. La mayor desventaja de esta forma de integración es que Nagios ejecuta process_perfdata.pl cada vez que realiza un check de host o servicio.
  • Bulk: este modo permite realizar múltiples actualizaciones de datos de performance juntos. Nagios escribe en un archivo temporal los datos obtenidos en cada check, y a intervalos definidos, Nagios ejecuta el script process_perfdata.pl.
  • Bulk with NPCD: desde el punto de vista de Nagios, es la mejor opción, dado que se independiza de la ejecución del script process_perfdata.pl. La ejecución de script queda a cargo del demonio NPCD (Nagios Performance C Daemon), el cual monitorea un directorio, donde deben ubicarse los datos de performance, a la espera de que un archivo sea creado. De esta forma, Nagios se encarga de guardar los datos de la ejecución de los plugins en archivos y luego mover los archivos al directorio donde los tomará NPCD.
  • Bulk with NPCD and npcdmod: el flujo de datos es igual a Bulk with NPCD, con la diferencia que simplifica la configuración de Nagios, ya que las definiciones de cómo procesar los datos se dejan al módulo npcdmod. El uso de este módulo se realiza definiendo un broker en la configuración de Nagios. Debe tenerse en cuenta que este modo no funciona con Nagios 4.
  • Gearman: se utiliza el módulo Gearman como agente intermediario entre Nagios y el procesamiento de los datos. Nagios se encarga de generar los datos y mod_gearman (broker para Nagios) se encarga de encolarlos para su procesamiento, utilizando el demonio gearmand. Luego el script process_perfdata.pl escanea esta cola en busca de datos. La mayor ventaja de este modo es que Nagios y PNP4Nagios pueden ejecutarse en máquinas distintas, ya que la conexión de Nagios con Gearman es a través de sockets. Además Gearman funciona con workers y puede tener múltiples workers trabajando a la vez. Este modo es el ideal para instalaciones de Nagios con hosts y servicios.

De las opciones listadas, elegí utilizar el modo Bulk with NPCD y npcdmod, dado lo fácil que es ponerlo en funcionamiento, y las ventajas que provee sobre el modo Synchronous. Si algún día utilizan Nagios 4, deberían utilizar otra opción como Bulk with NPCD o Gearman.


Instalación y Configuración

Instalar PNP4Nagios en debian y derivados es tan simple como ejecutar:
  # apt-get install pnp4nagios rrdtool
Existen paquetes para otras distribuciones como CentOS.
 
Luego de instalarlo, para integrarlo con Nagios utilizando el modo Bulk with NPCD y npcdmod, debe realizarse los siguientes pasos.

Setear la variable RUN en yes, en el archivo /etc/default/npcd, para habilitar en demonio NPCD.
RUN=yes
Editar el archivo /etc/nagios3/nagios.cfg de la siguiente manera:
# Habilita el procesamiento de datos de performance entregados por los plugins de chequeo
process_performance_data=1
# Hacer que Nagios comparta su información con la librería npcdmod
broker_module=/usr/lib/pnp4nagios/npcdmod.o config_file=/etc/pnp4nagios/npcd.cfg
Recargar Nagios:
/etc/init.d/nagios3 reload
Gualá, ya está la integración, eso fue fácil.

Por defecto, PNP4Nagios se habilita en un directorio web distinto al de Nagios. Los gráficos se acceden apuntando a las siguientes direcciones:
http://<nombre servidor>/pnp4nagios/pnp4nagios/graph?host=<nombre del host>
http://<nombre servidor>/pnp4nagios/pnp4nagios/graph?host=<nombre del host>&srv=<descripción del servicio>
Como nombre de host debe utilizarse el nombre que se utilizó para definirlo en Nagios.


Una forma más directa de acceder a los gráficos, es agregar el link como un action_url en la definición de hosts en Nagios. El camino fácil es definir nuevos templates para hosts y servicios, de la siguiente manera:
define host {
 use generic-host
 name pnp-host
 action_url /pnp4nagios/index.php/graph?host=$HOSTNAME$&srv=_HOST_
 register 0
}
define service {
 name pnp-service
 action_url /pnp4nagios/index.php/graph?host=$HOSTNAME$&srv=$SERVICEDESC$
 register 0
}
Luego, para cada host o servicio que se desee ver gráficos, simplemente agregar pnp-host y pnp-service en la definición de hosts.


Pensamientos Finales

A diferencia de MRTG que utiliza consultas SNMP para obtener sus datos, PNP4Nagios utiliza los datos entregados por los plugins de Nagios... los cuales pueden sacar datos por SNMP o de muchas otras maneras. Esta funcionalidad me resultó mucho más abarcativa, y ya que igualmente necesito Nagios y estoy obteniendo datos con sus plugins, por qué no aprovecharlos para generar los gráficos.
Claro que es posible obtener con MRTG datos de otras fuentes que no sea SNMP, MRTG es muy flexible. Sería posible llegar a resultados similares a los obtenidos con Nagios y PNP, pero de una forma más laboriosa. No estoy diciendo que esta solución reemplace MRTG, sino que me parece mucho más completa. La elección de utilizar una u otra es de cada administrador.



Referencias

- PNP4Nagios Documentation
- Archivo /usr/share/doc/pnp4nagios/README.Debian
Obtener uso de CPU y memoria, temperatura y estado de interfaces de dispositivos Cisco, utilizando SNMP
Continuando con la labor de monitorear el estado de dispositivos conectados a la red, decidí profundizar en el control de equipamiento Cisco. Para ello, recurrí al viejo pero muy vigente y útil protocolo SNMP. Los dispositivos Cisco, como la gran mayoría de los dispositivos de red, proveen información detallada a través de SNMP. El problema es que entre tanta información provista, uno se puede marear y perder lo que le interesa conocer.

Dado que no encontré mucha información que detalle cómo monitorear e interpretar la información de los recursos más importantes (si encontré pedazos de información por separado), decidí investigar y armar documentación propia. Distintas páginas de Cisco proveen información, pero dan por asumido que conocemos relativamente bien la información provista. Lo único que me resultó realmente útil es el navegador de objetos SNMP (http://tools.cisco.com/Support/SNMP/do/BrowseOID.do?local=en). En base a este último pude recopilar y armar las consultas que yo requería.

Yendo al grano, con la información provista en este artículo podrán no sólo saber cómo obtener por SNMP información del estado de los recursos del dispositivo, sino también entender los valores obtenidos.

Me centraré en obtener la información listada a continuación, la cual abarca todo lo necesario para conocer el estado de un dispositivo:
- Estado de las interfaces de red del dispositivo.
- Uso de CPU.
- Temperatura del chasis, y/u otra información provista por los sensores que el equipo posea.
- Uso de memoria.
En el artículo me centraré en describir los OIDs necesarios para obtener la información listada. Los ejemplos los haré utilizando snmpwalk como herramienta de consulta. Esta misma información se puede obtener con distintos clientes snmp, snmpwalk es sólo uno de ellos, muy versátil y completo. Una vez que se conocen los OIDs y la información provista, es muy fácil automatizar el monitoreo con herramientas como Nagios, o scripts propios. Dejaré la integración con Nagios para otro artículo :D

Si les interesa el monitoreo de redes, pueden encontrar más información al respecto en mis anteriores artículos:
- Nagios: monitoreo remoto de dispositivos + agentes Linux + agentes Windows
- Monitoreo de ancho de banda en routers/switches/etc usando MRTG
- Armar servidor de logging con rsyslog y configurar dispositivos para logging remoto
- Instalar Zabbix en Debian GNU/Linux

Estado de Interfaces

Hay ciertas interfaces que son muy interesantes para monitorear, dado que ello nos permitirá saber si un equipo importante perdió la conexión. Interfaces de este tipo pueden ser uplinks entre edificios, conexiones a servidores, enlaces a la WAN, etc.

El OID utilizado para ver el estado de las interfaces es el 1.3.6.1.2.1.2.2.1.8 (objeto ifOperStatus), descripto como "The current operational state of the interface". Este OID contiene una entrada por cada interfaz presente en el switch.
Por ejemplo, al realizar un walk en un 2950, obtenemos la siguiente información:
$ snmpwalk -v2c -c public 192.168.1.100 -On  1.3.6.1.2.1.2.2.1.8
.1.3.6.1.2.1.2.2.1.8.1 = INTEGER: up(1)
.1.3.6.1.2.1.2.2.1.8.2 = INTEGER: down(2)
.1.3.6.1.2.1.2.2.1.8.3 = INTEGER: down(2)
...
...
Ok, esto nos da el estado de las interfaces... pero cómo sabemos cuál interfaz es cada una? Para esto existe el OID 1.3.6.1.2.1.2.2.1.2 (objeto ifDescr), que contiene información de la interfaz. En el ejemplo anterior, la consulta de este OID nos dará lo siguiente:
$ snmpwalk -v2c -c public 192.168.1.100 -On  1.3.6.1.2.1.2.2.1.2
.1.3.6.1.2.1.2.2.1.2.1 = STRING: FastEthernet0/1
.1.3.6.1.2.1.2.2.1.2.2 = STRING: FastEthernet0/2
.1.3.6.1.2.1.2.2.1.2.3 = STRING: FastEthernet0/3
...
Ahora si, ya sabemos que el OID 1.3.6.1.2.1.2.2.1.8.1 representa el estado de la interfaz FastEthernet0/1, el 1.3.6.1.2.1.2.2.1.2.3 la Fa0/3, etc. Para cada interfaz que se desee monitorear, deberá consultarse primero la descripción de la misma, así sabemos con cuál OID se relaciona.


Uso de CPU

Cisco almacena distintos valores para el uso de CPU, utilizando distintos períodos de tiempo. Los OIDs son:
  • 1.3.6.1.4.1.9.9.109.1.1.1.1.10 (cpmCPUTotalMonIntervalValue): uso de CPU en el intervalo especificado por 1.3.6.1.4.1.9.9.109.1.1.1.1.9 (cpmCPUMonInterval). El intervalo suele ser 5 segundos, y este OID es el que debe utilizarse en lugar de cpmCPUTotal5sec y cpmCPUTotal5secRev, ya en desuso.
  • 1.3.6.1.4.1.9.9.109.1.1.1.1.7 (cpmCPUTotal1minRev): uso en el último minuto. Anteriormente se utilizaba cpmCPUTotal1min.
  • 1.3.6.1.4.1.9.9.109.1.1.1.1.8 (cpmCPUTotal5minRev): uso en los últimos cinco minutos. Debe utilizarse en lugar de cpmCPUTotal5min.
La empresa recomienda utilizar cpmCPUTotal5minRev, dado que representa la mejor estimación de uso.
Todos estos valores representan porcentaje de uso y van de 0 a 100.

Ejemplo de consulta del valor de carga de CPU en los últimos 5 minutos es el siguiente:
$ snmpwalk -v2c -c public 192.168.1.100 -On  1.3.6.1.4.1.9.9.109.1.1.1.1.8
.1.3.6.1.4.1.9.9.109.1.1.1.1.8.1 = Gauge32: 6
es decir, la carga es de 6%.

En la página How to Collect CPU Utilization on Cisco IOS Devices Using SNMP se explica bien como obtener información del uso de CPU.


Sensores (midiendo temperatura y estado de fans)

Existen varios sensores en los dispositivos que permiten medir diferentes valores, como la temperatura del chasis y el funcionamiento de los fans. Los valores de los sensores se almacenan en el objeto 1.3.6.1.4.1.9.9.91.1.1.1.1.4 (entSensorValue).
Por ejemplo, accediendo los valores de los sensores en un 4500 obtendremos algo así:
$ snmpwalk -v2c -c public 192.168.1.200 -On 1.3.6.1.4.1.9.9.91.1.1.1.1.4
.1.3.6.1.4.1.9.9.91.1.1.1.1.4.9 = INTEGER: 35
.1.3.6.1.4.1.9.9.91.1.1.1.1.4.15 = INTEGER: 1
.1.3.6.1.4.1.9.9.91.1.1.1.1.4.18 = INTEGER: 2
Este objeto contiene entradas indexadas según el índice físico de cada entidad (en este caso, el sensor es la entidad). Para saber a qué entidad refiere cada índice, se puede consultar el objeto 1.3.6.1.2.1.47.1.1.1.1.2 (entPhysicalDescr), el cual describe cada entidad física.
En el ejemplo anterior, podemos ver lo siguiente al consultar este objeto:
$ snmpwalk -v2c -c public 192.168.1.200 -On 1.3.6.1.2.1.47.1.1.1.1.2
...
.1.3.6.1.2.1.47.1.1.1.1.2.9 = STRING: "Chassis Temperature Sensor"
...
.1.3.6.1.2.1.47.1.1.1.1.2.15 = STRING: "Power Supply Fan Sensor"
...
.1.3.6.1.2.1.47.1.1.1.1.2.18 = STRING: "Power Supply Fan Sensor"
Es decir, los valores obtenidos anteriormente son la temperatura del chasis y el estado de los fans de las fuentes.
Ahora, qué significan esos valores? en qué unidades están representados? Bueno, para esto necesitamos consultar ooootro objeto, el 1.3.6.1.4.1.9.9.91.1.1.1.1.1 (entSensorType), que nos indica de qué tipo es cada sensor:
.1.3.6.1.4.1.9.9.91.1.1.1.1.1.9 = INTEGER: 8
.1.3.6.1.4.1.9.9.91.1.1.1.1.1.15 = INTEGER: 12
.1.3.6.1.4.1.9.9.91.1.1.1.1.1.18 = INTEGER: 12
Cisco tiene predefinidos estos valores en una tabla, la cual se puede encontrar en su página. La tabla indica lo siguiente:
1:other
2:unknown
3:voltsAC
4:voltsDC
5:amperes
6:watts
7:hertz
8:celsius
9:percentRH
10:rpm
11:cmm
12:truthvalue
13:specialEnum
14:dBm
Con esto sabemos que el primer sensor mide grados celsius (valor 8), y los otros dos otorgan un valor booleano (12). Pero qué significan los valores 1 y 2? cuál es verdadero y cuál es falso? Consultando la página de Cisco, encontramos lo siguiente:
other(1): a measure other than those listed below
unknown(2): unknown measurement, or
arbitrary, relative numbers
voltsAC(3): electric potential
voltsDC(4): electric potential
amperes(5): electric current
watts(6): power
hertz(7): frequency
celsius(8): temperature
percentRH(9): percent relative humidity
rpm(10): shaft revolutions per minute
cmm(11),: cubic meters per minute (airflow)
truthvalue(12): value takes { true(1), false(2) }
specialEnum(13): value takes user defined enumerated values
dBm(14): dB relative to 1mW of power
Gracias a esta última información, ahora sabemos que 1 es verdadero, y 2 es falso. Es decir, el Fan 1 funciona bien, mientras que el 2 no.

Juntando todo este meollo de consultas, ahora sabemos lo siguiente:
  • El switch posee 3 sensores.
  • El primero mide la temperatura del chasis, la cual en este momento es de 35º
  • Los otros dos sensores chequean el estado de los fans de las fuentes, estando el primer fan funcionando bien y el segundo no.
 
Uso de Memoria

Es posible ver el uso de memoria de los dispositivos, así como la cantidad de memoria libre, accediendo al objeto 1.3.6.1.4.1.9.9.48 (ciscoMemoryPoolMIB). Cisco divide la memoria en pools, según su uso. Los tipos de pools predefinidos son:
1:  processor memory
2:  i/o memory
3:  pci memory
4:  fast memory
5:  multibus memory
Además, los pools pueden ser categorizados en dinámicos o predefinidos. De los listados anteriormente, sólo el tipo processor debe ser soportado por todos los dispositivos.
Ejemplo de valores obtenidos en un 2950:
$ snmpwalk -v2c -c public 192.168.1.100 -On 1.3.6.1.4.1.9.9.48
.1.3.6.1.4.1.9.9.48.1.1.1.2.1 = STRING: "Processor"
.1.3.6.1.4.1.9.9.48.1.1.1.2.2 = STRING: "I/O"
.1.3.6.1.4.1.9.9.48.1.1.1.3.1 = INTEGER: 2
.1.3.6.1.4.1.9.9.48.1.1.1.3.2 = INTEGER: 0
.1.3.6.1.4.1.9.9.48.1.1.1.4.1 = INTEGER: 1
.1.3.6.1.4.1.9.9.48.1.1.1.4.2 = INTEGER: 1
.1.3.6.1.4.1.9.9.48.1.1.1.5.1 = Gauge32: 2417476
.1.3.6.1.4.1.9.9.48.1.1.1.5.2 = Gauge32: 687872
.1.3.6.1.4.1.9.9.48.1.1.1.6.1 = Gauge32: 1467696
.1.3.6.1.4.1.9.9.48.1.1.1.6.2 = Gauge32: 1492064
.1.3.6.1.4.1.9.9.48.1.1.1.7.1 = Gauge32: 1274620
.1.3.6.1.4.1.9.9.48.1.1.1.7.2 = Gauge32: 1492060
De los valores obtenidos, tenemos que:
  • 1.3.6.1.4.1.9.9.48.1.1.1.2 (ciscoMemoryPoolName) define el nombre del pool. En el ejemplo hay dos pools, Processor e I/O.
  • 1.3.6.1.4.1.9.9.48.1.1.1.5 (ciscoMemoryPoolUsed) muestra el uso de memoria actual de cada pool, expresado en bytes.
  • 1.3.6.1.4.1.9.9.48.1.1.1.6 (ciscoMemoryPoolFree) representa la memoria libre en cada pool, también en bytes.
La memoria total del equipo se puede obtener sumando los bytes libres con los utilizados.
AirWatch MDM Review
Hace un tiempo comenzamos a utilizar dispositivos Android en la empresa y se hizo evidente que necesitaríamos un software para administrarlos. Desde hace varios años utilizamos BlackBerry, para lo cual contamos con BlackBerry Enterprise Server (BES). Personalmente BES siempre me pareció una cagada, tiene sus cosas buenas y fue uno de los pioneros del rubro, pero las cosas no andan bien. Los servicios de BB en general siempre me parecieron una bosta. Que algún administrador de BES me diga si al menos una vez al mes (o a la semana) algún BB deja de sincronizar los mails y encuentra otro remedio más que desvincular el equipo y volver a vincurlarlo? si, a veces se soluciona con una reiniciada, pero muchas veces no! y lo peor es que es imposible saber la causa.

Bueno, dejando mi resentimiento hacia BES, volvemos a Android. Android es una plataforma muy buena, pero creo que no tan orientada a las empresas todavía. De a poco va queriendo, pero todavía le falta bastante. El tema es que los dispositivos Android parecen estar pensados solamente para el usuario hogareño. Para poder utilizar este sistema a nivel corporativo, necesitamos una herramienta que nos brinde ciertas funcionalidades.

Veamos primero qué es lo que se necesita para poder administrar dispositivos móviles en general, y luego nos adentraremos en AirWatch en particular.


Introducción MDM para Android

Mi experiencia me permite distinguir las siguientes necesidades, a la hora de administrar dispositivos móviles Android (u otros) corporativos:
  • Uso de mail corporativo: Android soporta ActiveSync para poder utilizar un servidor de correo Exchange. La necesidad que identifico es poder administrar esta conexión desde un software centralizado.
  • Políticas de seguridad: muy pero muy importante es poder aplicar restricciones a los equipos. Como la mayoría sabe, existen miles de aplicaciones maliciosas para Android. Si permitimos a cualquier usuario instalar cualquier cosa, vamos a tener un grave problema. Las políticas de seguridad deben permitir:
    • Forzar uso de PIN o patrón para bloquear el dispositivo.
    • Bloquear la instalación y desinstalación de aplicaciones. Así como no queremos que instalen nada, también queremos que no desinstalen software básico.
    • Bloquear "reset to factory" del equipo. Si borran la configuración, después pueden hacer cualquier cosa.
    • >
    • Bloquear el cambio de ciertas capacidades estéticas, como cambio de wallpaper.
  • Instalación remota de aplicaciones: bien, no permitimos instalar aplicaciones al usuario, pero si el mismo necesita algo para sus tareas, debemos poder instalarselo. Estos usuarios puede que no estén físicamente en las instalaciones de la empresa, así que poder instalar aplicaciones desde el administrador, es un requisito.
  • Uso de encripción: dado que los equipos pueden transportar información sensible, es necesario poder activar la encripción de la tarjeta de memoria y/o memoria del equipo.
  • Bloqueo / Wipe remoto: si un dispositivo es robado o perdido, el administrador debe poder bloquearlo e incluso borrarlo para que no se accedan a los datos de la empresa.
  • Configuración centralizada: tener que configurar muchos dispositivos de a uno, no solo estresa a cualquiera, sino que es una pérdida de tiempo. La mejor alternativa es realizar la configuración una vez, y que todos los dispositivos la apliquen al conectarse.

Los programas que proveen estas funcionalidades se denominan Mobile Device Management (MDM), y hay varios dando vueltas. El mismo BES es un MDM que implementa las funcionalidades descriptas. Uno de estos MDMs es AirWatch y en él me enfocaré el resto del artículo. AirWatch, según sus propias palabras, es el MDM líder a nivel empresarial.


AirWatch

Estuve utilizándo AirWatch durante un par de semanas, haciendo varias pruebas, y quería compartir mi experiencia con esta herramienta, por si alguno de ustedes está buscando algún MDM y está indeciso. Les comentaré tanto las cosas buenas, como las cosas malas que encontré al utilizarlo.

Para empezar, me pareció muy útil que soporte múltiples sistemas operativos (Android, iOS, BlackBerry, Symbian, Windows Phone, Mac OS X, entre otros). Si el parque de dispositivos no es homogéneo, como en nuestro caso, esto es una característica importante. Es importante observar aquí que no es la panacea. Si bien el soporte de sistemas operativos existe, en muchos casos el soporte es muy limitado. Por ejemplo, no podrán reemplazar BES por AirWatch para administrar los BlackBerry, ya que las características que AirWatch soporta para BB son muy (MUY) limitadas.
En cada opción de configuración de AirWatch, podrán observar al costado, qué sistemas operativos la soportan. Incluso dependiendo la versión del sistema operativo, puede que la opción esté disponible o no (por ejemplo, algunas cosas disponibles para Android 3, no están disponibles para Android 2). A favor de AirWatch hay que decir que en muchos casos la limitación la pone el sistema operativo. Si el sistema operativo no provee una forma de evitar el uso de la cámara, el software no podrá hacerlo.
Otro punto a tener en cuenta, es que la mayoría de las opciones de configuración para Android, sólo están disponibles para dispositivos SAFE (Samsung For Enterprises). Si no adquirieron dispositivos Android que sean SAFE, casi que ni les conviene utilizar AirWatch.

AirWatch implementa todos los puntos listados en la introducción, aunque no todos para todos los sistemas operativos. Para equipos SAFE, todas las opciones están disponibles, teniendo algunas limitaciones en cuanto a la granularidad.
Algo interesante es que AirWatch permite organizar los dispositivos en unidades organizativas (por ejemplo, departamento), y a los usuarios por grupos. Luego es posible definir perfiles (que incluyen políticas de uso, certificados, bookmarks, VPN, WiFi, etc) por unidad organizativa y por grupo de usuario. Esto brinda la flexibilidad de poder definir, por ejemplo, que ciertas restricciones se apliquen a los usuarios de comercial, que se encuentren en el grupo de usuarios atención al cliente. La organización de los grupos resulta un poco confusa al principio, pero luego del uso se va afinando.

La instalación remota de aplicaciones existe, pero no es del todo directa. Es posible distribuir tanto aplicaciones públicas, como adquiridas por la empresa (llámese APK). Es posible instalar las APK sin intervención del usuario, pero si es una aplicación de la Play Store, requerirá la intervención del usuario, algo para nada conveniente. Tal vez sea algo que me falló a mi, pero luego de varias pruebas, no logré que se instale algo directamente.

Dado que muchos dispositivos incluyen aplicaciones que tal vez no deseamos (Google trae algunas que en modo usuario son imposible desinstalar... llámese Gmail, G+, etc), es posible desinstalarlas/desactivarlas mediante AirWatch. Este es un punto muy a favor del software, dado que sin rootear el dispositivo no es posible eliminar ciertas aplicaciones. Además es posible restringir la instalación de aplicaciones y no permitir que el usuario desinstale las importantes. Esto se logra con los grupos de aplicaciones Blacklisted, Whitelisted y Required. Todas las listas se arman con los IDs de las aplicaciones. Cada aplicación tiene un ID único, como com.google.android.gm para GMail.
  • Blacklisted permite listar las aplicaciones explícitamente prohibidas. Si una aplicación está en esta lista, no se podrá instalar.
  • Whitelisted, si está activa, restringe que no se instale ninguna aplicación que no se encuentre en la lista.
  • Required no permite que el usuario desinstale ninguna de las aplicaciones listadas.
Me parece importante remarcar que las aplicaciones que vienen "de fábrica" como GMail, seguirán instaladas incluso si utilizamos la Whitelist y no la incluímos. La única forma que logré de remover estas aplicaciones es listándolas explícitamente en la Blacklist.
El único aspecto negativo que encontré en esta funcionalidad, es que el sistema permite al usuario llegar hasta el último paso de la instalación antes de decirle "está prohibido instalar la aplicación". Estaría bueno que no lo permitiera de entrada.

Otro aspecto que en nuestro caso era importante, es dónde está instalado el servidor. Para empezar, AirWatch vende el servicio en la nube, es decir, está el servidor instalado en algún lugar del ciberespacio, y nosotros lo administramos remotamente. Consultando con un agente oficial, me indicaron que también es posible instalar el sistema on-site, es decir, tener nuestro propio servidor AirWatch. El requerimiento para hacerlo es contratar por lo menos 100 licencias, donde las licencias son por dispositivos... para hacer una prueba inicial no resulta muy atractivo tener que contratar 100 licencias. Como dato extra, cada licencia cuesta entre 5 y 6 dólares mensuales. No estoy para nada a favor de colocar datos de la empresa en la nube, mucho menos información de dispositivos, que incluye localización, registro de llamadas, mensajes, etc, pero en este caso, no tuvimos opción.

Algo que todavía no mencioné es que la interfaz de administración es Web. La interfaz es, a mi gusto, poco intuitiva y confusa. No es fácil familiarizarse con ella, e incluso luego de utilizarla un par de semanas, a veces no encuentro las cosas. Tal vez sea una mera impresión mia, pero conversando con mis compañeros de trabajo, les dio la misma sensación. La primer gran confución es la diferencia entre los usuarios. Existen dos tipos de usuarios, los administradores, y los "dueños" de dispositivos. Los primeros tienen permiso de administrar la aplicación, y mediante roles se puede restringir lo que pueden hacer. Los segundos se utilizan para registrar equipos. Es decir, cuando damos de alta un equipo, éste debe pertenecer a algún usuario, por lo que antes habrá que dar de alta el usuario. Cada uno de estos usuarios puede tener asignado uno o más equipos.
Otra confusión es dónde estamos parados. Como cada dispositivo pertenece a una unidad organizativa, si estamos en una de ellas, no podremos ver los dispositivos en otras (salvo que estemos en la OU raíz, donde podemos ver todos). Lo mismo sucede con los usuarios.
Un problema que encontré aquí es mover dispositivos o usuarios entre unidades organizativas... simplemente, no pude. Si el usuario tiene asignado un dispositivo que se encuentra en una dada OU, no se puede mover el usuario, y lo mismo pasa con los dispositivos.
Más allá de ser confusa, tengo para decir a favor de la interfaz que genera interesantes gráficos. El dashboard es muy visual y La información sobre los dispositivos es muy completa.

Un aspecto muy negativo, es la falta de documentación. Googlear cómo hacer algo en AirWatch es prácticamente inútil. Sólo se encuentran broshures de propaganda. Incluso en la página de AirWatch no encontrás los manuales! Tienen una sección de whitepapers que no me resultó muy útil. Por suerte, nuestro proveedor de AirWatch en la nube brindó un buen soporte a nuestras consultas, y nos entregaron un manual oficial... aunque este manual oficial no era muy completo tampoco. En fin, punto negativo en cuanto a documentación.


Agente AirWatch

La administración de los dispositivos se logra instalando un agente AirWatch en cada uno de ellos. Este agente se instala desde la Play Store de Google (en el caso de Android), y se registra en el servidor utilizando una URL de enrollment (que no es la misma que la de administración), el ID de la unidad organizativa, y un usuario del tipo "dispositivo" (no uno de administración).

Para la instalación encuentro como punto negativo que no se provea una APK. El uso de la Play Store de Google fuerza registrar una cuenta Google. Para un dispositivo corporativo, por qué la empresa debería tener cuentas Google? Parte del monopolio encubierto de Google.

Una vez que se registra el dispositivo, se aplica la configuración indicada por el servidor, la cual incluye el perfil del usuario. Acá encontré otro problema. Si bien el agente provee una forma de forzar la sincronización, la aplicación del perfil no siempre funciona. Tuve muchísimos problemas creando bookmarks desde el servidor. A veces el agente los borra, no se actualizan...

Encontré riesgoso el echo de no poder prohibir la desactivación del agente. Si bien encontré la forma de que el usuario no desinstale el agente (Required list, descripta anteriormente), no encontré la forma de que el usuario no desactive el agente. Una vez que se desactiva el agente, el usuario sigue teniendo las restricciones, pero el servidor pierde el contacto con el dispositivo.


Conclusión

AirWatch es un muy buen comienzo en la administración centralizada de dispositivos móviles. Sin embargo, creo que odavía tienen mucho camino por delante. Tal vez dependa mucho de la evolución de los sistemas operativos como Android, que seguramente tomarán un approuch cada vez más empresarial, como BB.
No utilizaría AirWatch como reemplazo de BES. Si tienen dispositivos BB, debido al limitado soporte, recomiendo seguir utilizando BES por más feo que sea :P

Como puntos a favor encuentro que:

  •  Implementa las características más importantes de un MDM.
  •  Brinda muy buena información sobre los dispositivos.
  •  Permite una buena restricción de las aplicaciones instaladas y que no se pueden desinstalar.
  •  Soporte de múltiples sistemas operativos.
  •  Aplicación de políticas por grupos de usuarios y/o dispositivos. Facilidad de generar perfiles y aplicar uno o más a cada dispositivo.

En contra, encuentro que:

  • La interfaz web de administración no es muy intuitiva.
  • La documentación casi inexistente, ya que una vez que nos vamos adentrando en el software y queremos hilar más fino, no se encuentra información al respecto.
  • El soporte de Android es principalmente para equipos SAFE, y salvo iOS, las características disponibles para el resto de los SOs es muy limitada.
  • El agente falla cada tanto y no aplica correctamente los perfiles.
  • Se requieren demasiadas licencias (al menos 100) para poder instalar el servicio on-site.
Thunderbird/Icedove como cliente de Mails, Calendario y Libreta de direcciones de Exchange
El conjunto de herramientas de Exchange utilizado en entornos Windows, es muy importante en la mayoría de las empresas. Para tener una Workstation GNU/Linux totalmente funcional con respecto a sus pares Windows, necesitamos integrar tanto mails, como calendario y libreta de direcciones, provistos por Exchange.

Por suerte, existe una excelente herramienta denominada DavMail que nos facilita la vida. Esta herramienta actúa de proxy entre el cliente de mails que utilicemos (Thunderbird/Icedoe, Evolution, etc) y el servidor Exchange. Para ello, transforma protocolos estándar (IMAP, SMTP, LDAP, etc) en solicitudes WebDav (extensión del protocolo HTTP) reconocidas por el servicio OWA de Exchange.

A continuación explicaré cómo utilizar Thunderbird/Icedove como cliente de mails, calendario y libreta de direcciones provistos por Exchange, a través de DavMail. La explicación está basada en los tutoriales provistos en la propia página de DavMail: IMAP Thunderbird mail setup, Thunderbird calendar setup y Thunderbird directory setup. Además, la explicación está basada sobre debian GNU/Linux, pero la configuración es igual para otras distribuciones e incluso Windows.


Instalación y configuración de DavMail

Descargar DavMail desde su página en sourceforge. Dirigirse al directorio de instalación y ejecutar:
  #dpkg -i davmail.deb # ver nombre real del paquete descargado, o renombrarlo
  #apt-get -f install  # instala todas las dependencias necesarias para davmail

DavMail también está disponible para Windows y Mac, dado que está programado en Java.

Una vez instalado DavMail, debe ejecutarse. Lo recomendable es linkearlo para que se ejecute al inicio de sesión del usuario, así no tenemos que ejecutarlo a mano cada vez que queramos usarlo. En la primera ejecución hay que configurarlo para que apunte al servidor Exchange.
Los campos requeridos, como base, se encuentran en la pestaña Main, y son los siguientes:
  - Exchange Protocol: elegir EWS para Exchange 2010, o WebDav para Exchange 2007.
  - OWA (Exchange URL): la URL del servidor Exchange, por ejemplo: https://miserver.com/OWA
  - Elejir los puertos locales en los que DavMail debe esperar conexiones POP, IMAP, SMTP, Caldav y LDAP. El cliente que configuremos, se conectará a estos puertos. Pueden dejar los puertos default y desactivar alguno de los protocolos si no son necesarios (POP por ejemplo).


Configuración de la cuenta de mail en Thunderbird/Icedove

Los pasos para configurar la cuenta de dominio en el cliente Thunderbird, son los siguientes:
  1- Elegir Crear una nueva cuenta de e-mail. Dependiendo de si ya tienen configurada alguna cuenta o no, los pasos pueden variar un poco al principio.
      - Si no tienen una cuenta definida, el wizzard busca para crear una cuenta con alguno de sus partners. Si este es el caso, clickear el botón "Saltear esto y usar mi correo existente".
      - Si ya tienen otra cuenta en Thunderbird, ir a "Editar -> Configuración de Cuentas...", elegir "Acciones de cuenta -> Agregar una cuenta de correo".
  2- Completar el campo de Nombre y dirección de mail en la configuración de la cuenta. Aconsejo destildar la opción recordar contraseña. Dar continuar.

  3- Al dar continuar, Thunderbird fallará en la comprobación del dominio y les solicitará que ingresen los datos manualmente. En la configuración, utilizar los siguientes valores:
Entrante/Incoming:
IMAP (pueden elegir POP, pero IMAP es más completo)
Nombre del servidor: localhost
Puerto: el puerto que configuraron en DavMail. Por defecto es 1143.
SSL: Ninguno.
Autenticación: Contraseña normal.
Saliente/Outgoing:
Nombre del servidor: localhost
Puerto: el puerto que configuraron en DavMail para SMTP. Por defecto 1025.
SSL: Ninguno.
Autenticación: Contraseña normal.
  Nombre de usuario: su nombre de usuario en el dominio, agregando el nombre del dominio: DOMINIO\usuario

Tal vez DavMail indique que el servidor Exchange provee un certificado no confiable. Esto sucede si el servidor utiliza un certificado autofirmado, o firmado por una CA local. Dar aceptar.


Calendario

Para configurar el calendario, debemos instalar la extensión iceowl-extension (extensión Lightning):
  # apt-get install iceowl-extension

Luego, desde Thunderbird/Icedove abrimos el calendario "Events and Tasks -> Calendar", y realizamos los siguientes pasos:
  1- Click derecho en el panel izquierdo (deben tener el predefinido calendario "Home") y clickeamos "New Calendar".
  2- En la ventana que aparece, elegimos "On the Network", luego "Siguiente".

  3- En la siguiente opción elegimos "CalDAV" en el formato, y utilizamos la siguiente dirección para el "Location": http://localhost:1080/users/usuario@dominio.com/calendar, donde deberán reemplazar usuario@dominio.com por su usuario y dominio.

  4- Una vez que terminen, les solicitará un nombre de calendario, color, si desean recibir recordatorios. Configurar a gusto.
  5- Para finalizar, les solicitará las credenciales de dominio. Hay que colocarlas como en el caso de los mails, utilizando el formato DOMINIO\usuario para el nombre de usuario.


Libreta global de direcciones

La libreta global de direcciones de Exchange se habilita de la siguiente manera:

  1- Ir a "Herramientas -> Libreta de direcciones".
  2- En la ventana emergente, seleccionar "Archivo -> Nuevo -> Directorio LDAP".
  3- Abre una nueva ventana, donde se nos solicitan los siguientes datos:
      Nombre: el que ustedes elijan (ej: Exchange)
      Servidor: localhost
      DN base: ou=people
      Número de puerto: 1389 (ver la configuración de DavMail para saber qué puerto usar con LDAP, por defecto es 1389)
      DN para inicio de sesión: DOMINIO\usuario

Para que Icedove autocomplete con las direcciones de la libreta global, hay que ir a "Editar -> Preferencias -> solapa Redacción". Allí tildar la opción "Servidor de directorios" y elegir la libreta recién creada.

Cuando vayan a redactar un e-mail, Thunderbird/Icedove les solicitará la contraseña del usuario de dominio.