Configuración de un túnel para el servidor de correo

Rationale

Ya tenemos un servidor de correo, configurado para poder ser alcanzado en Internet. Sin embargo, surgen ciertos problemas debido a la red donde está conectado el servidor Cactus.

  • Durante la fase de desarrollo, el servidor Cactus está conectado a una red doméstica, con una dirección IP dinámica. Las direcciones de este tipo son publicitadas por los operadores a los organismos de listas negras de spam para que las bloqueen, puesto que es raro que un usuario doméstico mantenga su propio servidor de correo, mientras que es muy frecuente el caso de todo tipo de malware que conecta a servidores SMTP aleatorios con el fin de introducir correo basura o malicioso.
  • El firewall de la red de la usal tiene bloqueadas las conexiones TCP salientes por el puerto 25, lo que impide que el servidor de correo de Cactus pueda relacionarse con otros servidores de correo. Esta restricción suele introducirse en las redes con muchos equipos para limitar la difusión de spam y virus. Las conexiones SMTP de los usuarios a sus respectivos servidores de correo deben hacerse por el puerto submission (587). Más información sobre este puerto en Puerto submission.
  • El router de la red de la usal implementa NAT, por lo que el servidor Cactus no tiene IP pública habilitada en el momento de la defensa, y por tanto le es imposible recibir directamente mensajes de otros servidores.

Introducción

Para afrontar este problema utilizaremos un túnel con el servidor rufian.eu. Este servidor tiene una IP estática y buena reputación, por lo que la mayoría de servidores de correo de Internet aceptarán mensajes procedentes de él. Este servidor ya tiene instalado y configurado un servidor de correo, independiente del de Cactus.

Nuestro objetivo es aprovechar este servidor auxiliar para conectar a Cactus con el exterior, de manera que pueda enviar y recibir mensajes en Internet independientemente de la red donde se encuentre.

La solución al problema se divide en tres partes:

  • Habilitar un usuario capaz de crear túneles
  • Lograr enviar correo
  • Lograr recibir correo

Habilitar un usuario capaz de crear túneles

En el servidor rufian.eu habilito un nuevo usuario, cactus que será usado por el servidor Cactus para autenticarse y obtener un túnel en el momento del arranque.

Ejecuto la orden useradd en rufian.eu para crear el nuevo usuario. Le asigno la shell nula, pues no quiero que sea capaz de ejecutar órdenes en el servidor.

useradd -m -s /bin/false cactus

Todavía en rufian.eu, edito el fichero /etc/ssh/sshd_config para dar permiso al usuario cactus para crear túneles. Sólo le permito abrir un túnel directo hacia el servidor de correo.

Match User cactus
    AllowTcpForwarding yes
    GatewayPorts clientspecified
    PermitOpen 127.0.0.1:25

Nota

GatewayPorts clientspecified permite al usuario crear túneles inversos en cualquier dirección y puerto (mayor de 1023).

Desgraciadamente, SSH no tiene ninguna opción para limitar este acceso a sólo unas direcciones y puertos determinados (cosa que sí permite con los túneles directos).

Se puede imponer esta restricción mediante SELinux. Más adelante se propondrá una solución alternativa que no requiere SELinux.

Reinicio el servidor ssh de rufian.eu para que la nueva configuración surta efecto.

/etc/init.d/sshd restart

Ahora en Cactus, creo el usuario que ejecutará los túneles. Lo llamaré tunnels.

useradd -m tunnels

Creo un par de claves RSA para dicho usuario, sin frase de paso.

su - tunnels -c "ssh-keygen -N '' -t rsa"

Copio la clave pública de tunnels@cactus.rufian.eu. Dicha clave está en el siguiente fichero:

/home/tunnels/.ssh/id_rsa.pub

Pego en el fichero de equipos autorizados del usuario cactus de rufian.eu. Para ello puedo ejecutar las siguientes órdenes en la shell de rufian.eu.

mkdir /home/cactus/.ssh
cat >> /home/cactus/.ssh/authorized_keys
<Ctrl+Mayus+V>
<Ctrl+D>

Desde rufian.eu creo un fichero vacío en /home/cactus/.hushlogin para evitar que salga el motd al iniciar sesión (lo cual evita ensuciar el log de arranque).

touch /home/cactus/.hushlogin

Lograr enviar correo

Creación del túnel

Ejecuto desde Cactus la siguiente orden:

