Archivo de la categoría: Sistemas

Reiniciar el router automáticamente

Tengo un problema con mi router ADSL de casa. Cada 3 o 4 días se cuelga por alguna extraña razón (¿será saturación? 😛 ) y hay que resetearlo para que vuelva a funcionar. Cansado de estos cuelgues decidí solucionarlo con un pequeño script que mi máquina Linux ejecuta cada dos días de madrugada, así no me entero. Ya no me he vuelto a dar cuenta de los cuelges.
Lo primero que hay que hacer es habilitar el acceso telnet al router desde dentro de la red local, nunca desde el exterior 😛 .

expect es una herramienta que permite automatizar aplicaciones interactivas tipo telnet o ftp, donde tienes que introducir tu usuario y tu clave y alguien tiene que estar ahí para teclearlas. Expect lo que hace es, como su propio nombre indica, esperar determinadas cadenas de texto e introducir los datos que se hayan programado. Pongamos un ejemplo práctico. Cuando haces un telnet tienes la siguiente secuencia de eventos:

[osus@servidor ~]# telnet 192.168.0.100
Trying 192.168.0.100...
Connected to 192.168.0.100.
Escape character is '^]'.

Username:

Password:

Es decir, cuando haces un telnet «esperas» la cadena «Username:» para introducir tu usuario y después introduces tu clave cuando recibes «Password:«.

Esto es exactamente lo que automatiza expect. Veamos cómo con un script práctico.

[osus@servidor scripts]# cat router.sh
#!/usr/bin/expect -f
set force_conservative 1 ;
if {$force_conservative} {
        set send_slow {1 .010}
        proc send {ignore arg} {
                sleep .2
                exp_send -s -- $arg
        }
}
spawn telnet 192.168.0.100
expect "Username :*"
send -- "USUARIOr"
expect "Password :*"
send -- "CLAVEr"
expect "ROOT :>*"
send -- "rebootr"
expect "figuration(y/n) ? :*"
send -- "yr"
expect -gl "\[*]$*"
exit

Creo que habla por sí sólo el script. Esperamos a recibir cada cadena de texto para enviarle nuestros datos. Una vez estamos logueados esperamos la cadena inicial que nos devuelve el router, en mim caso «ROOT :>«. Ahí es cuando le pedimos el reboot y lo confirmamos. Ya está, el router se reinicia sólo. Obviamente todas las cadenas que esperas recibir deben coincidir con las de tu router, las aquí indico son las del mío.

Creando enlaces punto a punto entre servidores Linux con OpenVPN

Si hace unas semanas veíamos como crear un servidor VPN, hoy veremos como crear un túnel seguro dedicado entre dos servidores que esté permanentemente levantado y que se cree automáticamente al reiniciar alguna de las máquinas. ¿La utilidad? Tener una conexión directa con la máquina de backups, con un servidor de bases de datos, con la oficina, con otro centro de datos… seguro que se te ocurren más 😛 .

Como comentábamos entonces, PPTP no es el paradigma de la seguridad, pero la ventaja que tiene es la facilidad con que un cliente se conecta contra el servidor sin apenas conociemientos.  El ejemplo de hoy no nos servirá para crear túneles bajo demanda, pero veremos lo sencillo que puede ser crear un túnel dedicado. Siempre que se habla de VPN se termina en IPSec, pero ¿quién no se vuelto loco con la instalación de un túnel de este tipo? ¿Es realmente necesario utilizar IPSec para cualquier túnel? Yo creo que no, cada cosa para lo que está. Igual que con el primer artículo creábamos un sistema de VPN sencillo para el usuario, ahora que simplemente queremos conectar máquinas entre sí recurrimos a otro software que lo hace sin quebraderos de cabeza.

Necesitaremos, obviamente, máquinas Linux (aunque también se pueden emplear Windows) y el software que utilizaremos en este artículo: OpenVPN.

Tal y como reza en la home de OpenVPN:

Starting with the fundamental premise that complexity is the enemy of security, OpenVPN offers a cost-effective, lightweight alternative to other VPN technologies that is well-targeted for the SME and enterprise markets.

Y además lo cumple, ligero y sencillo.

Configuración

Lo primero será instalar el software. En mi CentOS no tendremos más que hacer

yum install openvpn lzo

Lzo nos proporcionará la compresión a través del túnel.

Aunque no lo parezca, ya queda poco 🙂 . Ahora debemos generar la clave SSL  que asegurará las comunicaciones a través del tunel. Muy sencillo, sólo necesitaremos un comando:

openvpn --genkey --secret clave.key

Esta clave debemos copiarla a /etc/openvpn de los dos servidores que se quieren conectar. Para copiarla al segundo lo puedes hacer con scp:

scp clave.key tuservidor.com:/etc/openvpn

Finalmente sólo nos queda el archivo de configuración. En el primer servidor creo un archivo con el nombre del segundo para identificar fácilmente qué conexión es puesto que podemos haber definido varios túneles.

[osus@servidor1 openvpn]# cat servidor2.conf
port 1194
proto udp
remote servidor2.com 1194
dev tun
ifconfig 192.168.2.2 192.168.2.1
secret clave.key
comp-lzo
keepalive 10 60
ping-timer-rem
persist-tun
persist-key
log /var/log/vpn.log

Como se puede ver, la configuración es muy sencilla, no necesita apenas explicación. En servidor2.com pones la IP del servidor al que se va a conectar, defines el puerto, las IPs privadas del servidor y del cliente y voilà, ya tienes la primera máquina preparada.

Vamos ahora con el segundo servidor.

[osus@servidor2 openvpn]# cat servidor1.conf
port 1194
proto udp
remote servidor1.com
dev tun
ifconfig 192.168.2.1 192.168.2.2
secret clave.key
comp-lzo
keepalive 10 60
ping-timer-rem
persist-tun
persist-key
log /var/log/vpn.log

Casi lo mismo que en el caso del servidor, sólo cambiamos la IP del servidor remoto y el orden de las IP’s privadas.

¡Ya está!

Sólo nos queda probarlo. Lanzamos el servicio en las dos máquinas y veremos si todo ha ido bien:

[osus@servidor1 ~]# service openvpn start
Starting openvpn:                                          [  OK  ]

[osus@servidor1 ~]# ifconfig tun0
tun0      Link encap:UNSPEC  HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
          inet addr:192.168.2.1  P-t-P:192.168.2.2  Mask:255.255.255.255
          UP POINTOPOINT RUNNING NOARP MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:100
          RX bytes:0 (0.0 b)  TX bytes:0 (0.0 b)

Parece que sí. Para verificar que, en efecto nos hemos conectado satisfactoriamente revisaremos el log.

[osus@servidor1 ~]# tail -f /var/log/vpn.log
Mon Feb  2 20:36:11 2009 NOTE: the current --script-security setting may allow this configuration to call user-defined scripts
Mon Feb  2 20:36:11 2009 LZO compression initialized
Mon Feb  2 20:36:11 2009 TUN/TAP device tun0 opened
Mon Feb  2 20:36:11 2009 /sbin/ip link set dev tun0 up mtu 1500
Mon Feb  2 20:36:11 2009 /sbin/ip addr add dev tun0 local 192.168.2.1 peer 192.168.2.2
Mon Feb  2 20:36:11 2009 UDPv4 link local (bound): [undef]:1194
Mon Feb  2 20:36:11 2009 UDPv4 link remote: servidor2.com:1194
Mon Feb  2 20:36:16 2009 Peer Connection Initiated with servidor2.com:1194
Mon Feb  2 20:36:16 2009 Initialization Sequence Completed

Parece que sí, que ha funcionado. ¿Veremos la segunda máquina desde la primera?

[osus@servidor1 ~]# ping 192.168.2.2
PING 192.168.2.2 (192.168.2.2) 56(84) bytes of data.
64 bytes from 192.168.2.2: icmp_seq=1 ttl=64 time=276 ms
64 bytes from 192.168.2.2: icmp_seq=2 ttl=64 time=197 ms

Eso es un sí ¿no? 😛 .

Consideraciones adicionales

