Vamos a suponer por un momento que estamos en un entorno en el que no podemos tener un servidor PXE en condiciones. Para ponerlo todavía peor, imaginemos que somos de los que no conseguimos encontrar un cd de Knoppix razonablemente reciente cada vez que se nos fastidia un servidor y que, a pesar de las prisas, tenemos que esperar a ver como carga todo un entorno gráfico antes de poder hacer un simple fsck.

Todo esto tendría que ser parte del pasado, al estilo de los videos Beta o los cassettes; pero no, todavía es demasiado común, así que a ver si conseguimos dar algunas ideas útiles y buscamos alternativas que, aunque no sean lo más moderno que existe, nos faciliten un poco el trabajo.

No esperéis nada original en este post. Todo lo que escribo aquí está ya más que documentado, y mucho mejor que en estas cuatro notas. Aún así, a ver si os sirve como punto de partida.

Queremos conseguir dos cosa:

  • Un método para arrancar rápidamente una distribución live, sencilla, que permita recuperar particiones, transferir ficheros, ..., este tipo de cosas.
  • Un sistema para instalar distribuciones de forma automática, usando ficheros kickstart para RedHat/CentOS/... y preseed para Debian/Ubuntu/..., pero teniendo en cuenta que no podemos usar PXE, ni Cobbler, ni nada similar.

Ya hace mucho tiempo que se pueden arrancar sistemas desde memorias USB, y además GRUB tiene funcionalidades que permiten arrancar desde una ISO. Siendo esto así, ya tenemos todo lo necesario. Si incluimos un servidor web para guardar nuestros ficheros ks y preseed y, si queremos acelerar un poco las instalaciones, los paquetes de las distribuciones CentOS y Debian (las que voy a usar en este post), conseguiremos además que las instalaciones sean automáticas y razonablemente dinámicas.

Pasos previos

Empezamos. Buscad un pendrive que no uséis para nada y podáis formatear. Desde este momento asumo que el dispositivo que estáis usando corresponde a /dev/sde, y que tiene una única partición fat normal y corriente, en /dev/sde1. Si no es así, lo de siempre: "fdisk /dev/sde" + "mkfs.vfat -n USB_INSTALACIONES /dev/sde1":

#fdisk -l /dev/sde
Disposit. Inicio    Comienzo      Fin      Bloques  Id  Sistema
/dev/sde1   *        2048    15654847     7826400    c  W95 FAT32 (LBA)

Una vez más, aseguráos de que podéis y queréis borrar el contenido del pendrive. Por supuesto, vuestro kernel debe tener soporte para este tipo de particiones, y también necesitáis las utilidades para gestionarlas. En Debian están en el paquete "dosfstools". En cualquier caso, lo normal es que ya lo tengáis todo.

Vamos a montar la partición en "mnt" (o donde queráis), con un simple:

mount /dev/sde1 /mnt

Lo siguiente es instalar grub en /dev/sde. Otra vez, cuidado con lo que hacéis, no os confudáis de dispositivo.

grub-install --no-floppy --root-directory=/mnt /dev/sde

Dejamos estos pasos básicos y vamos ya a por la configuración más específica.

Creando el menú

En realidad, no vamos a hacer nada más que configurar GRUB. Podéis ser todo lo creativos que queráis, pero para este ejemplo voy a simplificar todo lo que pueda: Ni colores, ni imágenes de fondo, ni nada de nada.

Para tener un poco de variedad, desde mi USB se va a poder arrancar lo siguiente:

  • Una Debian Wheezy sin preseed, para ir configurando a mano.
  • Un CD con utilidades de repación. El que más os guste. Para este ejemplo: Ultimate BootCD.
  • Una Slax, por si quisiera arrancar un entorno gráfico completo.
  • Una Debian Wheezy con preseed, completamente automática.
  • Una CentOS 6.4 con kickstart, completamente automática.

Prestad atención a los dos últimos elementos de la lista, porque son los que nos van a permitir ir a un servidor "vacío", arrancar desde el USB, y en 5 minutos tener una Debian o una CentOS perfectamente instalados.

Vamos a crear el menú de GRUB. Necesitamos editar el fichero "/mnt/boot/grub/grub.cfg" con lo siguiente:

menuentry "Debian Wheezy x86_64 installer" {
        set gfxpayload=800x600
        set isofile="/boot/iso/wheezy_mini_amd64.iso"
        loopback loop $isofile
        linux (loop)/linux priority=low initrd=/initrd.gz
        initrd (loop)/initrd.gz
}

menuentry "Ultimate BootCD 5.2.5" {
        loopback loop /boot/iso/ubcd525.iso
        linux (loop)/pmagic/bzImage edd=off load_ramdisk=1 prompt_ramdisk=0 rw loglevel=9 max_loop=256 vmalloc=384MiB keymap=es es_ES iso_filename=/boot/iso/ubcd525.iso --
        initrd (loop)/pmagic/initrd.img
}

menuentry "Slax Spanish 7.0.8 x86_64" {
        set isofile="/boot/iso/slax-Spanish-7.0.8-x86_64.iso"
        loopback loop $isofile
        linux (loop)/slax/boot/vmlinuz load_ramdisk=1 prompt_ramdisk=0 rw printk.time=0 slax.flags=toram from=$isofile
        initrd (loop)/slax/boot/initrfs.img
}

menuentry "Debian Wheezy x86_64 preseed" {
        set isofile="/boot/iso/wheezy_mini_amd64.iso"
        loopback loop $isofile
        linux (loop)/linux auto=true preseed/url=http://192.168.10.40/instalaciones/wheezy_preseed_131.cfg debian-installer/country=ES debian-installer/language=es debian-installer/keymap=es debian-installer/locale=es_ES.UTF-8 keyboard-configuration/xkb-keymap=es console-keymaps-at/keymap=es debconf/priority=critical netcfg/disable_dhcp=true netcfg/get_ipaddress=192.168.10.131 netcfg/get_netmask=255.255.255.0 netcfg/get_gateway=192.168.10.1 netcfg/get_nameservers=192.168.10.1 --
        initrd (loop)/initrd.gz
}

menuentry "Centos 6.4 x86_64 kickstart" {
        set isofile="/boot/iso/CentOS-6.4-x86_64-netinstall.iso"
        loopback loop $isofile
        linux (loop)/images/pxeboot/vmlinuz ip=192.168.10.131 noipv6 netmask=255.255.255.0 gateway=192.168.10.1 dns=192.168.10.1 hostname=fn131.forondarena.net ks=http://192.168.10.40/instalaciones/ks_rh6_131.ks lang=es_ES keymap=es
        initrd (loop)/images/pxeboot/initrd.img
}

menuentry "Restart" {
        reboot
}

menuentry "Shut Down" {
        halt
}

Suficiente, no necesitamos nada más. Repasemos un poco:

  • Las entradas del menú se separan en bloques "menuentry", uno para cada instalación diferente, e incluyendo las dos últimas para reiniciar y para apagar el equipo (no son muy útiles pero sirven de ejemplo).
  • Como no me gusta escribir demasiado, he definido la variable, "isofile" con la imagen que va a usar GRUB para arrancar (ahora hablamos sobre esto) en cada bloque.
  • Vamos a usar imágenes ISO "normales", y GRUB va a asumir que son la raíz de la instalación.
  • Como sabéis, cuando queremos arrancar un sistema, es habitual especificar un kernel en una línea que empieza con "linux", las opciones que queremos usar con este núcleo, y un initrd. Aquí estamos haciendo exactamente esto, pero debemos especificar la ruta dentro de la imagen ISO donde encontrar el kernel y el initrd. Lo más fácil es que montéis la imagen y veáis dónde está cada uno.
  • Las dos instalaciones automáticas usan más opciones que el resto. Lo que estamos haciendo es pasar el fichero de configuración (preseed o kickstart) que el sistema leerá vía http, y luego una serie de opciones básicas (idioma, teclado, ...). Ni todas son necesarias, ni son todas las que se pueden poner.
  • Como hemos dicho que no queremos usar PXE, asumo que tampoco queremos usar DHCP, así que las configuraciones de red son estáticas.
  • La IP que asignamos al servidor en esta fase de instalación no tiene que ser necesariamente la misma que instalaremos en el servidor, aunque en este ejemplo asumo que será así.