su - tunnels -c 'ssh -q -N -L 127.0.0.25:2500:127.0.0.1:25 cactus@rufian.eu' &

Esta orden crea un túnel SSH directo, con las siguientes características:

  • Los datos viajarán por SSH estándar, cifrado, funcionando mediante TCP en el puerto 22.

    El puerto 22 está abierto en el firewall, por lo que por esta conexión podemos transmitir lo que queramos.

  • En el equipo Cactus, el ejecutable del cliente ssh crea un socket que escucha conexiones TCP a la dirección IP 127.0.0.25 (una dirección de loopback) en el puerto 2500.

  • Cuando un proceso se conecte al socket del punto anterior y envíe datos, ssh enviará la información de conexión y datos al servidor rufian.eu, codificados en un paquete de SSH (el cual, como dijimos en el primer punto, puede atravesar el firewall).

  • El servidor ssh de rufian.eu decodifica los datos recibidos, abre una conexión TCP a la dirección IP 127.0.0.1 por el puerto 25, y envía por ella los datos que ha decodificado. De forma similar, cuando recibe datos por esa conexión, los codifica y los envía de vuelta a Cactus a través del túnel.

En conclusión, una vez montado este artilugio, tenemos virtualmente el servidor SMTP de rufian.eu en el puerto 2500 de la interfaz de loopback de Cactus.

Podemos probar que el túnel efectivamente está funcionando con nc.

-> nc 127.0.0.25 2500
220 rufian.eu ESMTP Exim 4.80.1 Sat, 04 May 2013 23:04:34 +0200

Configuración de Postfix (Cactus)

Ahora queda configurar Postfix en Cactus para que no haga entrega directa de los mensajes que no vayan a cactus.rufian.eu, sino que los entregue a rufian.eu a través del túnel. Esto se configura en /etc/postfix/main.cf, con la variable relayhost.

relayhost = 127.0.0.25:2500

Nota

El lector se puede estar preguntando por qué en este documento utilizamos la dirección 127.0.0.25 en vez de la tan habitual 127.0.0.1.

La razón es que, tanto Postfix como Exim4 tienen restringida la dirección 127.0.0.1 (asociada a localhost), porque habitualmente significaría que el mensaje se transmite al mismo equipo que lo recibió, generándose un bucle infinito. Esto no ocurre en nuestro caso, puesto que estamos relevando siempre a un puerto distinto en el que escucha un proceso distinto y en la comunicación no se producen ciclos.

A pesar de que tanto Postfix como Exim4 restringen 127.0.0.1, ambos admiten cualquier dirección IP distinta del bloque de loopback, por lo que utilizamos otra, 127.0.0.25.

Puesto que el túnel SSH ya va cifrado, no es necesario volver a cifrar el SMTP saliente. Además, tenerlo activado generará avisos en el log de que el certificado no es correcto, puesto que el certificado ofrecido por rufian.eu no autentica a 127.0.0.25, como es esperable.

Podemos desactivar el cifrado en el SMTP saliente editando la variable smtp_use_tls.

smtp_use_tls = no

Prueba

Una vez hechos los cambios, reinicio Postfix y envío un mensaje de prueba con mutt.

mutt rufian@usal.es -s "Test message"

En el log de Postfix, /var/log/mail.info se puede ver como el mensaje se ha relevado al túnel.

May  5 02:39:33 cactus postfix/pickup[15809]: 237C5158CF5: uid=0 from=<root>
May  5 02:39:33 cactus postfix/cleanup[15861]: 237C5158CF5: message-id=<20130505003933.GA15842@cactus.rufian.eu>
May  5 02:39:33 cactus postfix/qmgr[15810]: 237C5158CF5: from=<root@cactus.rufian.eu>, size=424, nrcpt=1 (queue active)
May  5 02:39:33 cactus postfix/smtp[15864]: 237C5158CF5: to=<rufian@usal.es>, relay=127.0.0.25[127.0.0.25]:2500, delay=0.46, delays=0.02/0.01/0.14/0.3, dsn=2.0.0, status=sent (250 OK id=1UYmzF-0007LX-9o)
May  5 02:39:33 cactus postfix/qmgr[15810]: 237C5158CF5: removed

Así mismo, en el log de Exim4 de rufian.eu se puede ver como el mensaje se ha recibido desde el túnel y se ha entregado en usal.es.

