Ejecutar como servicio un programa que no es demonizable
Hoy estuve lidiando con una aplicación que se ejecuta desde consola y queda escuchando en un puerto, pero que no provee la posibilidad de correr como demonio (tipo Apache, sshd, etc). Para no tener que loguear un usuario y dejar la aplicación en ejecución en alguna consola colgada, mejor es convertirla en un servicio. Mi caso en particular se dió con la aplicación CherryMusic, que permite compartir musica a través de una interfaz web.

Como primer paso, vamos a crear un directorio donde alojar la aplicación, y para un caso como el que describo, creo que el mejor lugar es /opt:
# mkdir /opt/cherrymusic
Para cherrymusic, si hacen un clone del proyecto, el directorio se crea sólo, lo mismo si descomprimen un tar.gz.

A continuación creemos el usuario con el que se ejecutará la aplicación:
# useradd cherrymusic -d /opt/cherrymusic -s /bin/false
El comando anterior indica que el home del usuario será /opt/cherrymusic y que utilice /bin/false como shell... es decir, que no tenga shell.

Para ver qué sucede con nuestra aplicación, estaría bueno ver algún log, así que creemos uno, con los permisos necesarios para que la aplicación pueda escribir:
# touch /var/log/cherrymusic
# chown cherrymusic /var/log/cherrymusic
La ejecución del programa la haremos de la siguiente forma:
# sudo -u cherrymusic -H /usr/bin/python /opt/cherrymusic/cherrymusic --port 8080 &>>/var/log/cherrymusic
Esto es, le decimos que ejecute la aplicación con el usuario cherrymusic (-u), usando el home de dicho usuario (-H), y redirigimos la salida a /var/log/cherrymusic

Como último paso, creamos un init script. Si usamos el viejo estándar, podemos meter un script como el siguiente en /etc/init.d/cherrymusic
#!/bin/bash
### BEGIN INIT INFO
# Provides:          cherrymusic
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
### END INIT INFO
case "$1" in
  start)
    iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8080
    sudo -u cherrymusic -H /usr/bin/python /opt/cherrymusic/cherrymusic --port 8080 &>>/var/log/cherrymusic
    ;;
  stop)
    iptables -t nat -D PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8080
    killall -u cherrymusic
    ;;
  *)
    echo "usage: $0 <start | stop>"
    ;;
esac
Ok, tal vez un killall no es lo mejor, pero sirve para mostrar un script muy simple.
Al script le agregué un "plus" que es levantar una regla iptables que redirija lo que llegue por el puerto 80, al 8080. Esto se debe a que, al ejecutar el script con un usuario no privilegiado, el mismo no se puede hookear al puerto 80.
Faltaría sólo agregarlo para que se ejecute al inicio:
  # update-rc.d cherrymusic defaults
Si quisieramos hacer lo mismo, pero utilizando upstart, podemos crear el siguiente archivo en /etc/init/cherrymusic.conf:
# Cherry Music
#
start on runlevel [2345]
stop on runlevel [!2345]
script
  iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8080
  sudo -u cherrymusic -H /usr/bin/python /opt/cherrymusic/cherrymusic --port 8080 &>>/var/log/cherrymusic
end script
post-stop script
  iptables -t nat -D PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8080
end script
Eso es todo, ya pueden utilizar "service cherrymusic start" y "service cherrymusic stop", y además la aplicación se ejecutará cada vez que se encienda el equipo. Los pasos serían los mismos si ejecutaran cualquier otra aplicación =)
ejabberd LDAP, certificados y clustering
Después de pelear durante un par de días con ejabberd, me pareció interesante compartir la experiencia ganada en el proceso, ya que no todo es tan directo como parece. La documentación oficial está buena, pero la encontré un poco escueta, por lo que si no usas una configuración similar a la de los ejemplos, no sabes bien qué poner en cada parámetro.
Ejabberd está escrito en lenguaje Erlang, y utiliza el formato de este lenguaje para su archivo de configuración. Si bien no es complicado, no es a lo que uno está acostumbrado.

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


Instalación

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


Configuración básica

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

Habilitar LDAP

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

Usar grupos de LDAP como grupos en los rosters

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

Instalar certificado propio

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

Timeouts

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

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

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

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


Clustering 

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

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


Configuración Master

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

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

Configuración Slave

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

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


Referencias



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

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

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


1. Configurar OpenSSL

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

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

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

2. Generar clave y certificado de CA

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


3. Firmar certificados

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

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

4. Revocar certificados

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

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

Referencias

How To Setup a CA