Fácil, ¿Verdad? El siguiente paso es descargar las imágenes que estamos usando en cada bloque (opción "isofile"). Tened en cuenta que no hay nada raro en estas ISO. Son las imágenes estándar de las distribuciones, aunque las he renombrado para que todo quede más ordenado. Para guardarlas he creado un directorio "/mnt/boot/iso/", y he copiado ahí los siguientes ficheros:

  • Para Debian, con y sin preseed: mini.iso (renombrada a wheezy_mini_amd64.iso).
  • Para CentOS: netinstall.iso.
  • Para Ultimate BootCD: ubcd.
  • Para Slax: slax.

Cuando lo tengáis todo, desmontad /mnt, y ya habremos terminado con el pendrive. Quedan los preseed/kickstart.

Ficheros kickstart y preseed

Si os fijáis en el menú de GRUB, para Debian estamos usando una referencia al fichero wheezy_preseed_131.cfg. Este fichero no es más que un preseed normal y corriente que, obviamente, está preparado para la instalación que queremos hacer. Los ficheros preseed consisten en escribir todas las respuestas a todas las opciones de menú que pueden aparecer en el instalador. Esto hace que el sistema sea muy flexible, pero también muy denso. Si queréis ver un listado con todas las opciones disponibles, id a una máquina Debian y ejecutad lo siguiente:

debconf-get-selections --installer >> wheezy_preseed_131.cfg
debconf-get-selections >> wheezy_preseed_131.cfg

Ahí tenéis, todo un "wheezy_preseed_131.cfg"; una locura. Afortunadamente, no siempre hacen falta todas las opciones. De hecho, yo en mis instalaciones para KVM uso esta versión, mucho más reducida. Claro, esto implica que si os sale un diálogo durante la instalación para el que no hemos previsto una respuesta, quizá porque vuestro hardware pida "algo" extra, la instalación automática se va a parar. En este caso tendréis que buscar la opción que os falta y añadirla.

Os recomiendo que abráis el fichero y que le déis una vuelta. Tened en cuenta que está pensado para instalaciones sobre KVM y los drivers virtio, así que el dispositivo de disco que se usa es /dev/vda. Además, uso sólo un interfaz de red, eth0. En cuanto al particionado, uso una partición para boot, primaria y de 50MB, otra para swap de unos 512MB, y por último, el resto del disco, en un grupo LVM para la raíz.

Además de esto, suelo usar un mirror local de Debian para agilizar la primera instalación. No es necesario, podéis usar un mirror público y, con ello, simplificar aún más la infraestructura. Bueno, "simplificar" por decir algo, porque no se puede decir que copiar el contenido de los DVD de instalación de Debian en un servidor web sea complicado.

Aunque el sistema sea diferente, en realidad todo esto que he dicho para Debian se aplica igual para kickstart y las instalaciones automatizadas de CentOS. Revisad si queréis este ejemplo, subidlo a un servidor web, y adaptadlo a lo que os haga falta. Tened en cuenta que también suelo usar un mirror local en estos casos (otra vez, se trata sencillamente de descomprimir los DVD).

Pruebas en KVM

Una vez instalado GRUB, con el menú y las ISO copiadas en el pendrive, podemos probar el nuevo sistema en KVM. Es muy sencillo. De hecho, si usáis virt-manager, casi no tendréis que hacer nada. Una vez creada la máquina virtual, id a los detalles y pulsad sobre "agregar nuevo hardware". Después no tenéis más que elegir "usb host device" y, de la lista, la memoria USB. Una vez agregado el dispositivo, en el arranque de la máquina virtual, justo al principio, aparecerá una opción para acceder al menú de arranque pulsando F12. Entre las opciones que os van a aparecer debería estar el pendrive.

Por último, esto es lo que veréis si todo ha ido bien:

Pantallazo GRUB

Y ya está, con esto hemos terminado. Romped vuestros CDs!!

Nota

En el post hablo sobre un servidor web, pero luego no escribo nada más sobre ello. No hay demasiado que decir; en mi caso uso un directorio "instalaciones", y en ese directorio pongo los preseed/ks. Junto a esto, debajo de "instalaciones", creo un directorio "centos/6.4-x86_64" y otro "debian/wheezy" y copio ahí el contenido de los DVD de cada distribución.


Enrutamiento para dummies

jue, 27 sep 2012 by Foron

Si hay algo sobre lo que no hubiese querido escribir nunca en este blog es sobre el enrutamiento básico en Linux. Hace 10 años quizá hubiese sido más interesante, pero no ahora. Aún así, en este mundo del botón y del siguiente siguiente no tengo nada claro que la gente sepa exactamente lo que hay debajo de un "route -n", así que vamos a ello. Eso sí, para dummies. De hecho, me voy a pasar de básico, con lo que escribiré cosas que en condiciones normales merecerían una discusión. En fin.

Empezamos con el viejo comando "route -n", tan simple como siempre:

  # route -n
  Kernel IP routing table
  Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
  0.0.0.0         10.213.208.1    0.0.0.0         UG    0      0        0 eth0
  10.213.208.0    0.0.0.0         255.255.240.0   U     0      0        0 eth0
  192.168.10.0    0.0.0.0         255.255.255.0   U     0      0        0 eth1
  192.168.11.0    0.0.0.0         255.255.255.0   U     0      0        0 eth1

Lo normal cuando usamos la combinación route/ifconfig en la mayoría de las distribuciones, una puerta de enlace, una red para llegar a esa puerta de enlace a través de un dispositivo, y luego, en este caso, nuestras redes locales.

¿No hay nada más?

Pues sí, hay mucho más, claro. Esto no es más que una mínima porción de la tabla de enrutamiento de vuestro kernel favorito. Vamos a verlo pasando a la utilidad "ip", la pobre, que lleva tanto tiempo existiendo, pero que tan poco se usa (solo comparable al uso eterno de los aliases de IPs, pero esta es otra historia).

  # ip route ls
  default via 10.213.208.1 dev eth0
  10.213.208.0/20 dev eth0  proto kernel  scope link  src 10.213.218.162
  192.168.10.0/24 dev eth1  proto kernel  scope link  src 192.168.10.1
  192.168.11.0/24 dev eth1  proto kernel  scope link  src 192.168.11.1

¿Todo este rollo para ver lo mismo con un formato diferente?

Sí, porque estamos viendo, una vez más, solo una parte de la tabla de enrutamiento. ¡Hola "ip rule"!

  # ip rule ls
  0:      from all lookup local
  32766:  from all lookup main
  32767:  from all lookup default