2013-05-05 02:39:33 1UYmzF-0007LX-9o <= root@cactus.rufian.eu H=localhost (cactus.rufian.eu) [127.0.0.1] P=esmtp S=626 id=20130505003933.GA15842@cactus.rufian.eu
2013-05-05 02:39:54 1UYmzF-0007LX-9o => rufian@usal.es R=dnslookup T=remote_smtp H=mx01.puc.rediris.es [130.206.18.129] X=TLSv1:DHE-RSA-AES256-SHA:256
2013-05-05 02:39:54 1UYmzF-0007LX-9o Completed

Nota

El lector puede estar preguntándose cómo ha funcionado esto sin cambiar una sola línea de la configuración de Exim4 en rufian.eu.

El truco está en esta línea de exim.conf (la cual tiene el valor por defecto).

hostlist relay_from_hosts = 127.0.0.1

Con esta configuración Exim4 releva el correo que proceda de procesos de la máquina local. Como los mensajes están llegando a Exim4 a través de un túnel, que es un proceso de la máquina, los mensajes son relevados sin problema.

Lograr recibir correo

Creación del túnel inverso

Para la recepción de correo necesitamos un túnel SSH inverso, de manera que expongamos el servidor de correo de Cactus a rufian.eu.

Para crear el túnel inverso ejecutamos desde Cactus la siguiente orden:

su - tunnels -c 'ssh -q -N -R 127.0.0.25:2500:127.0.0.1:25 cactus@rufian.eu' &

El funcionamiento del túnel inverso es similar al túnel directo, con la diferencia de que es el equipo servidor (rufian.eu) quien crea el socket y a través de él puede conectarse a un proceso del equipo cliente (Cactus).

Podemos probar el túnel inverso desde rufian.eu ejecutando:

-> nc 127.0.0.25 2500
220 cactus.rufian.eu ESMTP Postfix Si Me Temes, Para

Con esto tenemos los dos servidores comunicados.

Registro MX

El siguiente problema que tenemos que solucionar es el registro MX. Cuando un servidor de correo de Internet (por ejemplo, usal.es) quiere enviar un mensaje a otro servidor primero tiene que determinar la dirección IP de dicho servidor. Para esto consulta su registro MX, y en caso de no existir, el registro A.

Durante la defensa de la práctica el registro A de cactus.rufian.eu apuntará a una dirección IP privada, por lo que es imprescindible crear un registro MX que apunte a una dirección globalmente accesible.

Edito el fichero de zona de rufian.eu y añado un registro MX para cactus.rufian.eu, de manera que apunte a la IP de nuestro servidor auxiliar.

cactus      IN      MX      10  mail
mail        IN      A       46.105.20.51

Nota

mail.rufian.eu apunta la misma dirección IP de rufian.eu, nuestro servidor auxiliar. No es la dirección IP de Cactus.

Configuración de Exim4 (rufian.eu)

Con la configuración hecha hasta el momento, los servidores de Internet enviarán el correo con destino cactus.rufian.eu a rufian.eu, pero este lo rechazará al no estar configurado como responsable de tal dominio.

Para que rufian.eu acepte e-mail con destino a cactus.rufian.eu hay que modificar la variable de Exim4 relay_to_domains, en /etc/exim/exim.conf.

domainlist relay_to_domains = cactus.rufian.eu

Con esta configuración, Exim4 aceptará recibir correo con destino a Cactus.

El comportamiento predeterminado para la entrega de correo es hacer una consulta DNS por el nombre de dominio y entregar el mensaje a la dirección IP resuelta (a la del registro MX, o del registro A en ausencia del primero). Este comportamiento no nos vale puesto que el servidor se entregaría el mensaje a sí mismo. En lugar de eso, necesitamos que el mensaje viaje por el túnel.

Modificaremos el comportamiento de enrutado de Exim4 de manera que para los mensajes con destino a cactus.rufian.eu no realice consulta DNS, sino que los envíe directamente por el túnel, mientras que para el resto de correo mantenga el comportamiento por defecto.

Del código abajo mostrado, la sección cactusmail es nueva, mientras que el resto pertenecen a la configuración por defecto, y se proporciona como referencia.

begin routers

cactusmail:
  driver = manualroute
  transport = remote_smtp
  domains = cactus.rufian.eu
  route_list = cactus.rufian.eu 127.0.0.25::2500