Obviamente habrá que abrir el puerto que hemos configurado para el túnel en el router (si lo hubiese) y verificar que el firewall no está bloqueando el tráfico desde o hacia ese puerto, suele ser el principal problema a la hora de conectarse, si tu firewall está bien configurado bloqueará automáticamente todo el tráfico que no sea el permitido (http, smtp, dns…).

Si queremos que el túnel se inicie automáticamente al reiniciar las máquinas habrá que añadirlo a los servicios de arranque. En el caso de CentOS/RHEL sería:

chkconfig --level 2345 openvpn on

Finalmente nos queda un comentario. Supongamos que la primera máquina es nuestro servidor remoto y el segundo está en nuestra oficina y que queremos poder llegar desde el primero a otros servidores de la oficina. Supongamos también que la red de la oficina es del rango 192.168.0.0/24 (el túnel, como hemos visto, está en la red 192.168.2.0). En el servidor enrutamos el tráfico a las IP’s de la oficina a través del túnel añadiendo esta línea al archivo de configuración de la VPN, OpenVPN añadirá la ruta cada vez que se levante el túnel.

route-up "route add -net 192.168.0.0/24 tun0"

Ya sólo te queda añadir en el servidor cliente las reglas iptables adecuadas para que el tráfico se diriga hacia donde tú quieres que lo haga.

Éso es todo. Como os prometí, es un sistema muy sencillo a la vez que potente y ligero.

Copias de seguridad de bases de datos

A través de Pensamientos Ágiles llego a un artículo donde explican una interesante manera de hacer backups de bases de datos Mysql utilizando subversion. La idea es excelente, pero me plantea una serie de dudas si se trata de bases de datos de gran tamaño. Creo que es genial para mantener la estructura de la base de datos de un proyecto y tener controlados los cambios que se realizan sobre ella, pero mantener también los datos… no acabo de verlo.

En uno de mis proyectos tengo una base cuyo dump ocupa alrededor de 1Gb. Yo, sinceramente, no veo coherente mantener semejante archivo en svn. La idea es excelente y para bases de datos «pequeñas» creo que sí es útil, habría que decidir, en todo caso, qué entendemos por «pequeñas«. Pero insisto, no lo veo para archivos grandes, y 1Gb, tengamos el criterio de grande/pequeño que tengamos, es un archivo grande.

Normalmente lo que hago para hacer los backups es sacar un dump de cada base de datos, de manera que pueda recuperar cualquiera de ellas, y comprimir el resultado con bzip2. ¿Por qué bzip2 y no gzip? Simplemente por el nivel de compresión. En su día hicimos pruebas con ámbos sistemas y bzip2 salía mejor parado. Éstos son los resultados que obtendríamos hoy en día con la compresión estándar de ámbos sistemas:

-rw-r--r--   1 osus   users  1023246033 Feb  2 01:50 bbdd.sql
-rw-r--r--   1 osus   users   141481434 Feb  2 01:50 bbdd.sql.bz2
-rw-r--r--   1 osus   users   190733372 Feb  2 01:50 bbdd.sql.gz

Con bzip2 el backup pesa unos 50mb menos.

El script que utilizo para hacer backups de base de datos es más o menos lo que suele hacer la mayoría de la gente:

#!/bin/sh
dia=`date +%w`
for i in ` /usr/bin/mysql -N --execute='show databases;' `
do
echo "Haciendo backup de $i"
/usr/bin/mysqldump -C --opt $i > /backup/bbdd/$dia/$i.sql
bzip2 -fz /backup/bbdd/$dia/$i.sql
done

En otras palabras, mantengo las copias completas de los últimos siete días, semanalmente se se reemplazan por las del día en que nos encontramos. En la carpeta del día correspondiente genero los dumps comprimidos de todas las bases de datos.

El último paso es copiar los dumps del día a otra máquina. La tarea que se ejecuta diariamente lanza el proceso una vez termina de crear los backups. Esto lo hacemos a dos máquinas distintas. La primera es una máquina externa y se envían por ssh desatendido, utilizando claves públicas, de manera que la máquina remota no solicita la clave del usuario. La segunda es una máquina de nuestra oficina con la que tenemos una vpn que se levanta antes del backup, se copia con ssh destendido también y se cierra al terminar. En ésta máquina de la oficina hay otra tarea que diariamente restaura el backup de ciertas bases de datos en su Mysql local matando así dos pájaros de un tiro: por un lado tenemos la base de datos de desarrollo casi idéntica a la de producción, faltarían sólamente los últimos datos del día, y por otro lado nos aseguramosn de que los backups que se realizan son correctos ya que en caso contrario fallaría la restauración.

Hasta ahora nos ha funcionado bien el sistema. En alguna ocasión hemos tenido que recuperar alguna tabla de ciertas bases de datos y no hemos tenido excesivos problemas, todo ha funcionado a la perfección.

Vuelvo a repetir que esto no es una crítica al sistema del subversion, todo lo contrario, la idea me parece cojonuda, pero creo que habría que puntualizar o estudiar bien la viavilidad en ciertos escenarios.

Convierte tu móvil 3G en un punto de acceso wifi con JoikuSpot

Hace unos días tuve un problema, necesitaba conectar dos portátiles a Internet simultáneamente pero sólo disponía de un teléfono 3G, así que, o conectaba uno o conectaba el otro. Buscando por Google llegué a JoikuSpot, una aplicación para Symbian que convierte tu teléfono 3G en un router inalámbrico. Sí, como lo has oído, lo mismo que hacen esos routers pero con un sencillo teléfono S60.

Además de la ventaja de compartir la red, me pareció interesante disponer de  un método de conexión portátil/teléfono alternativo y estándar al Bluetooth y al cable USB.

Obviamente este método sólamente sirve para terminales que dispongan de conexión wifi 😛 . En mi caso tengo un Nokia E51 que la tiene, así que es un dispositivo con dos interfaces de red, la wifi por un lado y la conexión dinámica 3G/HSDPA por el otro, lo mismo que un router normal y corriente.

Existen dos versiones de JoikuSpot,  Light (gratis) y Premium (en oferta por 15$). La diferencia fundamental es la cantidad de protocolos soportados. Mientras que con la primera sólamente tienes HTTP/HTTPS, con la segunda tienes prácticamente todos los necesarios para una navegación completa (smtp, pop3, vpn, messenger, skype…). Para el uso que le voy a dar yo me es suficiente con la versión Light, al menos por el momento.

Una vez descargada habrá que configurar el punto de acceso inalámbrico con los parámetros típicos de cualquier router: nombre de la red, canal, tipo de autentificación, clave…

screenshot0003.jpg

Una vez tenemos nuestro punto de acceso wifi bien configurado no tenemos más que iniciarlo, para lo cual nos pedirá qué punto de acceso de datos queremos utilizar para salir a Internet. Seleccionamos nuestra conexión 3G/HSDPA y nuestro móvil ya será un router inalámbrico.

screenshot0001.jpg

En todo momento tenemos información del tráfico y el tiempo de conexión.

screenshot0002.jpg

Ahora podemos buscar la red inalámbrica desde los portátiles. Veremos que en la lista de redes aparece una nueva con el nombre que le hemos puesto al configurar JoikuSpot. Nos conectamos indicando la clave y por arte de magia tenemos conexión a Internet en todos los equipos que se conecten a nuestro móvil.

screenshot0009.jpg

Todo muy sencillo de configurar.

Una alternativa más que útil para emergencias (Noooooo, no funciona Internet) o viajes.

Screenshot

Para los que se estén preguntando cómo hago las capturas de pantalla directamente desde el móvil, os recomiendo Screenshot, una aplicación Symbian libre para realizar capturas de pantalla y que te permite definir la combinación de teclas que dispararán la captura. Llevo utilizándolo mucho tiempo y siempre me ha dado muy buenos resultados.

Otras utilidades para un servidor de correo

A veces un servidor de correo puede sernos de muchísima utilidad si sabemos cómo manejarlo correctamente. En el artículo de hoy veremos como utilizar nuestro MTA para ejecutar automáticamente acciones cuando se recibe un determinado email o con los parámetros que definamos. Una vez conozcamos la teoría plantearemos dos casos prácticos como ejemplo.