Y ahora, sorpresa:

  # ip route ls table default
  _vacio_
  # ip route ls table main
  default via 10.213.208.1 dev eth0
  10.213.208.0/20 dev eth0  proto kernel  scope link  src 10.213.218.162
  192.168.10.0/24 dev eth1  proto kernel  scope link  src 192.168.10.1
  192.168.11.0/24 dev eth1  proto kernel  scope link  src 192.168.11.1
  # ip route ls table local
  broadcast 10.213.208.0 dev eth0  proto kernel  scope link  src 10.213.218.162
  local 10.213.218.162 dev eth0  proto kernel  scope host  src 10.213.218.162
  broadcast 10.213.223.255 dev eth0  proto kernel  scope link  src 10.213.218.162
  broadcast 127.0.0.0 dev lo  proto kernel  scope link  src 127.0.0.1
  local 127.0.0.0/8 dev lo  proto kernel  scope host  src 127.0.0.1
  local 127.0.0.1 dev lo  proto kernel  scope host  src 127.0.0.1
  broadcast 127.255.255.255 dev lo  proto kernel  scope link  src 127.0.0.1
  broadcast 192.168.10.0 dev eth1  proto kernel  scope link  src 192.168.10.1
  local 192.168.10.1 dev eth1  proto kernel  scope host  src 192.168.10.1
  broadcast 192.168.10.255 dev eth1  proto kernel  scope link  src 192.168.10.1
  broadcast 192.168.11.0 dev eth1  proto kernel  scope link  src 192.168.11.1
  local 192.168.11.1 dev eth1  proto kernel  scope host  src 192.168.11.1
  broadcast 192.168.11.255 dev eth1  proto kernel  scope link  src 192.168.11.1

Vaya sorpresa... Lo que vemos con un "route -n" es en realidad la tabla "main", que además, por ese 32766, parece tener menos prioridad que esa tabla "local" tan curiosa. Fácil de leer, ¿Verdad? Los broadcast los conocemos todos pero, ¿Y las rutas de tipo local? Sacado literalmente del manual (Sí, todo esto está en el manual de ip route!!!): "the destinations are assigned to this host. The packets are looped back and delivered locally". Y con esto hemos terminado, solo nos queda saber que "proto kernel" es la configuración más normal si no usamos software de enrutamiento (quagga, por ejemplo), y que "scope link" es para rutas broadcast y unicast mientras que "scope host" es para las locales.

Revisad los manuales de "ip rule" e "ip route", por favor, y entended cada entrada de estas tablas y reglas.

Ya que estamos, vamos a jugar un poco con todo esto que hemos visto, aunque seguimos en "modo sencillo" y no nos vamos a complicar demasiado. Solo unas ideas.

Nota: No hagáis caso a las IPs que uso a partir de aquí. Intentaré mantenerlas coherentes, pero me las estoy inventando sobre la marcha.

Tenemos reglas en "ip rule", tenemos tablas de enrutamiento, ... ¿Apostamos a que todo esto es modificable/configurable? ¡Por supuesto!

Vamos a vuestra distribución Debian favorita, y busquemos el fichero "/etc/iproute2/rt_tables"

  # cat /etc/iproute2/rt_tables
  #
  # reserved values
  #
  255     local
  254     main
  253     default
  0       unspec
  #
  # local
  #
  #1      inr.ruhep

  1001 proveedor1
  1002 proveedor2

No, en vuestros ficheros no van a estar las líneas "proveedor1" y "proveedor2". Las he añadido yo, alegremente. Donde estoy escribiendo este post no hay múltiples lineas de acceso a Internet, pero me voy a inventar, como ejemplo, que en mi equipo hay dos ADSL de proveedores diferentes. Uno me ha asignado la IP 192.0.2.33/24, con puerta de enlace 192.0.2.1, y el segundo 198.51.100.33/24, con gateway 198.51.100.1.

Como ya sabemos todo sobre enrutamiento, queremos mandar a los comerciales a través del primer proveedor, y a los técnicos a través del segundo. Supongamos que los comerciales están todos en la subred "172.16.0.0/24", y los técnicos en "172.16.1.0/24".

¡Juguemos con ip rule!

  # ip rule add from 172.16.0.0/24 table proveedor1
  # ip rule add from 172.16.1.0/24 table proveedor2
  # ip rule ls
  0:      from all lookup local
  32764:  from 172.16.1.0/24 lookup proveedor2
  32765:  from 172.16.0.0/24 lookup proveedor1
  32766:  from all lookup main
  32767:  from all lookup default

Efectivamente, hemos separado el tráfico en dos tablas, por ahora vacías. Es el turno de ip route:

  # ip route add 172.16.0.0/16 dev eth0 src 172.16.0.1 table proveedor1
  # ip route add 192.0.2.0/24 dev eth1 src 192.0.2.33 table proveedor1
  # ip route add default via 192.0.2.1 table proveedor1

  # ip route add 172.16.0.0/16 dev eth0 src 172.16.0.1 table proveedor2
  # ip route add 198.51.100.0/24 dev eth2 src 198.51.100.33 table proveedor2
  # ip route add default via 198.51.100.1 table proveedor2

¿Veis lo que hemos hecho? Hemos asignado dos puertas de enlace por defecto diferentes, en base al origen, por lo que todo lo que venga desde 172.16.0.0/24 irá por 192.0.2.1, y lo originado en 172.16.1.0/24 por 198.51.100.1. Por si fuera poco, el tráfico que no entre en ninguna de estas dos subredes accederá a la tabla main (from all lookup main), y con ello usará la puerta de enlace por defecto de toda la vida, esa que pensábamos que era única.

Y ya está, aquí lo dejo, aunque debo recordaros que esto no es una guía tipo copy/paste, ni remotamente.

Notas:

  • Como no podía ser de otra forma, el direccionamiento interno de este post no puede salir directamente a Internet. Hay que hacer SNAT (o el masquerade de toda la vida) en iptables. Lo normal es que conntrack haga magia y sepa dónde mandar vuestro tráfico, pero también es posible que se tenga que jugar más a fondo con ello.
  • Las entradas que añaden rutas a las tablas están simplificadas. Sería deseable completarlas, por ejemplo para evitar avisos de ICMP sobre mejores rutas si quisiésemos mandar tráfico entre redes. Lo dicho, no es más que un ejemplo.
  • En función de las configuraciones, interfaces, firewalls, ... puede ser posible que se tenga que cambiar algún parámetro de kernel, como por ejemplo, lo relacionado con rp_filter (Google es vuestro amigo). Por supuesto, ni que decir ip_forward.
  • Una vez más, esto es un mínimo ejemplo de lo mucho que se puede hacer. Las reglas, sin ir más lejos, pueden definirse por IP origen, destino, o incluso por marca de firewall, con lo que las posibilidades son enormes.
  • Os recomiendo completar lo visto en este post con todo lo que hay alrededor del enrutamiento en un kernel normal, con soporte Netfilter. Pista.
read more

rsyslog y zeroMQ

dom, 06 nov 2011 by Foron

Con la evolución de los sistemas actuales, cada vez más grandes, con más elementos interconectados, con más necesidades de comunicación; con el cloud computing, las nubes, nieblas y demás parafernalia, llevamos ya tiempo viendo como las diferentes implementaciones de Advanced Message Queuing Protocol, como RabbitMQ, van ganando más y más seguidores.

Desde el punto de vista de la administración de sistemas, entornos como Nova (parte de Openstack) hacen uso de este protocolo, y de RabbitMQ más concretamente, para la comunicación entre sus múltiples componentes.