dnslookup:
  driver = dnslookup
  domains = ! +local_domains
  transport = remote_smtp
  ignore_target_hosts = 0.0.0.0 : 127.0.0.0/8
  no_more

Prueba

Envío un mensaje desde el correo de la usal a ntrrgc@cactus.rufian.eu.

En el log de rufian.eu puedo ver como el mensaje ha llegado de los servidores de la usal y es enviado por el túnel.

2013-05-05 20:52:58 1UZ43N-0004u5-Sg DKIM: d=usal.es s=mail c=simple/simple a=rsa-sha256 t=1367779974 [verification succeeded]
2013-05-05 20:52:58 1UZ43N-0004u5-Sg <= rufian@usal.es H=relay1.usal.es [212.128.129.62] P=esmtp S=8475 id=5186AA83.6020809@usal.es
2013-05-05 20:52:58 1UZ43N-0004u5-Sg => ntrrgc@cactus.rufian.eu R=cactusmail T=remote_smtp H=127.0.0.25 [127.0.0.25] X=TLSv1:DHE-RSA-AES256-SHA:256
2013-05-05 20:52:58 1UZ43N-0004u5-Sg Completed

En el log de Cactus puedo ver como el mensaje ha llegado por el túnel y se ha entregado al usuario correctamente.

May  5 20:53:00 cactus postfix/smtpd[17571]: connect from localhost[127.0.0.1]
May  5 20:53:01 cactus postfix/smtpd[17571]: 1FC25158CC6: client=localhost[127.0.0.1]
May  5 20:53:01 cactus postfix/cleanup[17574]: 1FC25158CC6: message-id=<5186AA83.6020809@usal.es>
May  5 20:53:01 cactus postfix/qmgr[15810]: 1FC25158CC6: from=<rufian@usal.es>, size=8792, nrcpt=1 (queue active)
May  5 20:53:01 cactus postfix/local[17575]: 1FC25158CC6: to=<ntrrgc@cactus.rufian.eu>, relay=local, delay=0.1, delays=0.09/0/0/0, dsn=2.0.0, status=sent (delivered to mailbox)
May  5 20:53:01 cactus postfix/qmgr[15810]: 1FC25158CC6: removed
May  5 20:53:01 cactus postfix/smtpd[17571]: disconnect from localhost[127.0.0.1]

Y por supuesto, puedo ejecutar mutt y abrir el mensaje.

su - ntrrgc -c mutt

Mejora de seguridad (rufian.eu)

La opción GatewayPorts clientspecified permite al cliente poner un servidor en cualquier dirección y puerto del servidor, tunelado hasta su equipo.

Esto podemos restringirlo estableciendo GatewayPorts no (valor por defecto), con lo que, con independencia de la dirección que especifiquemos, el servidor siempre escuchará en 127.0.0.1 y nunca en otra dirección.

Sin embargo, Exim4 no puede conectar a 127.0.0.1, debe conectar a 127.0.0.25. Podemos hacer un apaño con iptables.

iptables -t nat -A OUTPUT -p tcp -d 127.0.0.25 --destination-port 2500 \
        -j DNAT --to-destination 127.0.0.1
/etc/init.d/iptables save

Nota

/etc/init.d/iptables save es una orden de Gentoo para persistir la configuración de iptables en sucesivos arranques del sistema. En Debian se hace diferente.

Esta orden hace que netfilter modifique los paquetes que se creen con destino a 127.0.0.25 en el puerto TCP 2500 y cambie la dirección de destino por 127.0.0.1, haciendo así mismo la traslación inversa: una vez establecida la conexión, cuando 127.0.0.1 responda, netfilter cambiará la dirección de origen del paquete respuesta por 127.0.0.25.

De esta forma cuando Exim4 hable con 127.0.0.25 estará hablando en realidad con 127.0.0.1 pero no tiene forma de darse cuenta.

Ahora que hemos respuelto este inconveniente, editamos /etc/ssh/sshd_config y asignamos la nueva restricción.

Match User cactus
    AllowTcpForwarding yes
    GatewayPorts no
    PermitOpen 127.0.0.1:25

Reiniciamos el servidor SSH.

/etc/init.d/sshd restart

Reiniciamos el túnel (matando con kill el PID del proceso túnel y volviéndolo a iniciar) y probamos que todo sigue funcionando.

Iniciar el túnel con el sistema

Tenemos el (doble) túnel, pero no queremos tener que arrancarlo manualmente cada vez que arranquemos Debian.