Para comenzar necesitaremos un servidor Linux con Qmail como MTA. Supongo que cualquier otro servidor de correo servirá (Sendmail, Postfix), yo lo personalizo en Qmail porque es el que conozco y utilizo, pero estoy seguro de que con los demás se puede hacer lo mismo.

La teoría

Para entender cómo funciona la idea debemos entender primero cómo Qmail realiza la entrega de mensajes en los buzones locales. Es un tema sobre el que hay bastante literatura buscando en Google pero que puede no quedar muy claro en una lectura rápida. Es el famoso lío de los archivos .qmail.

Cada vez que se crea un usuario del sistema se debe crear, si va a recibir emails, un archivo .qmail-default en su directorio de usuario.

[jiglesias@lerez ~]# cat /home/jiglesias/.qmail-default
./Maildir/

Normalmente este archivo se crea automáticamente ya que al instalar qmail se habrá creado una copia de este archivo en el esqueleto de creación de usuarios /etc/skel:

[jiglesias@lerez ~]# ls -la /etc/skel
total 56
drwxr-xr-x  4 root root  4096 Jul 29 18:29 .
drwxr-xr-x 97 root root 12288 Dec 30 04:08 ..
-rw-r--r--  1 root root    33 Feb  1  2008 .bash_logout
-rw-r--r--  1 root root   176 Feb  1  2008 .bash_profile
-rw-r--r--  1 root root   124 Feb  1  2008 .bashrc
drwx------  5 root root  4096 Aug 29  2007 Maildir
-rw-r--r--  1 root root    12 Jan  2  2008 .qmail-default
-rw-r--r--  1 root root   658 Sep 12  2006 .zshrc

Para el que no lo sepa, el esqueleto son los archivos que se copiarán al directorio de usuario (con los permisos de éste) cada vez que se crea uno nuevo. Si quieres que todos tus usuarios tengan algún archivo automáticamente, éste es tu sitio. En nuestro caso vemos que además del .qmail-default está el directorio Maildir, el de entrega por defecto del correo en qmail. Teniendo un usuario este archivo y este directorio, podrá recibir correo.

Vale vale, vas muy deprisa. Todavía no has explicado para que sirve el .qmail-default ese.  Cierto. Los archivos .qmail indican las reglas de entrega de los mensajes en base a dos parámetros:

  • El nombre del archivo .qmail-xx hace referencia a la cuenta de correo sobre la que actúa.
  • El contenido indica qué hacer con el correo.

Supongamos un usuario (jiglesias)  que recibe el correo de dos cuentas distintas (jiglesias@… y osus@…).

Por defecto todo su correo irá a su buzón ya que es lo que indica el archivo .qmail-default. Queremos ahora que el comportamiento sea distinto dependiendo de la cuenta a la que vaya dirigido, creamos entonces los archivos .qmail para las direcciones:

  • .qmail-jiglesias : controla el correo que vaya a jiglesias@…
  • .qmail-osus : controla el correo que vaya a osus@…

Podemos incluso ir un poco más lejos con un archivo .qmail-jiglesias-default, y controlaríamos el correo que vaya a cualquier dirección del tipo jiglesias-XXXX@…, es decir, cualquier dirección que comience por jiglesias- será controlada por este archivo .qmail.

En el caso básico, que es el que veíamos, la entrega se realiza al buzón de correo del usuario (el directorio Maildir) pero podríamos hacer otras cosas en función del contenido del archivo .qmail encargado de procesar la entrega del correo:

Reenvío a otra cuenta:

[jiglesias@lerez ~]# cat /home/jiglesias/.qmail-jiglesias
reenvio@osusnet.com

Reenvío a un programa/script:

[jiglesias@lerez ~]# cat /home/jiglesias/.qmail-osus
|preline /usr/bin/programa

Combinación de los anteriores

[jiglesias@lerez ~]# cat /home/jiglesias/.qmail-jiglesias
./Maildir/
reenvio@osusnet.com
|preline /usr/bin/programa

El caso que nos interesa es el segundo, es decir, pasar la entrega del email a un script que se encargará de analizar el email y tomar decisiones.

Te habrá llamado la atención el |preline del archivo .qmail. Es el sistema que prepara un email para ser procesado y entregado a otro script añadiendo algunas cabeceras. La salida del script que reciba el email es importante ya que de ella depende el resultado de la entrega final de email, en concreto es importante saber que si queremos rechazar un email habrá que terminar el script con un exit (100), esto indicará a qmail que debe rechazar ese mensaje. Veremos más adelante la utilidad de esta salida.

La práctica

Una vez sabemos cómo pasar el control de un email recibido a un script, veamos como tratarlo. Lo haremos con un script PHP. En nuestro archivo .qmail haremos algo como:

[jiglesias@lerez ~]# more .qmail-jiglesias
|preline /usr/bin/php /home/jiglesias/prueba.php

Con esto hemos terminado el trabajo en el servidor de correo. Veamos ahora como parsear el email desde PHP.

Lo primero que debemos hacer es recoger el contenido del email desde el script a través de la entrada estándar,  después ya podemos procesar el email como una cadena de texto.

<?php
$email=file("php://stdin");
$email=implode("", $email);
?>

Con estas sencillas dos líneas de código tendremos en nuestro script el contenido del email. Ahora sólamente debemos procesarlo. Podemos hacerlo línea por línea por nuestra cuenta o apoyarnos en alguna librería. Yo utilizo Mail_mimeDecode de Pear. La ventaja de esta librería es que podemos obtener, además del texto del email, los archivos adjuntos.

<?php
$email=file("php://stdin");
$email=implode("", $email);
$params['include_bodies'] = true;
$params['decode_bodies'] = true;
$params['decode_headers'] = true;
$params['input'] = $email;
$structure = Mail_mimeDecode::decode($params);
$subject = trim($structure->headers['subject']);
$ddate = trim($structure->headers['date']);
$from = addslashes(trim($structure->headers['from']));
if(ereg("<(.*)>", $from, $p)) $from=$p[1];
if(ereg(""(.*)"", trim($structure->headers['from']), $pp))
    $nombre=$pp[1];
?>

Así podemos ya procesar el email y tomar las decisiones que consideremos oportunas. Podremos insertarlo en una base de datos, lanzar otros procesos automatizados, enviar avisos por SMS… lo que se nos ocurra.

Casos prácticos

La pregunta clave, después de ver la teoría, sería ¿para qué me sirve esto?.  Os propongo dos aplicaciones que yo he hecho.

Sistema de soporte