Hace unos años, y en desacuerdo con la evolución de AMQP, la gente de Imatix (ojo, es una de las empresas responsables de su implementación original), decidió apartarse de este protocolo y desarrollar una pequeña librería, zeroMQ, con la que se pudieran escribir aplicaciones con capacidades de comunicación avanzadas, pero de una manera sencilla; de tal manera que en lugar de un elemento más bien central en la infraestructura (AMQP), se tuviera un modelo más distribuido, en el que la mayoría del trabajo se delegase a los extremos (las aplicaciones), y no a ese nodo central. Usando una analogía de un documento de introducción a zeroMQ, estamos hablando de pasar de subversion (AMQP) a git (zeroMQ).

Bien, ahora que ya tengo vuestro interés (¿Verdad?), vamos a olvidarnos de la literatura y a buscar formas de aprovechar lo que zeroMQ puede ofrecernos para el trabajo diario del administrador de sistemas. Recordemos que al hablar de este tipo de tecnologías nos referimos al paso de mensajes entre procesos, a su gestión, a su enrutamiento, .... Y quien dice mensajes, cómo no, puede estar diciendo logs.

Al hablar de logs, por otro lado, hablamos de syslog, de servidores remotos a los que enviar datos, de ficheros que abrir y procesar, de plantillas para bases de datos en las que escribir los logs directamente, y quizá de sistemas más avanzados, como flume. ¿Sería posible usar lo bueno que tienen estas aplicaciones, y mejorarlas con lo que ofrece zeroMQ, sin volvernos locos en el intento?

Algo parecido debieron pensar en Aggregate Knowledge. Sencillamente, han escrito un par de plugins (de entrada y de salida) para rsyslog, de tal manera que podamos usar mucho de lo que nos ofrece zeroMQ (centrándome en el plugin de salida hablamos de sockets pub, push y rep básicamente) desde aplicaciones externas.

Dicho de otra forma, en una frase, podremos conectarnos en cualquier momento al rsyslog de un servidor, y ver lo que está logueando desde una aplicación que nosotros mismos habremos escrito, ya sea con Python, Perl, C, node.js, PHP, ... o con cualquiera de los muchos lenguajes para los que hay un "binding". De esta manera, podremos procesar datos en tiempo real o analizar lo que está pasando en una máquina, sin tener que acceder a ella y sin tener que ejecutar ningún tipo de software adicional en el servidor.

Antes de seguir, si queréis probarlo vosotros mismos, aquí os he dejado un backport de rsyslog 5.8.5 con soporte para zeromq para Debian Squeeze, y otro .deb de la misma versión de rsyslog para Debian Testing. En cualquier caso, recordad que son paquetes que se han creado en el momento en que se ha escrito este post. Por lo tanto, y más para Debian Testing, es muy posible que las versiones hayan cambiado cuando instaléis los paquetes, así que cuidado, bajo vuestra responsabilidad.

Aclarado esto, sigamos con el ejemplo.

Recordad que zeroMQ es una librería, y que como tal puede usarse en cualquier aplicación, sin límites. Accederemos a los logs de rsyslog (me estoy centrando en el plugin de salida) a través del socket adecuado, y a partir de ahí es cosa nuestra. En todo caso, hagamos lo que hagamos, probablemente podremos catalogar nuestra aplicación en uno de estos dos grandes grupos (es una simplificación, obviamente):

  1. Monitorización: A menudo sólo queremos ver lo que está pasando en un servidor. No necesitamos procesar los mensajes, nos vale con hacer un seguimiento de la actividad de una máquina. Cuando se configura rsyslog para usar sockets tipo "pub", por ejemplo, estamos creando una especie de feed de datos. Todos los clientes que se conecten a ese socket (probablemente de tipo "sub") recibirán los mismos datos, como si fuera una emisión de radio. Además, no necesitamos guardar los mensajes si no hay nadie escuchando, podemos descartarlos.
  2. Tratamiento: Los sockets tipo "push" balancean la carga automáticamente entre todos los clientes conectados (probablemente de tipo "pull"). Esto es perfecto cuando queremos procesar, por ejemplo, los logs de correo de un servidor. En un primer momento podríamos empezar con un único cliente escuchando y procesando los datos del servidor. Si en algún momento esta aplicación no pudiera con el volumen de logs, sería suficiente con arrancar una segunda instancia, de tal manera que se dividiría el trabajo entre las dos, automáticamente, y siempre que fueran lo suficientemente inteligentes, claro.

Podemos complicarnos mucho más, claro, pero lo dejaremos así por ahora. Como he dicho anteriormente, probablemente haya simplificado demasiado todo lo que puede hacerse, pero creo que estos dos escenarios describen bien el potencial de la librería.

Vamos a ver con un par de ejemplos lo sencillo que es todo esto. Empezamos con nuestro feed de logs.

  # cat /etc/rsyslog.conf
  ...
  *.* :omzeromq:bind=tcp://*:5557,pattern=pub;RSYSLOG_TraditionalFileFormat
  ...
  *.*     -/var/log/syslog
  ...

Los clientes tendrán que conectarse al puerto 5557. Además, como se muestra en este ejemplo, el que usemos zeroMQ no significa que no podamos escribirlos también en otros ficheros.

En Fedora 15, si quisieramos escribir una pequeña aplicación con Python 3.x, sería suficiente con lo siguiente:

  # yum install python3-zmq
  # cat prueba_pub_sub.py
  import zmq
  context = zmq.Context()
  socket = context.socket(zmq.SUB)
  print ("Conectando a servidores…")
  socket.connect ("tcp://servidor1.forondarena.net:5557")
  socket.connect ("tcp://servidor2.forondarena.net:5557")
  print ("Conectados a dos servidores")
  socket.setsockopt(zmq.SUBSCRIBE, b"")
  while 1:
      string = socket.recv()
      print ("Recibimos datos: ",string.decode())

Y, básicamente, esto es todo lo necesario. Es más, en este caso, nos conectamos a dos servidores, "servidor1.forondarena.net" y "servidor2.forondarena.net", con lo que recibiremos todos los mensajes que generen ambos. Con "setsockopt" estamos poniendo un filtro para los mensajes, que en este caso es una cadena vacía, así que no aplicaremos ninguno. Si abrimos 10 terminales y ejecutamos este python en cada una, veremos como se muestran los mismos mensajes en todas ellas.

Con los sockets push/pull la cosa es igual de sencilla:

  # cat /etc/rsyslog.conf
  ...
  *.* :omzeromq:bind=tcp://*:5557,pattern=push;RSYSLOG_TraditionalFileFormat
  ...
  *.*     -/var/log/syslog
  ...

La aplicación cliente tampoco tiene ningún misterio:

  import zmq
  context = zmq.Context()
  socket = context.socket(zmq.PULL)
  print ("Conectando a servidores…")
  socket.connect ("tcp://servidor1.forondarena.net:5557")
  print ("Conectados")
  while 1:
          string = socket.recv()
          print ("Recibimos datos: ",string.decode())

En este caso, nos conectamos sólo a "servidor1". La gran diferencia con el ejemplo anterior es que, si ejecutamos el script en dos terminales, veremos un mensaje en una, y el siguiente en otra (load balance). Este modelo es perfecto, por lo tanto, para los casos en los que las aplicaciones generan una única línea de log independiente por evento. En todo caso, como es nuestra aplicación la responsable del tratamiento de los mensajes, tenemos vía libre para hacer lo que queramos con los logs, ya sea insertándolos en MySQL una vez procesados, o en Hadoop, o en Cassandra, o en Redis, o en MongoDB, o en ....

En definitiva, creo que la unión de zeroMQ con rsyslog es una estupenda idea de la gente de Aggregate Knowledge. Además, el que zeroMQ pueda usarse casi con cualquier lenguaje de programación abre la ventana para que desarrollemos todo tipo de aplicaciones, ya sean en modo texto o gráficas, para consola o para interfaces web.