Para solucionarlo, creamos un par de initscripts.

/etc/init.d/tunnel-smtp-outgoing

### BEGIN INIT INFO
# Provides:             smtp-tunnel-outgoing
# Required-Start:       $syslog
# Required-Stop:        $syslog
# Default-Start:        2 3 4 5
# Default-Stop:         0 1 6
# Short-Description:    Allows SMTP message submission through a tunnel server.
### END INIT INFO

NAME=tunnel-smtp-outgoing
LONG_NAME="tunnel for outgoing SMTP"
TUNNEL_USER=tunnels
TUNNEL_BIN=/usr/bin/ssh
TUNNEL_OPTIONS="-q -N -L 127.0.0.25:2500:127.0.0.1:25 cactus@rufian.eu"
TUNNEL_PIDFILE="/var/run/$NAME.pid"

test -x $TUNNEL_BIN || exit 1

# Define LSB log_* functions.
. /lib/lsb/init-functions


d_start() {
        start-stop-daemon --background --make-pidfile --start --quiet --pidfile $TUNNEL_PIDFILE \
                --chuid $TUNNEL_USER --exec $TUNNEL_BIN -- $TUNNEL_OPTIONS
}


d_stop() {
        start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $TUNNEL_PIDFILE \
                --user $TUNNEL_USER --exec $TUNNEL_BIN
}

case "$1" in
  start)
        log_daemon_msg "Starting $LONG_NAME" $NAME
        d_start
        log_end_msg $?
        ;;
  stop)
        log_daemon_msg "Stopping $LONG_NAME" $NAME
        d_stop
        log_end_msg $?
        ;;

  restart)
        log_daemon_msg "Restarting $LONG_NAME" $NAME
        d_stop
        d_start
        echo "."
        ;;
  status)
        echo -n "$NAME "
        status_of_proc -p $TUNNEL_PIDFILE $TUNNEL_BIN $TUNNEL || exit $?
        ;;

  *)
        log_action_msg "Usage: $NAME {start|stop|status|restart}" >&2
        exit 2
        ;;
esac

exit 0

/etc/init.d/tunnel-smtp-incoming

### BEGIN INIT INFO
# Provides:             smtp-tunnel-incoming
# Required-Start:       $syslog
# Required-Stop:        $syslog
# Default-Start:        2 3 4 5
# Default-Stop:         0 1 6
# Short-Description:    Allows SMTP messages to be received from a tunnel server.
### END INIT INFO

NAME=tunnel-smtp-incoming
LONG_NAME="tunnel for incomming SMTP"
TUNNEL_USER=tunnels
TUNNEL_BIN=/usr/bin/ssh
TUNNEL_OPTIONS="-q -N -R 127.0.0.25:2500:127.0.0.1:25 cactus@rufian.eu"
TUNNEL_PIDFILE="/var/run/$NAME.pid"

test -x $TUNNEL_BIN || exit 1

# Define LSB log_* functions.
. /lib/lsb/init-functions


d_start() {
        start-stop-daemon --background --make-pidfile --start --quiet --pidfile $TUNNEL_PIDFILE \
                --chuid $TUNNEL_USER --exec $TUNNEL_BIN -- $TUNNEL_OPTIONS
}


d_stop() {
        start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $TUNNEL_PIDFILE \
                --user $TUNNEL_USER --exec $TUNNEL_BIN
}

case "$1" in
  start)
        log_daemon_msg "Starting $LONG_NAME" $NAME
        d_start
        log_end_msg $?
        ;;
  stop)
        log_daemon_msg "Stopping $LONG_NAME" $NAME
        d_stop
        log_end_msg $?
        ;;

  restart)
        log_daemon_msg "Restarting $LONG_NAME" $NAME
        d_stop
        d_start
        echo "."
        ;;
  status)
        echo -n "$NAME "
        status_of_proc -p $TUNNEL_PIDFILE $TUNNEL_BIN $TUNNEL || exit $?
        ;;

  *)
        log_action_msg "Usage: $NAME {start|stop|status|restart}" >&2
        exit 2
        ;;
esac

exit 0

Instalamos los servicios para que se inicien con el sistema.

insmod tunnel-smtp-outgoing
insmod tunnel-smtp-incoming

Podemos reiniciar para comprobar que efectivamente, el túnel se inicia con el arranque y podemos enviar y recibir mensajes.