El típico sistema de tickets de soporte. En el asunto del email se arrastra el identificador del ticket, por ejemplo [#123445]. Tendremos que analizar el asunto y comprobar si aparece el patrón predefinido. Si no existe estamos ante un nuevo ticket e insertamos los datos en nuestra base de datos, en caso contrario es una respuesta a una incidencia anterior y ahí tendremos el identificador. Sencillo ¿no?. Podemos incluso adjuntar a nuestras indicendias archivos que puedan llegar en el email.

Envío de archivos desde el movil

El segundo ejemplo sería semejante al anterior técnicamente pero distinto conceptualmente. La mayoría de los móviles (salvo los de gama alta) no pueden enviar archivos desde los formularios wap (<input type=»file»>). La alternativa es que el usuario envie un email o un MMS (la mayoría de operadoras permiten el envío a direcciones de email) con sus archivos. Nuestro script procesará el contenido del mail recibido, decodificará los archivos y los tratará como sea oportuno.

Son dos sencillos ejemplos de cómo utilizar el email para automatizar tareas, pero, como he comentado, podríamos hacer todo lo que se nos ocurra, desde enviar un SMS de aviso hasta incluso reiniciar nuestro servidor o lanzar cualquier otra tarea.

Centro de Sistemas de la Xunta de Galicia, las cosas bien hechas

Hace algo más de un mes recibí un email de Marcos avisándome que no podía enviar emails a cuentas del dominio xunta.es, a los diez días le venían rebotados con un error de conexión. Hasta entonces siempre había funcionado bien, pero llevaban ya unos días con el problema. Haciendo una análisis detallado desde el servidor obteníamos lo siguiente:

[osus@lerez ~]# dig xunta.es
; <<>> DiG 9.3.4-P1 <<>> xunta.es
;; global options: printcmd
;; connection timed out; no servers could be reached

[osus@lerez ~]# dig @85.91.64.171 xunta.es
; <<>> DiG 9.3.4-P1 <<>> @85.91.64.171 xunta.es
; (1 server found)
;; global options: printcmd
;; connection timed out; no servers could be reached

[osus@lerez ~]# dig @85.91.64.172 xunta.es
; <<>> DiG 9.3.4-P1 <<>> @85.91.64.172 xunta.es
; (1 server found)
;; global options:  printcmd
;; connection timed out; no servers could be reached

[osus@lerez ~]# ping 85.91.64.171
PING 85.91.64.171 (85.91.64.171) 56(84) bytes of data.
64 bytes from 85.91.64.171: icmp_seq=1 ttl=45 time=184 ms
--- 85.91.64.171 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 184.025/184.025/184.025/0.000 ms

[osus@lerez ~]# ping 85.91.64.172
PING 85.91.64.172 (85.91.64.172) 56(84) bytes of data.
64 bytes from 85.91.64.172: icmp_seq=1 ttl=45 time=173 ms
-- 85.91.64.172 ping statistics ---
2 packets transmitted, 1 received, 50% packet loss, time 1000ms
rtt min/avg/max/mdev = 173.111/173.111/173.111/0.000 ms

[osus@lerez ~]# telnet 85.91.64.172 53
Trying 85.91.64.172...
Connected to 85.91.64.172 (85.91.64.172).
Escape character is '^]'.
Connection closed by foreign host.

Donde 85.91.64.171 y 85.91.64.172 son los servidores DNS de xunta.es.

Como se puede ver en el resultado de las pruebas, conectividad con los servidores DNS sí que había, respondían al ping, no había ningún tipo de bloqueo, sin embargo el servidor DNS no aceptaba nuestras solicitudes, con lo cual no podíamos resolver ningún host de xunta.es. Era ése precisamente el error que reportaba nuestro MTA cuando devolvía los emails, no se ha encontrado el servidor de destino (como no podía resolver los servidores MX asumía que el dominio no existía).

Decido entonces ponerme en contacto con el centro de sistemas y el primer mensaje no se me ocurre otra cosa que escribirlo con una cuenta desde la misma máquina que fallaba. Me llegó devuelto a los diez días tal como acabo de describir, pero me sirvió para ver con mis propios ojos el problema. Volví a escribir con una cuenta de otra máquina explicándoles el problema y las pruebas que os he copiado.

En un primer momento me contestan que, tras abrir una incidencia con el centro de gestión de red,  no tienen ningún problema de conectividad y que deberíamos tener acceso.

Abrín incidencia co centro de xestión de rede da Xunta de Galicia, e dinme que en principio non debería haber ningún problema co acceso ó noso servidor dende a vosa ip, polo que deberíades poder enviar sen problemas.

Entre otras cosas me indican, además, que haga una prueba de acceso al servidor de correo. Obviamente no han entendido bien que el problema es sólamente la resolución DNS, el resto funciona correctamente.

El 23 de noviembre vuelvo a escribirles explicando que el problema no es la conectividad, es sólo la resolución DNS y que al no poder resolver estamos cerrando a nuestra máquina cualquier host de xunta.es, adjuntando de nuevo las pruebas realizadas.

Unos días después me contestan que han hecho algunos cambios en sus DNS, que comprobemos si ya funciona, pero sigue sin hacerlo y así se lo hago saber el 1 de diciembre y les indico además, por si quieren hacer pruebas, el entorno (sistema operativo, versión de bind…).

10 días despues me contestan, atención:

Non me olvidei do voso problema co dns da Xunta… Simplemente comentarche que escalei a incidencia ó Centro de Xestión de Rede e como non me respostaran onte volvín a enviarlle a consulta e dinme que están preparando un escenario para reproducir o voso problema, porque en principio non hai ningún filtro no dns que impida que unha ip como a vosa realice consultas ó dns… Creo que van montar unha máquina cunha ip como a vosa para facer probas, mantereite informado dos progresos nas súas probas.

Es decir, que escalaron de nuevo la incidencia al centro de gestión de red y que, como no encontraban ningún problema,  iban a replicar nuestro entorno con nuestra misma IP para probar y ver in situ la situación.

Al día siguiente nos informan que, efectivamente, han visto el error que comentábamos y que nos avisarían cuando lo solucionasen. La verdad, temía que a ellos sí que les funcionase, pero no fue así.

Un par de días más tarde nos avisan que la incidencia está ya resuelta, que hagamos las pruebas oportunas para comprobarlo. En efecto, ahora todo volvía a funcionar correctamente.

En todo momento pensé que el problema iba a quedar sin solucionar, era bastante escéptico y creía que jamás se preocuparían por ver qué estaba pasando, sin embargo ocurrió todo lo contrario. Muchos centros de soporte y de sistemas deberían tomar nota y aprender de este caso. Hay ocasiones en que aunque parezca que tus sistemas están bien porque a todo el mundo le funcionan, puede haber algún caso aislado que presente una incidicencia que sea problema tuyo y no de él. El clásico «si a todo el muno le funciona es que funciona y si a uno no le va, será problema de él«, puede no ser siempre correcto, pero hay que molestarse en comprobarlo. Sé de buena tinta que en la inmensa mayoría de sitios habrían pasado de nosotros y mucho menos aún se habrían tomado las molestias de replicar nuestro escenario para comprobar lo que les contábamos.

Desde aquí dar las gracias a Silvia, la persona que se responsabilizó de nuestra incidencia y que puso todo su empeño en encontrar la solución, y agradecer a todo el Centro de Xestión de Rede de la Xunta de Galicia su colaboración y ayuda. No me han dicho el origen del problema, entiendo que puede ser información confidencial en cuanto a tipología de redes, firewalls, DMZ’s… pero el trabajo ha sido excelente.

Conversión de vídeos a 3gpp bajo demanda en un entorno web

Hoy veremos como aplicar conversiones de vídeo en un entorno web donde los usuarios suben sus vídeos en cualquier formato. Sí, tienes razón, ni más ni menos que lo que hace YouTube, de hecho utilizaban un sistema muy similar a lo que veremos ahora y basado en el mismo software. En realidad el artículo se podría aplicar a prácticamente cualquier tipo de conversiones, incluso para iPod o PSP, sólo hay que utilizar los parámetros adecuados. Nosotros nos centraremos en la conversión a 3gp para después poder hacer streaming con ellos además de permitir su descarga.

Nuestro entorno se basará en un servidor con Linux (Centos5 en mi caso). Como software sólamente necesitaremos ffmpeg, herramienta imprescindible para cualquier aplicación (tanto web como de escritorio) bajo Linux.

¿Qué es ffmpeg?

Hace unas semanas veíamos cómo utilizar ffserver para convertir flujos de vídeo y hacer streaming. ffmpeg es la utilidad en la que se apoya ffserver para realizar las conversiones.

ffmpeg es una herramienta de software libre que permite realizar conversión entre la mayoría de formatos de vídeo más utilizados. Una inmensa mayoría de programas de conversión bajo Windows no son más que frontends gráficos para ffmpeg.

Normalmente los paquetes precompilados de ffmpeg no vienen con las extensiones adecuadas para las conversiones que necesitamos, así que deberemos compilar nuestra propia versión.

Necesitas instalar los siguientes paquetes, son más de los imprescindibles, pero así nos ahorraremos problemas futuros:

libmp4v2
libvorbis
libvorbis-devel
lame
lame-devel
faac
faac-devel
faad2
x264
x264-devel
xvidcore
xvidcore-devel

Debo decir que para mi Centos no están todos disponibles como rpm con lo cual habrá que compilar manualmente algunos de ellos. No deberías tener ningún problema para localizar cada librería.

Finalmente habrá instalar los siguientes paquetes:

http://ftp.penguin.cz/pub/users/utx/amr/amrnb-7.0.0.2.tar.bz2

http://ftp.penguin.cz/pub/users/utx/amr/amrwb-7.0.0.3.tar.bz2

Con esto tenemos ya todo preparado para compilar nuestro propio ffmpeg. Descarga el paquete desde la web oficial, descomprímelo y:

./configure --enable-libmp3lame --enable-libvorbis --enable-libogg --enable-libamr-nb --enable-libamr-wb --enable-libfaac --enable-gpl --enable-libxvid --enable-libx264 --enable-libfaad --enable-shared
make
make install

Si has seguido bien todos los pasos tendrás el software de conversión preparado.

Convirtiendo a 3gp

Para realizar las conversiones algo tan sencillo como:

ffmpeg -y  -i original.avi -s qcif -r 12 -b 30 -ac 1 -ar 8000 -ab 12200 video.3gp
/usr/local/bin/MP4Box -3gp -mtu 1450 -hint video.3gp

Cuando vimos cómo hacer streaming a móviles veíamos como utilizar la utilidad MP4Box para que nuestros vídeos 3gp se puediesen utilizar para emitir en vivo.

Para nuestro proyecto es probable que necesitemos algo más. Si queremos que también se puedan visualizar los vídeos desde una web necesitaremos convertirlos a flv:

ffmpeg -y -i original.avi -acodec libmp3lame -ac 2 -ar 22050 -r 12 -b 196 -s 176x144 video.flv
/usr/bin/flvtool2 u video.flv

Flvtool es una herramienta que inserta en los flv los metadata necesarios para que funcione bien en el reproductor flash.

Y necesitaremos capturar algún fotograma del vídeo para mostrar como demo:

ffmpeg -i original.avi -y -ss 00:00:01 -vframes 1 -an -sameq -s 160x120 -f image2 thumbnail.jpg

Ahora ya sabemos:

  • cómo crear nuestro vídeo 3gp que nos sirva tanto para descarga como para streaming.
  • cómo crear el vídeo flv que nos sirva para ver vía web.
  • cómo generar thumbnails de escenas del vídeo.

Creando el entorno web

Supongamos que queremos desarrollar una web al estilo Youtube donde el usuario sube vídeos desde un formulario y posteriormente se desea mostrarlos categorizados, con un buscador, una preview (flv), descarga para móviles, etc.

La parte de la web propiamente dicha resulta obvia, no vamos a entrar en detalles. Lo que nos ocupa aquí es como realizar la conversión según las instrucciones que hemos visto. Obviamente no podemos ejecutar la conversión cada vez que un usuario sube un vídeo, sería un proceso lento y pesado. La mejor manera es crear una cola de conversión. Cuando se sube un nuevo vídeo no esta disponible públicamente (no se ha convertido todavía). Podríamos definir tres estados diferentes para un video:

  1. Sin procesar (convertir).
  2. Procesando.
  3. Procesado.

Según esto sería sencillo tener en una tabla el estado de los vídeos. Sólo los procesados se listarían públicamente.

Por otro lado tendríamos una tarea encargada de buscar vídeos en estado «sin procesar» y realizar su conversión. Esta tarea podría ser o bien un demonio residente que está permanentemente buscando vídeos o bien, si el tráfico de nuevos vídeos no va a ser elevado, podría ejecutarse periódicamente para realizar las conversiones.

Si el tráfico fuese muy elevado podría incluso haber varias tareas en paralelo ya que una vez una de ellas encuentra un vídeo «no procesado» actualiza su estado a «procesando» de manera que la siguiente tarea ya no escogerá ese mismo vídeo.

La teoría es mucho más sencilla de lo que parece. La tarea que realiza la conversión creará los formatos que estimemos oportunos según ya hemos visto antes.

Convirtiendo a otros formatos

Con ffmpeg se pueden generar los vídeos para casi cualquier formato existente incluyendo PSP, iPod/iPhone/iTouch… Googleando un poco encontrarás los parámetros adecuados para cada formato.

Conclusiones

En este artículo he explicado cómo realizar conversiones de vídeos a distintos formatos y cómo aplicarlo a un entorno web dónde los usuarios suben sus propios vídeos. He dado los pasos básicos para entender el sistema, vosotros tendríais que adaptarlo a vuestras necesidades.

Depurando condiciones Rewrite en Apache

Últimamente he tenido que utilizar bastante ModRewrite de Apache en distintos proyectos, algunos de migración y otros de Internet Móvil donde se necesitan ciertas URL’s para que las operadoras lancen sus sitemas de billing.

Con ModRewrite es muy fácil convertir URLs antiguas al formato nuevo en caso de migración (de manera que no pierdes el SEO que tengas). En el caso del billing de las operadoras móviles es todavía más sencillo puesto que simplemente lo usamos para simular una URL premium. Por ejemplo:

http://tudominio.com/premium.php?noticia=3

Se convierte en:

http://tudominio.com/index.php?noticia=3

De este modo no es necesario hacer scripts independientes y separados por operadora sino que todo queda integrado en nuestro framework.

El problema es que es bastante tedioso depurar lo que hace ModRewrite para ver si las condiciones que has escrito son correctas o si algún filtro no se está ejecutando correctamente. Para solucionarlo lo mejor que podemos hacer es decirle a Apache que «logee» todas las decisiones que va tomando sobre nuestros filtros. Tan fácil como esto:

RewriteEngine on
RewriteLog "/logs/rewrite.log"
RewriteLogLevel 4

RewriteCond %{REQUEST_URI} ^/premium.php$
RewriteRule ^/premium.php /index.php

Este sencillo ejemplo nos permite comprobar qué veremos en el log:

213.xxx.xxx.xxx - - [15/Nov/2008:00:48:23 +0000] [tudominio.com/sid#127d7b0][rid#16b8c20/initial] (3) applying pattern '^/premium.php' to uri '/premium.php'
213.xxx.xxx.xxx - - [15/Nov/2008:00:48:23 +0000] [tudominio.com/sid#127d7b0][rid#16b8c20/initial] (4) RewriteCond: input='/premium.php' pattern='^/premium.php$' => matched
213.xxx.xxx.xxx - - [15/Nov/2008:00:48:23 +0000] [tudominio.com/sid#127d7b0][rid#16b8c20/initial] (2) rewrite /premium.php -> /index.php
213.xxx.xxx.xxx - - [15/Nov/2008:00:48:23 +0000] [tudominio.com/sid#127d7b0][rid#16b8c20/initial] (2) local path result: /index.php
213.xxx.xxx.xxx - - [15/Nov/2008:00:48:23 +0000] [tudominio.com/sid#127d7b0][rid#16b8c20/initial] (2) prefixed with document_root to /var/www/tudominio.com/index.php
213.xxx.xxx.xxx - - [15/Nov/2008:00:48:23 +0000] [tudominio.com/sid#127d7b0][rid#16b8c20/initial] (1) go-ahead with /var/www/tudominio.com/index.php [OK]

Por cada URL que se solicita al servidor, Apache va comprobando las posibles condiciones que has escrito por si coincide con el patrón (matched) o no (not matched). Si la URL cumple los requisitos, la reescribe según se indica en la regla, en caso contrario la deja pasar tal cual. Con toda esta información podemos hacer un completo seguimiento de las reglas que queremos aplicar para comprobar si hay algún error o por qué determinada URL no hace lo que esperamos que haga.

El nivel del log indica la cantidad de información que se guardará, de 0 a 9. A  mayor valor, más información.

Algo muy sencillo pero que pasa desapercibido para la mayoría y acabamos perdiendo muchísimo tiempo probando reglas sin saber qué está ocurriendo en el interior de nuestro servidor web.

Cómo emitir webcams en vivo con un servidor Linux y en formato FLV

Hace un tiempo tuve un cliente que quería visualizar las cámaras que había instalado en su oficina desde su casa. En aquel momento eran cámaras IP cableadas. Este tipo de cámaras tienen un servidor web integrado desde donde puedes configurar su dirección IP además de ver el vídeo que transmiten. Por lo general estas cámaras transmiten en formato mjpeg, un flujo de imágenes JPG normales, aunque también podría ser que lo hiciesen directamente en mpeg. Firefox es capaz de reproducir este formato directamente, no así Internet Explorer que necesita de un applet Java para hacerlo.

El reto consistía, por tanto, en transmitir las cámaras en un formato que se pudiese reproducir fácilmente y que se pudiese integrar en una página web, sin programas de terceros para visualizarlas. Así llegamos a la solución que hoy os presento y que no es exclusiva de cámaras IP sino que puedes utilizar cualquier webcam USB que tengas por casa. El formato que hemos escogido para el flujo de vídeo será flv,  con lo que con una sencilla aplicación Flash podremos visualizar cada una de nuestras cámaras evitando, de paso, los applets Java.

Para el proyecto de hoy necesitaremos:

  • Servidor Linux, imprescindible, en mi caso Centos5
  • Apache instalado en el servidor.
  • ffmpeg, para hacer la conversión de formatos
  • ffserver, parte del paquete ffmpeg, para transmitir elvídeo

Si no tienes el software necesario, el primer paso es instalarlo. En mi caso:

yum install ffmpeg apache

Comenzaremos configurando ffserver desde /etc/ffserver.conf:

Port 8090
BindAddress 0.0.0.0
MaxClients 1000
MaxBandwidth 10000            

<Feed feed1.ffm>
  File /tmp/feed1.ffm
  FileMaxSize 5M
</Feed>
<Feed feed2.ffm>
  File /tmp/feed2.ffm
  FileMaxSize 5M
</Feed>            

<Stream camara1.flv>
  Feed feed1.ffm
  Format flv
  VideoCodec flv
  VideoBitRate 128
  VideoBufferSize 500
  VideoFrameRate 5
  VideoSize 320x240
  NoAudio
  Preroll 5
</Stream>            

<Stream camara2.flv>
  Feed feed2.ffm
  Format flv
  VideoCodec flv
  VideoBitRate 128
  VideoBufferSize 500
  VideoFrameRate 5
  VideoSize 320x240
  NoAudio
  Preroll 5
</Stream>            

<Stream stat.html>
  Format status
</Stream>

ffserver funciona como un servidor, transmitiendo en el puerto que le indiquemos lo que recibe en los archivos que se indican en los feed’s. A continuación se indican los flujos de vídeos que vamos a utilizar (feed’s), en mi caso dos cámaras. Finalmente definimos los streams que será a lo que realmente nos conectaremos nosotros, un stream por cada feed en formato flv. Podríamos crear tantos streams de cada feed como creamos oportuno en distintos formatos, consulta la ayuda de ffserver y ffmpeg.  Finalmente creamos un stream especial, el de status, donde veremos información sobre el estado del servidor y clientes contectados.

Lanzamos el servidor ffserver tal cual.

ffserver

Con esta configuración tendremos acceso a dos flujos de vídeo en formato FLV en las direcciones:

http://localhost:8090/camara1.flv
http://localhost:8090/camara2.flv

Vale, sí, aún no hemos transmitido el vídeo, eso viene ahora. Debemos enlazar el flujo de vídeo de nuestras cámaras con ffserver para que éste haga la conversión de formato y lo emita por las urls indicadas. Para hacerlo recurrimos al inseparable compañero de ffserver, ffmpeg.

Si tus cámaras están conectadas al servidor bien sea por USB o por capturadoras de vídeo (debes tenerlas configuradas previamente):

ffmpeg -f video4linux -s 320x240 -r 5 -i /dev/video0 http://localhost:8090/feed1.ffm
ffmpeg -f video4linux -s 320x240 -r 5 -i /dev/video1 http://localhost:8090/feed2.ffm

Si tus cámaras fuesen IP habría que capturar primero el vídeo desde la cámara y pasárselo a ffmpeg, por ejemplo:

curl http://ip-de-tu-camara:8080 | ffmpeg -er 4 -y -v quiet -an -f mjpeg -r 4 -i - http://localhost:8090/feed1.ffm

Sea como sea el formato de tus cámaras, y si todo ha ido bien, tendrás a ffmpeg enviando el vídeo a ffserver para que lo convierta a FLV y lo publique en las URL’s anteriormente indicadas.

Debes tener cuidado con un detalle importante si conectas más de una cámara USB, debes enchufarlas a concentradores distintos de tu equipo para diversificar adecuadamente los flujos de vídeo y el ancho de banda que ocupan, si conectases dos cámaras al mismo concentrador no tendrían ancho de banda suficiente y se bloquearían entre ellas.

Veamos ahora como visualizar los flujos de vídeo. Necesitaremos el editor de Flash de toda la vida. Creamos un nuevo documento y arrastramos al escenario un objeto FLVPlayback. Como nombre de instancia le ponemos «video» y en sus propiedades ponemos isLive a true. En el primer frame añadimos el siguiente código:

video.contentPath=path_video;

De esta manera podremos indicar desde html la url del flujo de vídeo sin tener que tener un swf por cada cámara. Compila y publica el proyecto puesto que ya hemos terminado.

No me voy a preocupar ahora del código html generado, eso es cosa tuya. Dentro del archivo html que te genera el editor de flash tendrás algo como:

<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=8,0,0,0" width="550" height="400" id="webcam1" align="middle">
<param name="allowScriptAccess" value="sameDomain" />
<param name="movie" value="webcam.swf" />
<param name="quality" value="high" />
<param name="bgcolor" value="#ffffff" />
<embed src="webcam.swf" mce_src="webcam.swf" quality="high" bgcolor="#ffffff" width="550" height="400" name="webcam1" align="middle" allowScriptAccess="sameDomain" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" />
</object>

Vamos a convertirlo en esto:

<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=8,0,0,0" width="550" height="400" id="webcam1" align="middle">
<param name="allowScriptAccess" value="sameDomain" />
<param name="movie" value="webcam.swf?path_video=http://192.168.90.1:8090/camara1.flv" />
<param name="quality" value="high" />
<param name="bgcolor" value="#ffffff" />
<embed src="webcam.swf?path_video=http://192.168.90.1:8090/camara1.flv" mce_src="webcam.swf?path_video=http://192.168.3.2:8090/camara1.flv" quality="high" bgcolor="#ffffff" width="550" height="400" name="webcam1" align="middle" allowScriptAccess="sameDomain" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" />
</object>

Es decir, añadimos al parámetro de la película la variable path_video con la url del flujo tal y como lo hemos generado antes. Debe ser una URL a la que se pueda acceder externamente pues el swf se conectará a ella para recoger el flujo flv generado por ffserver, si tienes un router o un firewall de por medio, deberás abrir ese puerto. Copiaremos este código tantas veces como cámaras tengamos modificando la URL del flujo de vídeo.

Voila! esto es todo, ya tienes publicadas tus cámaras en un formato sencillo de visualizar como es el FLV.  Abre la página html que has creado y comenzarás a visualizar tus cámaras.

Pero… vayamos un paso más allá. Con lo que hemos visto hasta ahora tendremos que abrir el puerto 8090 de buestra máquina para permitir el acceso a los flujos de vídeo, pero nosotros no queremos hacer esto, ya tenemos el puerto 80 con Apache a la escucha. ¿Qué hacemos? Sencillo, mod_proxy viene a nuestro rescate. Asegúrate de tenerlo habilitado en la configuración de tu Apache. Después simplemente habrá que añadir las siguientes líneas a la configuración del virtual host en el que quieres publicar los streams:

ProxyPass /camara1.flv http://127.0.0.1:8090/camara1.flv
ProxyPassReverse /camara1.flv http://127.0.0.1:8090/camara1.flv        

ProxyPass /camara2.flv http://127.0.0.1:8090/camara2.flv
ProxyPassReverse /camara2.flv http://127.0.0.1:8090/camara2.flv

Con esto estaremos enviando automáticamente cada stream desde su url original en el puerto 8090 a una url de tu virtual host del tipo http://tudominio.com/camaraX.flv. Esa ruta no existe físicamente, es virtual y Apache sabe que debe redirigirla al stream del ffserver.

Finalmente queda un detalle importante. Hasta ahora habíamos incializado ffserver por un lado y los flujos de las cámaras por otro con ffmpeg. Esto es un rollo y no nos gusta, queremos que ffserver lo inicie todo automáticamente. Pues vale, añadamos una directiva Launch a cada feed de la configuración de ffserver con los parámetros que utilizábamos para llamarlo desde la línea de comandos. En el ejemplo de nuestra primera cámara quedaría:

<Feed feed1.ffm>
  File /tmp/feed1.ffm
  FileMaxSize 5M
  Launch  ffmpeg -f video4linux -s 320x240 -r 5 -i /dev/video0
</Feed>

A partir de ahora, cada vez que lancemos ffserver tecleando simplemente ese comando, se inciarán, además del propio servidor, los streams que hayamos definido.

Hasta aquí hemos llegado por hoy. De una manera extremadamente sencilla hemos publicado nuestras webcams. Es responsabilidad tuya y sólamente tuya el asegurar el acceso a los flujos y a la página de visualización que hemos creado. Otro día quizás veamos cómo montar un sistema de videovigilancia casero con detector de presencia 😉 .

Os dejo el código de la aplicación Flash de visualización de los streams.

Usando certificados SSL de cliente como sistema de autenticación web

A menudo creamos aplicaciones web con un backend de gestión que, por ser también web, exponemos públicamente a cualquiera que consiga averiguar la URL. Habitualmente estos sistemas son de acceso restringido, sólo un pequeño grupo de usuarios lo utiliza.

En escenarios donde tenemos un número de usuarios acotado y se necesita autentificación, se puede utilizar un mecanismo de certificados que aporten mayor seguridad al sistema, de esta manera solo aquellos usuarios que tengan el certificado en cuestión tendrán acceso a la máquina.

Hoy veremos como permitir el acceso a nuestra aplicación a aquellos usuarios que dispongan de un certificado que previamente les habremos enviado mientras que si no lo tienen no podrán acceder de ningún modo. Este método se puede combinar, además, con el tradicional usuario/clave para dar mayor seguridad. Podremos incluso verificar que el nombre de usuario que se intenta utilizar se corresponde con el certificado de usuario que le hemos enviado y no intenta autentificarse con otro.

Conceptos básicos sobre certificados SSL

El método que vamos a ver se basa en certificados SSL. Se utilizan para asegurar la información entre un cliente y el servidor y prevenir escuchas ya que la información viaja encriptada. Ésta es su función y la hace aunque no esté firmado por una autoridad certificadora (CA) oficial o, incluso, aunque esté caducado. Sigue asegurando las comunicaciones.

Los navegadores web reconocen, por defecto, una serie de autoridades certificadoras como Verisign o Thawte, aunque hay muchas más. Puedes verlas todas en las opciones de tu navegador. Pero, ¿qué es realmente lo que hace una Autoridad Certificadora? Firmar. Firma tu certificado SSL asegurando que os pertenece a ti y a tu dominio. Cuando un cliente accede a tu dominio y descarga el certificado SSL, busca dentro de sus certificados de CA‘s si hay alguno que lo haya firmado. Si lo encuentra, acepta tu certificado y no ocurre nada especial, pero si no encuentra la CA lanza un aviso indicando que no se reconoce la autoridad que lo firma. Esto no quiere decir que el certificado no sea válido, lo único que ocurre es que no sabe quien lo firma. Esto significa, por tanto, que tú mismo puedes ser tu propia autoridad certificadora y firmar tus certificados, funcionarán perfectamente y cumplirán su cometido de asegurar las comunicaciones cliente/servidor.

Comercialmente o en sistemas de acceso público en general, no se recomiendan certificados autofirmados ya que el aviso de autoridad de certificación no reconocida generará desconfianza entre tus usuarios, pero en entorno intranet o de paneles de adminsitración es un método ideal.

El servidor puede requerir, además, otro certificado al cliente, de manera que ámbos extremos autentifiquen la comunicación. Esto es precisamente lo que vamos a hacer hoy en este artículo.

Según lo que hemos explicado, los certificados autofirmados son igual de seguros que los firmados por una autoridad certificadora. Como en el ejemplo que estamos viendo estamos asegurando el acceso a nuestra aplicación para un grupo reducido de usuarios, no hay ningún problema en utilizar un certificado firmado por nosotros mismos ya que nuestros usuarios sabrán que no hay ningún problema. Pero esto no es todo, por esta misma razón podemos decir a los usuarios que se instalen el certificado público de nuestra CA, tal y como hacen las CA oficiales, y automáticamente el navegador comenzará a confiar en nuestros certificados ya que, ahora sí, tiene un certificado de una CA que firma los certificados SSL.

Como resumen, nuestro trabjo consistirá en:

  • Crear nuestra autoridad certificadora y su certificado.
  • Crear el certificado SSL para nuestro servidor web firmado por nuestra CA.
  • Crear los certificados de cliente para nuestros usuarios.
  • Habilitar la lectura de los datos SSL desde PHP.

Servidor web SSL

Utilizaremos el paquete Openssl para generar los certificados. Si aún no lo tienes instalado en tu servidor, es el momento. Explicaré de manera rápida como crear crear certificados SSL para asegurar las comunicaciones ya que es el primer paso necesario para añadir certificados de cliente, sin embargo no es el objeto principal de este artículo y hay mucha documentación, googlea un poco 😉 .

Primero creamos el certificado y la clave privada de nuestra autoridad de certificación:

openssl req -x509 -newkey rsa:2048 -days 3650 -keyout CAXplotaKey.pem -out CAXplotacert.pem

Lo más importante de este comando es el parámetro days, ya que no queremos que dentro de un año nos caduque el certificado de nuestra propia entidad. Yo le pongo 10 años. Este comando genera dos archivos, la clave privada con la que firmaremos nuestros futuros certificados y el certificado con la clave pública que instalaremos, si queremos no recibir avisos, en el navegador. Este comando te pedirá algunos datos (nombre de empresa, país…) y, sobre todo, una contraseña. Deberás recordarla cada vez que vayas a firmar un certificado SSL, así que no la olvides. Ya tenemos nuestra CA creada.

Creamos ahora el certificado SSL para nuestro dominio:

openssl genrsa -des3 -out claveprivada.pem 2048
openssl req -new -key claveprivada.pem -out certificado.pem

El primer comando crea la clave privada de nuestro certificado. Te pedirá otra contraseña, esta vez para la clave privada. Recuérdala también.

El segundo comando genera la petición de certificado sobre la clave privada anterior. Te pedirá la contraseña de la clave privada anterior.

En este punto tenemos cuatro archivos, la clave y el certificado de tu CA y la clave y la solicitud de tu certificado SSL. Sólo queda firmar el certificado con nuestra CA y ya podremos utilizarlo.

Para poder firmarlo debemos generar primero un fichero de texto con algunos parámetros de configuración:

cat configservidor.cnf

basicConstraints = critical,CA:FALSE
extendedKeyUsage = serverAuth

Y firmamos el certificado.

openssl x509 -CA CAXplotacert.pem -CAkey CAXplotaKey.pem -req -extfile configservidor.cnf -in certificado.pem -days 3650 -CAcreateserial -sha1 -out certificado-servidor.pem

Ya tenemos un certificado SSL preparado para utilizar en nuestro servidor web. En nuestro caso es certificado-servidor.pem. Vamos a configurar Apache para que lo utilice.

Habrá que editar httpd.conf y añadir

LoadModule ssl_module modules/mod_ssl.so
Listen 443

Es probable que, si no has compilado tu propio Apache y has instalado un paquete precompilado, tengas ya algún ssl.conf. Activándolo tendrás este paso preparado. En mi caso, un CentOS5, sólo hay que incluir /etc/httpd/conf.d/ssl.conf.

Finalmente hay que indicar en el virtual host que quieres asegurar que use nuestro nuevo certificado. Para hacerlo añadimos un nuevo virtual que escuche en el puerto 443 y añadimos las siguientes líneas:

SSLEngine on
SSLCertificateFile /ruta/a/certificado-servidor.pem
SSLCertificateKeyFile /ruta/a/claveprivada.pem

Si ahora reinicias Apache y accedes a tu dominio con https verás que en tu navegador aparece el candado indicando que la información es segura. Verás también que te aparece un aviso de que no se confía en la autoridad certificadora. Veremos más adelante como solucionarlo.

Añadiendo certificados de cliente

Ahora que ya tenemos nuestro servidor web seguro con nuestros certificado autofirmado llega el momento de crear certificados para nuestros clientes de manera que si alguien intenta acceder a nuestra aplicación sin uno de ellos se le prohíba el paso.

Crearemos primero un archivo de configuración con los parámetros que necesitaremos.

cat configcliente.cnf

basicConstraints = critical,CA:FALSE
extendedKeyUsage = clientAuth

Con esto daremos instrucciones de que es un certificado cliente a la hora de firmar el certificado.

Creamos ahora, igual que hacíamos antes, la clave privada y la solicitud de certificado.

openssl genrsa -des3 -out clave-cliente.pem 2048
openssl req -new -key clave-cliente.pem  -out certificado-cliente-req.pem

Como antes, al generar la clave te pedirá una contraseña que deberás introducir después, al hacer la solicitud de certificado. Los datos que te pide esta solicitud, como ocurría antes, los podrás leer posteriormente para comprobar datos o lo que estimes oportuno, así que es importante que prestes atención.

Firmamos ahora el certificado con nuestra CA:

openssl x509 -CA CAXplotacert.pem -CAkey CAXplotaKey.pem -req -in certificado-cliente-req.pem -days 3650 -extfile configcliente.cnf  -CAcreateserial -sha1 -out certificado-cliente.pem

Como véis, el proceso es el mismo para generar el certificado de servidor, pero cambiamos el contenido del archivo de configuración que le indica que es un certificado cliente, no servidor.

Vale, bien, pero ¿no quedamos que es el cliente el que debe instalar el certificado? Sí, ahí vamos ahora. El certificado que acabamos de generar lo debes instalar en tu navegador web, no en el servidor, así que habrá que convertirlo a algún formato que puedan entender. Para esto hacemos lo siguiente:

openssl pkcs12 -export -in certificado-cliente.pem -inkey clave-cliente.pem -certfile CAXplotacert.pem -out cliente.p12

Nos pedirá la contraseña de la clave privada del certificado y nos solicitará otra para el que va a generar. Es importante poner contraseña al certificado final ya que es el que vas a enviar a tus usuarios y pretendes que sólo estos puedan utilizarlo, así que poner una contraseña nunca está demás.

Ya tienes tu certificado cliente.p12. Puedes probarlo tu mismo. Ah no, espera, que no hemos configurado el servidor web para que solicite sí o sí un certificado al cliente. Añade a la configuración SSL de tu virtual host de Apache:

SSLCACertificateFile /path/a/CAXplotacert.pem
SSLVerifyClient require

Y reinicia tu servidor web. Si ahora intentas acceder a tu dominio verás como salta una ventana solicitando un certificado que no tienes. Desde las opciones de tu navegador, busca la sección de certificados SSL y añade un nuevo certificado. Selecciona tu cliente.p12. Te pedirá la contraseña que pusiste al convertirlo a pk12. Ya está. Accede ahora y verás como te aparece el certificado en la ventana de certificiados disponibles y, si lo seleccionas, te deja acceder a tu aplicación.

Revocar certificados

A veces puede ser necesario prohibir el paso con determinados certificados, bien porque el usuario ya no colabora o trabaja contigo, bien porque hay posibilidades de que el certificado haya sido robado, etc. Una opción es, a la hora de crear el certificado, y si sabes de antemano que el usuario lo necesitará pocos días (cuenta de prueba, usuario esporádico o cualquier razón similar), generarlo con una validez limitada (10 días, un mes, etc.), de modo que pasado este tiempo el certificado caduca y el usuario no puede entrar. El problema más importante lo tendrás cuando necesites prohibir el acceso a usuarios con certificados a largo plazo. Para hacerlo debemos crear una lista de revocación de certificados.

En mi caso (CentOS5 y RHEL5) tuve algunos problemas de configuración. Openssl debería de mantener una lista de los certificados que emite y sus números de serie sin embargo tal como hemos generado nuestros certificados no lo hace. Para solucionarlo, creamos a mano la lista.

touch /etc/pki/CA/index.txt

Ahora debemos editar el archivo de configuración de Openssl para reflejar algunos parámetros. Por defecto indica una ruta relativa hacia los directorios donde dejará los certificados, pero no me funcionó bien hasta que no le puse rutas absolutas. Modificamos pues la ruta al directorio y le indicamos donde están la clave privada y el certificado de nuestra CA.

Editar /etc/pki/tls/openssl.cnf

dir             = /etc/pki/CA              # Where everything is kept
certificate     = /path/a/CAXplotacert.pem    # The CA certificate
private_key     = /path/a/CAXplotaKey.pem # The private key

Ahora sí, podemos revocar un certificado de manera tan sencilla como:

openssl ca -revoke certificado-cliente.pem

A partir de la lista con el estado de los certificados debemos crear una lista de revocación que usaremos para indicar al servidor web los certificados que no debe permitir.

openssl ca -gencrl -out recovados.crl

Es posible que al lanzar este comando te apareza otro error relacionado con crlnumber. Esto viene relacionado con lo que os indicaba antes y es simplemente que no existe el archivo de números de serie. Para solucionarlo, simplemente lo creamos:

echo "01" > /etc/pki/CA/crlnumber

Y volvemos a generar la lista de revocación. Cada vez que revoques un certificado deberás repetir esta operación para tener la nueva lista.

Ya sólo falta indicarle a Apache qué certificados no debe permitir. Añadimos a la configuración de nuestro virtual host, en el mismo sitio donde configuramos anteriormente el certificado SSL, la siguiente línea:

SSLCARevocationFile path/a/revocados.crl

Eso es todo, ya tenemos nuestro servido SSL funcionando, nuestra aplicación protegida con certificados de cliente y la opción para revocar certificados.

Comprobando los certificados con PHP

Queremos ir un poco más allá. Queremos que, desde nuestra aplicación, podamos leer el certificado SSL del cliente y comprobar quién es. Seguro que a ti se te ocurren mejores cosas que hacer con estos datos 😉 . Lo primero que tendremos que hacer será configurar Apache para que pase los datos del certificado. En la configuración de tu virtual host añades:

<Files ~ ".(cgi|shtml|phtml|php?)$">
 SSLOptions +StdEnvVars +ExportCertData
</Files>

Ya está. Si ahora haces un print_r($_SERVER) en la aplicación donde tienes instalado tu certificado SSL de cliente verás entre todos los datos algo como:

[SSL_CLIENT_S_DN] => /C=ES/ST=Valencia/L=Valencia/O=Osusnet/CN=blog.osusnet.com/emailAddress=osusENosusnet.com
[SSL_CLIENT_S_DN_C] => ES
[SSL_CLIENT_S_DN_ST] => Valencia
[SSL_CLIENT_S_DN_L] => Valencia
[SSL_CLIENT_S_DN_O] => Osusnet
[SSL_CLIENT_S_DN_CN] => blog.osusnet.com
[SSL_CLIENT_S_DN_Email] => osusENosusnet.com

Tienes toda la información que necesitas, son los datos que se solicitaban al crear la solicitud de certificado. Generando estos parámetros de manera adecuada podrás saber quién entra a tu aplicación a partir del certificado cliente que usa.

Eso es todo por hoy. De una manera muy sencilla hemos añadido un poco más de seguridad a una aplicación web de acceso privado y con un número de usuarios acotado. También hemos aprendido y poco más sobre certificados SSL y hemos visto cómo firmar nuestros propios certificados, igual de fiables que los comerciales. Espero que os sirva de algo 😛 .