Notas

Este post es extremadamente superficial. AMQP, zeroMQ o rsyslog dan por sí mismos para páginas y páginas de blogs. Es más, ya las hay, y a montones. Aquí tenéis un puñado de referencias que os pueden servir para profundizar en el tema:

  • Introducción a zeroMQ. Estupenda introducción a zeroMQ. Empezad por aquí.
  • Guía de zeroMQ. Magnífica guía de zeroMQ, con ejemplos y propuestas de patrones.
  • Ejemplo1 de aplicación zeroMQ.
  • Otro ejemplo2 de uno de los patrones propuestos en la guía de zeroMQ, y unos benchmarks de Aggregate Knowledge.
  • Motivos por los que Imatix no está de acuerdo con la evolución de AMQP. Cuidado, es pelín denso.

No he querido incluir en el post los pasos para preparar los .deb de rsyslog. Si os interesa, dejad un comentario o un tweet y escribo algo al respecto.

read more

PXE para instalaciones básicas de CentOS y Debian

jue, 20 oct 2011 by Foron

Otro mini-post que vuelve a salirse completamente de la idea general de este blog. Ya hay mucha documentación sobre PXE y sobre instalaciones automatizadas tanto de CentOS como de Debian, pero bueno, a ver si le es útil a alguien.

En este caso, vamos a montar un sencillo servidor PXE, que podremos usar para hacer instalaciones de CentOS y Debian. Bueno, en realidad, podemos instalar cualquier cosa, pero en mi caso no suelo necesitar nada más.

Todo esto se debe a que, para las pruebas que hago en mi laboratorio, el mínimo de máquinas virtuales que necesito no baja de... tres, y al final tiendo a perder mucho tiempo con las instalaciones, y poco con el trabajo "de verdad". Por eso, nada mejor que PXE, kickstart y debian-installer para agilizar el proceso.

Ojo, que todo esto, aunque perfectamente válido, no es lo que yo usaría, a priori, en un entorno real. Hay mucho software que automatiza lo que vamos a hacer aquí, con un interfaz web, scripts para actualizar repositorios, .... Por eso, creo que aplicaciones como Cobbler (por citar una) deben ser la primera opción.

Aún así, para los que queréis aprender cómo se montan estos "inventos", aquí van unas pinceladas.

El problema

Necesitamos una forma rápida de instalar servidores. En mi caso, máquinas virtuales. Aunque hoy en día la gente de VmWare, RedHat o Virsh+libvirt ofrecen alternativas para clonar instancias como churros, vamos a optar por una solución genérica y mucho más divertida.

Las distribuciones que vamos instalar con este sistema son CentOS y Debian. Para el caso de las basadas en kickstart, la cosa está muy clara, pero para Debian, muchos os preguntaréis ¿Por qué no FAI? Bien, la respuesta rápida, en mi caso, es que no quiero instalar un servidor NFS, y la última vez que miré era un requisito. Como decía, buscad la solución que más os guste.

Otro problema posterior es la configuración "fina" de las instancias. Eso no lo voy a documentar aquí, pero como pista: cfengine, puppet, ....

La solución

He dicho que iba a ir rápido, y ya me estoy enrollando.... Venga, lo primero que necesitamos, como no, es una BIOS que permita instalar los clientes vía PXE. Por supuesto, tanto Qemu/KVM como VmWare lo permiten, así que ni una palabra más al respecto.

En cuanto al software necesario para el servidor de instalaciones, necesitamos un DHCPD razonable (por ejemplo el del ISC), un TFTPD (por ejemplo atftpd) y un servidor web, que en mi caso va a ser apache. Situemos todo en contexto:

  1. La máquina virtual arranca y "habla" con DHCPD.
  2. Además de la información IP, se trasmite, vía DHCP+TFTP, pxelinux.0.
  3. En el caso de Debian, también se aprovecha para dar una pista sobre dónde encontrar el fichero para debian-installer.
  4. Cargamos el menú de arranque con TFTP.
  5. El sistema usa apache para obtener los ficheros ks, debian-installer y los rpm, deb y demás parafernalia.

Empezamos por lo fácil: El contenido web. Tan sencillo como copiar el contenido de los CDs de instalación de las distribuciones a una ruta del arbol web.

  # ls /var/www/instalaciones/centos/6.0-x86_64/
  CentOS_BuildTag  EULA  images    Packages                  repodata              RPM-GPG-KEY-CentOS-Debug-6     RPM-GPG-KEY-CentOS-Testing-6
  EFI              GPL   isolinux  RELEASE-NOTES-en-US.html  RPM-GPG-KEY-CentOS-6  RPM-GPG-KEY-CentOS-Security-6  TRANS.TBL

  # ls /var/www/instalaciones/debian/squeeze/
  autorun.inf  dists  firmware  g2ldr.mbr  install.amd  md5sum.txt  pool         README.mirrors.html  README.source  setup.exe  win32-loader.ini
  css          doc    g2ldr     install    isolinux     pics        README.html  README.mirrors.txt   README.txt     tools

Sigamos con la configuración para DHCP. Hemos dicho que, además de las obvias IPs, también van a ayudar a las Debian con la definición del fichero para debian-installer. Hay tres bloques de configuración que os quiero enseñar:

  # less /etc/dhcp/dhcpd.conf
  ...
  subnet 192.168.10.0 netmask 255.255.255.0 {
     option routers 192.168.10.1;
     option domain-name-servers 192.168.10.1;
     option domain-name "forondarena.net";
     option tftp-server-name "inst.forondarena.net";
     filename "/instalaciones/pxelinux.0";
  }
  host centos1 {
     hardware ethernet 52:54:00:30:38:c6;
     server-name "fn134.forondarena.net";
     fixed-address 192.168.10.134;
  }
  host debian1 {
     hardware ethernet 52:54:00:da:4a:92;
     server-name "fn135.forondarena.net";
     fixed-address 192.168.10.135;
     if substring (option vendor-class-identifier, 0, 3) = "d-i" {
       filename "http://inst.forondarena.net/instalaciones/squeeze_preseed_135.cfg";
          }
  }
  ...

Como veis, para CentOS no trasmitimos nada relacionado con el fichero kickstart, pero para Debian sí. ¿Por qué? Pues para enriquecer un poco el post con algo diferente, la verdad. Hay más de una forma de hacerlo.

Con esto ya tenemos la primera fase del arranque. Lo siguiente: El "boot menu". Y para esto necesitamos TFTP:

  /srv/tftp/instalaciones/
  ├── pxelinux.0
  ├── pxelinux.cfg
  │   ├── C0A80A86
  │   └── C0A80A87
  ├── squeeze-x86_64
  │   ├── initrd.gz
  │   └── vmlinuz
  ├── centos-6.0-x86_64
  │   ├── initrd.img
  │   └── vmlinuz
  ├── msgs
  │   ├── centos
  │   │   ├── boot.msg
  │   │   └── general.msg
  │   └── debian
  │       ├── f1.txt
  │       └── f2.txt

La mayoría de estos ficheros se pueden bajar casi desde cualquier sitio:

  http://ftp.cz.debian.org/debian/dists/squeeze/main/installer-amd64/current/images/netboot/debian-installer/amd64/

Como el contenido de "msg" es obvio, vamos a los fichero importantes, C0A80A86 y C0A80A87. ¿Qué clase de nombres son estos? Pues son las IPs, en hexadecimal, de nuestras dos máquinas virtuales:

  $ IP_ADDR="192.168.10.134"; printf '%02X' ${IP_ADDR//./ }; echo
  C0A80A86
  $ IP_ADDR="192.168.10.135"; printf '%02X' ${IP_ADDR//./ }; echo
  C0A80A87

El contenido, para CentOS, en C0A80A86:

  timeout 100
  prompt 1
  display msgs/centos/boot.msg
  F1 msgs/centos/boot.msg
  F2 msgs/centos/general.msg

  default Centos6

  label Centos6
    menu label ^Centos 6 amd64
    kernel centos-6.0-x86_64/vmlinuz
    append initrd=centos-6.0-x86_64/initrd.img ramdisk_size=6878 ip=dhcp ks=http://192.168.10.40/instalaciones/ks_rh6_134.ks

Como veis, hemos especificado la ruta web para el fichero kickstart.

Para Debian, en C0A80A87:

  # less C0A80A87
  menu hshift 15
  menu width 49
  prompt 1
  display msgs/debian/f1.txt
  timeout 100
  f1 msgs/debian/f1.txt
  f2 msgs/debian/f2.txt
  menu title Installer boot menu
  menu color title    * #FFFFFFFF *
  ...
  menu tabmsgrow 18
  menu tabmsg Press ENTER to boot or TAB to edit a menu entry

  default install

  label install
      menu label ^Debian Squeeze Texto amd64
      menu default
      kernel squeeze-x86_64/vmlinuz
      append video=vesa:ywrap,mtrr vga=788 initrd=squeeze-x86_64/initrd.gz auto=true priority=critical locale=es_ES console-keymaps-at/keymap=es --

En este caso, no hemos dicho dónde encontrar el fichero para debian-installer. Ya lo sabemos.

Y ya casi por último, veamos los ficheros kickstart y debian-installer, a los que vamos a acceder desde apache:

  # ls -1  /var/www/instalaciones/
  ...
  ks_rh6_134.ks
  squeeze_preseed_135.cfg

Todos conocéis el formato de los .ks; son simples y fáciles de leer.

  #version=RHEL6
  install
  url --url=http://192.168.10.40/instalaciones/centos/6.0-x86_64/
  lang es_ES.UTF-8
  keyboard es
  network --device eth0 --onboot yes --bootproto static --ip 192.168.10.134 --netmask 255.255.255.0 --gateway 192.168.10.1 --nameserver 192.168.10.1 --hostname fn134.forondarena.net
  rootpw  --iscrypted contraseña
  firewall --service=ssh
  authconfig --enableshadow --passalgo=sha512 --enablefingerprint
  selinux --enforcing
  timezone --utc Europe/Madrid
  bootloader --location=mbr --driveorder=vda --append="crashkernel=auto rhgb quiet"
  clearpart --all --drives=vda
  part /boot --fstype=ext4 --size=200
  part pv.gjsqnW-TYE3-SDwW-646R-0SSI-hhDa-ZF6bXm --grow --size=200
  volgroup vg_primario --pesize=4096 pv.gjsqnW-TYE3-SDwW-646R-0SSI-hhDa-ZF6bXm
  logvol / --fstype=ext4 --name=lv_root --vgname=vg_primario --size=7400
  logvol swap --name=lv_swap --vgname=vg_primario --size=588
  ##repo --name="CentOS"  --baseurl=http://192.168.10.1/instalaciones/centos/6.0-x86_64/ --cost=100

  halt

  %packages
  @core
  @server-policy
  @spanish-support
  openssh*
  %end

Es un ejemplo autogenerado para una máquina virtual tipo KVM. No le hagáis mucho caso al contenido en sí. Lo dicho, tenéis un kilo de documentación, e incluso aplicaciones para hacer estos ficheros.

Otra cosa muy diferente es debian-installer. La verdad, no conozco a mucha gente que no se haya atascado con esto en algún momento. El concepto es sencillo, con una opción para cada una de las opciones de cada uno de los menús que pueden aparecer durante el proceso de instalación. El problema es que, esto tan fácil de decir, es un listado .... largo; y con algunos algoritmos, como el del cálculo de espacio para particiones, nada claros. Además, como suele ser demasiado frecuente, el que decidió los nombres de las opciones.... bueno, que no me parece demasiado intuitivo, aunque si estás familiarizado con Debian te haces enseguida.

No voy a escribir un ejemplo completo. Os vais a tener que conformar con una pequeña muestra:

  # cat /var/www/instalaciones/squeeze_preseed_135.cfg
  ...
  # Keyboard selection.
  d-i console-tools/archs select at
  d-i console-keymaps-at/keymap select es
  d-i keyboard-configuration/xkb-keymap select es
  ### Network configuration
  d-i netcfg/choose_interface select eth0
  d-i netcfg/disable_dhcp boolean true
  # Static network configuration.
  d-i netcfg/get_nameservers string 192.168.10.1
  d-i netcfg/get_ipaddress string 192.168.10.135
  d-i netcfg/get_netmask string 255.255.255.0
  d-i netcfg/get_gateway string 192.168.10.1
  d-i netcfg/confirm_static boolean true
  ...
  # Root password, encrypted using an MD5 hash.
  d-i passwd/root-password-crypted password contraseña
  ...
  ### Partitioning
  d-i partman-auto/disk string /dev/sda
  d-i partman-auto/method string lvm
  d-i partman-lvm/device_remove_lvm boolean true
  d-i partman-lvm/device_remove_lvm_span boolean true
  d-i partman-auto/purge_lvm_from_device boolean true
  d-i partman-auto-lvm/new_vg_name string vg_forondarenanet
  d-i partman-basicmethods/method_only boolean false
  d-i partman-auto/expert_recipe string boot-root :: \
  100 100 100 ext3 \
         $defaultignore{ } \
         $primary{ } \
         device{ /dev/sda } \
         $bootable{ } \
         method{ format } \
         format{ } \
         use_filesystem{ } \
         filesystem{ ext3 } \
         mountpoint{ /boot } \
         . \
  200 1000 -1 ext4 \
         $defaultignore{ } \
         $primary{ } \
         device{ /dev/sda } \
         method{ lvm } \
         vg_name{ vg_forondarenanet } \
         . \
  300 4000 -1 ext4 \
         $lvmok{ } \
         lv_name{ lvroot } \
         in_vg{ vg_forondarenanet } \
         method{ format } \
         format{ } \
         use_filesystem{ } \
         filesystem{ ext4 } \
         mountpoint{ / } \
         . \
  512 512 512 linux-swap \
         $lvmok{ } \
         in_vg{ vg_forondarenanet } \
         lv_name{ lvswap } \
         method{ swap } \
         format{ } \
         .
  d-i partman-auto/choose_recipe select boot-root

  d-i partman/confirm_write_new_label boolean true
  d-i partman/choose_partition select finish
  d-i partman/confirm boolean true
  d-i partman/confirm_nooverwrite boolean true
  d-i partman/confirm_nochanges boolean true
  d-i partman-lvm/confirm boolean true
  d-i partman-lvm/confirm_nooverwrite boolean true
  d-i partman-lvm/confirm_nochanges boolean true
  ...
  # Individual additional packages to install
  d-i pkgsel/include string openssh-server less
  d-i pkgsel/upgrade select safe-upgrade
  popularity-contest popularity-contest/participate boolean false
  d-i finish-install/reboot_in_progress note
  d-i debian-installer/exit/halt boolean true
  # This will power off the machine instead of just halting it.
  d-i debian-installer/exit/poweroff boolean true
  ...

Y así podría seguir...

En este caso es una máquina virtual para ESX. Me interesa que veáis la parte de particionado. Aunque no os lo creáis, he definido una partición primaria /boot de unos 100M, y el resto en LVM, con aproximadamente 512M de swap, y el resto de espacio disponible para raíz.

Y poco más hace falta. Ahora bien, en este post, las notas que suelo añadir al final son más importantes que nunca.

Notas

Nota 1: Los ficheros de instalación automática de los ejemplos hacen un halt una vez finalizada la instalación. ¿Por qué? Porque no tengo automatizado que la BIOS cambie por arte de magia de arranque por PXE a arranque desde disco.

Nota 2: Más que una nota, un consejo. No os volváis locos con la configuración de ficheros de sistema, usuarios y aplicaciones. Tenéis herramientas mucho mejores para la configuración "fina", así que plantearos añadir puppet, cfengine, chef, ... en los bloques "%packages" y "d-i pkgsel/include", y dejarles que hagan su trabajo al arrancar la máquina. Para CentOS (desde puppetlabs):

  %post
  ....
  /sbin/chkconfig --level 345 puppet on
  /bin/echo "$PUPPETIP puppet" >> /etc/hosts
  /bin/echo "nameserver $NAMESERVERIP" >> /etc/resolv.conf
  hostname $hostname
  # Write out the hostname to a file for reboot.
  /bin/echo -e "NETWORKING=yes\nHOSTNAME=$hostname" > /etc/sysconfig/network
  /usr/sbin/puppetd -tv

y, como mini-ejemplo, un poco diferente, para Debian:

  d-i preseed/late_command string in-target wget -P /tmp/ http://servidor_web/script.sh;
    in-target chmod +x /tmp/script.sh; in-target /tmp/script.sh

dejando que ese "script.sh" haga cosas como "sed -i 's/START=no/START=yes/'", siguiendo la estructura de Debian de ficheros en default y todas estas cosas.

Nota 3: Lo más importante. Este post no es suficiente para hacer que las instalaciones automáticas os funcionen. Necesita trabajo. Si os atascáis con algo, tweet o comentario, y lo intentaremos solucionar.

read more

Buzones compartidos con Dovecot

dom, 02 oct 2011 by Foron

Ahora que ando reestructurando mi laboratorio, voy a aprovechar para documentar un par de aplicaciones que estoy moviendo al nuevo hierro.

En realidad, esto se sale un poco del objetivo de este blog, sobre todo teniendo en cuenta que ya hay kilos de documentación sobre, en este caso, Dovecot; pero bueno, a mí me va a servir como referencia rápida, y quizá os sea de utilidad a alguno de los cuatro que pasáis por aquí.

Empezamos con los buzones compartidos en Dovecot. Como siempre, pretendo ser lo más práctico posible, así que lo mejor que se me ha ocurrido es describir exactamente la forma en la que yo mismo tengo montado "el invento".

El problema

Antes de nada, hablemos sobre las aplicaciones que debemos tener funcionando antes de empezar:

Necesitamos un servidor de correo propio (probablemente Postfix) en el que recibir los mensajes, ya sea porque el MX apunta a él o porque usamos software tipo Fetchmail, por ejemplo. Además, tenemos varios usuarios en nuestro sistema que acceden a su correo a través de Dovecot.

Entre el correo que reciben, además del privado para cada uno, es muy probable que los usuarios estén suscritos a varias listas: de seguridad, de usuarios, anuncios de nuevas versiones de x aplicación, .... Además, también hay cuentas tipo helpdesk y listas para un departamento o grupo que deben estar accesibles para varios usuarios simultaneamente.

Algo habitual en estos casos es, por un lado, que cada usuario gestione sus propias subscripciones a listas de correo, y por otro, para el caso de las cuentas tipo helpdesk de las que hemos hablado, el mandar una copia a cada uno de los n usuarios implicados. Obviamente, esto genera un montón de copias y mensajes repetidos que, aunque no tenga que suponer un enorme problema, sí que es más propenso a fallos, además de no dejar de ser "poco elegante".

La solución

Bien, aquí ya entramos en la forma en la que yo lo hago, que no tiene que ser ni mucho menos la mejor o la única.

Vamos a empezar por el caso más fácil, los buzones públicos:

Por usar un ejemplo concreto, al hablar de "buzón público" me voy a referir a listas de correo generales y accesibles para todo el mundo. El único control de acceso que se va a hacer sobre ellas es a nivel de sistema de ficheros, por lo que recomiendo que los usuarios del sistema pertenezcan a un grupo concreto (o varios) siempre que quieran acceder a una u otra lista.

Y digo usuarios del sistema porque cada lista va a ser un usuario de sistema (mapeada con una cuenta de correo), de tal manera que la suscripción será única. Si quisieramos hacer que todo el mundo tuviera acceso a la lista de usuarios de Postfix, crearíamos el usuario "postlista", por ejemplo, y nos suscribiríamos a la lista de Postfix con postlista _A_T_ forondarena.net (no existe ni existirá nunca, que conste, pero ahora ya tengo un spamtrap más :D ). Como podéis suponer, el correo que llegue a esta dirección se guardará físicamente en (esta parte de la configuración de Postfix/Dovecot os la dejo a vosotros):

  drwxrwx--- 5 postlista postlista 4096 oct  1 17:57   /home/postlista/Maildir

Volviendo a los permisos a nivel de sistema operativo, hay algo importante a tener en cuenta: Dovecot 2.x (y con ello su agente de entrega local que usamos en Postfix) crea los ficheros de correo con los mismos permisos que su directorio principal, y por lo tanto debemos hacer que Maildir tenga 770, por ejemplo (siempre que optemos por los permisos a nivel de sistema operativo para limitar el acceso).

Una vez hecho esto vamos a crear un directorio común que nos va a servir como referencia para que los distintos usuarios "sepan" donde están las listas. La estructura va a ser la siguiente:

  ls -lha /home/listas/Maildir
  lrwxrwxrwx 1 root  root    21 sep 22 22:03 .postlista -> /home/postlista/Maildir

En el directorio "/home/listas" (o cualquier otro, no es un usuario del sistema) vamos a crear un enlace simbólico para cada uno de los buzones públicos.

¿Pero cómo sabe un usuario que puede acceder a esa lista pública?

Para esto usaremos los namespaces de IMAP (El que quiera entrar en el detalle sobre lo que son, que busque el RFC).

Dovecot define los namespaces en un bloque similar al siguiente, a veces en un único dovecot.conf, a veces en otro fichero. Debian, por ejemplo, divide los diferentes apartados de configuración en ".conf" diferentes:

  namespace {
    type = public
    separator = /
    prefix = listas-public/
    location = maildir:/home/listas/Maildir:INDEX=~/Maildir/listas-public
    subscriptions = no
  }

Adaptad la configuración a vuestro gusto, pero lo importante es que cuando el usuario "pepe" se loguee vía IMAP, va a poder suscribirse a todo lo listado en el directorio "/home/listas/Maildir", que además va a aparecer en su Thunderbird en el subdirectorio listas-public. Además, queremos que cada usuario tenga los ficheros propios de control que usa Dovecot en su maildir privado.

Esta es la forma fácil de crear buzones públicos.

Sin embargo, IMAP define una extensión para ACLs (los interesados también tienen RFCs al respecto), con las que podemos definir un control de acceso mucho más detallado, y que nos permitirán hilar mucho más fino en lo que puede hacer el usuario x en el buzón del usuario z. Para que os hagáis una idea, y cogido directamente del wiki de Dovecot, las ACLs permiten establecer los siguientes permisos:

  • lookup: Mailbox is visible in mailbox list. Mailbox can be subscribed to
  • read: Mailbox can be opened for reading
  • write: Message flags and keywords can be changed, except \Seen and \Deleted
  • write-seen: \Seen flag can be changed
  • write-deleted: \Deleted flag can be changed
  • insert: Messages can be written or copied to the mailbox
  • post: Messages can be posted to the mailbox by LDA, e.g. from Sieve scripts
  • expunge: Messages can be expunged
  • create: Mailboxes can be created (or renamed) directly under this mailbox (but not necessarily under its children, see ACL Inheritance section above) (renaming also requires delete rights)
  • delete: Mailbox can be deleted
  • admin: Administration rights to the mailbox (currently: ability to change ACLs for mailbox)

En el wiki de Dovecot podéis ver que letra corresponde a cada permiso. Por ahora lo dejamos y pasamos a la creación de un buzón compartido. Por cierto, no sé si está claro que con "buzón" me puedo estar refiriendo también a carpetas como "Trash", "Sent", o cualquier otra que creemos a mano.

Vamos a suponer que tenemos un usuario "helpdesk" en el que recibimos todo el correo destinado a "helpdesk _A_T_ forondarena.net" (otro spamtrap).

  /home/helpdesk/Maildir

Helpdesk es otro usuario del sistema, y su Maildir tiene permisos 770, para limitar el acceso también a nivel de sistema operativo.

Ahora que ya tenemos el usuario del sistema y que estamos recibiendo correo, vamos a activar el soporte para ACLs en Dovecot. Es muy sencillo, sólo hay que añadir un par de plugins, ya sea en dovecot.conf, o en el fichero que defina vuestra distribución.

  ...
  mail_plugins = acl
  ...
  protocol imap {
    mail_plugins = $mail_plugins imap_acl
  }

Y una vez tenemos los plugins, añadimos la configuración básica:

  plugin {
     acl = vfile
  }

Recordemos que queremos que el usuario "pepe" pueda acceder a todo el buzón "helpdesk". Para conseguirlo, vamos a hacer login con el usuario helpdesk, y vamos a usar el propio protocolo IMAP para dar acceso a pepe:

  # openssl s_client -connect 192.168.10.20:993
  CONNECTED(00000003)
  ..... (información SSL) .....
  * OK [CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE IDLE AUTH=PLAIN] Dovecot ready.
  . login helpdesk password
  . OK [CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE IDLE SORT SORT=DISPLAY THREAD=REFERENCES THREAD=REFS MULTIAPPEND UNSELECT CHILDREN NAMESPACE UIDPLUS LIST-EXTENDED I18NLEVEL=1 CONDSTORE QRESYNC ESEARCH ESORT SEARCHRES WITHIN CONTEXT=SEARCH LIST-STATUS ACL RIGHTS=texk] Logged in
  . setacl INBOX pepe lrwstipekxacd
  . OK Setacl complete.

Dicho de otra forma, hemos permitido "lrwstipekxacd" (cada permiso de la tabla anterior es una de estas letras) en el INBOX de helpdesk al usuario pepe.

¿Pero cómo sabe pepe que puede acceder a helpdesk?

Pues con los namespaces, claro. Vamos a crear uno de tipo "shared":

  namespace {
    ...
    type = shared
    separator = /
    prefix = buzones-shared/%%u/
    location = maildir:%%h/Maildir:INDEX=~/Maildir/buzones-shared/%%u
    ...
  }

Igual que con los buzones públicos, cuando pepe acceda a su cuenta va a ver un directorio buzones-shared en el que aparecerá helpdesk. ¿Verdad?

Pues no, porque no hay forma (con un rendimiento razonable) de hacer que Dovecot se recorra todos los buzones configurados (todos esos %%h y %%u), y sepa a cuáles tiene acceso pepe. A fin de cuentas, pepe y helpdesk son usuarios completamente diferentes, y no hay un enlace en un directorio claramente identificado, como "/home/listas" en el caso de los buzones públicos.

¿Entonces qué?

Es aquí donde entran en juego lo que en Dovecot se llaman "directorios", y que vamos a usar para decir, en este caso a pepe, que puede acceder a helpdesk. La configuración es la siguiente:

  plugin {
    acl_shared_dict = file:/etc/dovecot/acls/shared-mailboxes
  }

Quien dice "file:" dice base de datos o fichero .db. En cualquier caso, para este ejemplo usaremos un fichero en texto plano, "/etc/dovecot/acls/shared-mailboxes". Tened en cuenta, una vez más, que pepe tiene que poder leer el contenido de este fichero, y que además le interesa poder crear un lock en el directorio mientras está trabajando con él (vigilad los permisos unix, usad el directorio que queráis).

El contenido de shared-mailboxes es el siguiente:

  shared/shared-boxes/user/pepe/helpdesk
  1

Es una sintaxis que me parece particularmente "retorcida", pero es lo que hay si no queréis usar base de datos.

Y con esto lo tenemos ya "casi todo" (ver notas al final del post). Cuando pepe abra su Thunderbird y liste las carpetas a las que puede suscribirse verá el inbox de helpdesk, en este caso a partir de la carpeta "buzones-shared".

Notas

Primera nota:

En Dovecot, una vez que definimos un namespace, también tenemos que definir explicitamente el namespace privado. Por lo tanto, la configuración completa, en lo que a namespaces se refiere, tiene que parecerse a esto:

  namespace {
    inbox = yes
    location =
    prefix =
    separator = /
    subscriptions = yes
    type = private
  }
  namespace {
    location = maildir:/home/listas/Maildir:INDEX=~/Maildir/listas-public
    prefix = listas-public/
    separator = /
    subscriptions = no
    type = public
  }
  namespace {
    location = maildir:%%h/Maildir:INDEX=~/Maildir/buzones-shared/%%u
    prefix = buzones-shared/%%u/
    separator = /
    subscriptions = no
    type = shared
  }

Una vez más, adaptad la configuración a vuestras preferencias. Por ejeplo, quizá os venga bien añadir un "list = children" en vuestros namespaces.

Segunda nota:

¿Si habéis hecho pruebas antes de llegar al final, os habéis dado cuenta de que, una vez configurados los buzones shared, las listas públicas han dejado de verse?

¡Claro!, hemos empezado a usar ACLs, así que hay que dar acceso a pepe a las listas. Muy fácil, nos logueamos con el usuario de la lista que queramos abrir, y damos acceso a pepe:

 ...
  * OK [CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE IDLE AUTH=PLAIN] Dovecot ready.
  . login postlista password
  . OK [CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE IDLE SORT SORT=DISPLAY THREAD=REFERENCES THREAD=REFS MULTIAPPEND UNSELECT CHILDREN NAMESPACE UIDPLUS LIST-EXTENDED I18NLEVEL=1 CONDSTORE QRESYNC ESEARCH ESORT SEARCHRES WITHIN CONTEXT=SEARCH LIST-STATUS ACL RIGHTS=texk] Logged in
  . setacl INBOX pepe lrwstipekxacd
  . OK Setacl complete.
  . logout

Tercera nota:

Los ficheros dovecot-acl tienen el siguiente contenido de ejemplo:

  user=pepe akxeilprwts

Pero no los editéis directamente. Usad setacl, getacl, myrights, ..., o mejor aún, alguna extensión de Thunderbird que lo haga por vosotros.

Y para terminar, tened en cuenta, una vez más, que este post no es una referencia para hacer copy/paste. Necesita trabajo (poco) si queréis hacerlo funcionar.

read more