Contén tu información en una VPN utilizando Docker

En primer lugar, quisiera dar las gracias a los conejos por darme esta oportunidad para escribir en el blog. Desde que conocí el blog (he visto que mi primera interacción con su cuenta de Twitter fue a los pocos días de su nacimiento), me di cuenta de que iba a ser un blog a tener en cuenta para todos aquellos que nos gusta la seguridad informática, como luego se demostró al ganar el premio Bitácoras al mejor blog en la categoría de Seguridad Informática tras pocos meses de vida, o ser los organizadores de un track completo en la RootedCON 2018. Además, hace tiempo que me siento en deuda con ellos, no sólo por todo lo que me han ayudado a aprender con el blog o lo que aportan a través de su canal de Telegram, sino también a nivel humano. Conozco a casi todos sus miembros (Juan, ¿quién es Juan?), he tenido la oportunidad de coincidir con ellos en varias ocasiones, y desde el primer momento te reciben con los brazos abiertos y te regalan muchas risas y buen rollo (durísimas declaraciones). Como digo, es para mí una oportunidad devolverles una parte de lo que me han aportado.

Muy bien. Así me gusta, todo bien agrupadito en un único párrafo. Pero ya has repartido suficiente cariño, ¿no? Tenéis razón, voces de mi cabeza, vamos al lío. Menos mal que no ha sido la voz del Señor Lobo, la que ha dicho lo del cariño, ¿verdad? He dicho que vamos al lío.

Como igual habéis podido deducir por el título de este post, el objetivo del mismo es mostrar cómo instalar y configurar un servidor OpenVPN utilizando containers de Docker. ¿Por qué queréis dockerizar todo? No llego a comprenderlo… Dockerizar está bien para lo que se diseñó… pero no significa que tenga que usarse en todos los lados. ¿Por qué utilizar Docker para esto? En primer lugar, porque puedo. Anda que no hay tutoriales por ahí de cómo instalar y configurar OpenVPN en un servidor. Por lo que hacer un post sobre eso no sé si aportaría nada nuevo (ya, como si esto que vas a escribir tú sí lo hiciese). Además, se me ocurren por ejemplo un par de casos de uso perfectamente válidos, que, ojo cuidao, no digo que me haya visto yo en alguno de ellos. O en ambos. Pero para nada. Por favor, tengan en cuenta que ésta no es una lista exhaustiva de cuándo utilizar Docker para montar un servidor OpenVPN:

  • En un entorno corporativo, se va a establecer una prueba de concepto de unos pocos usuarios para utilizar OpenVPN para conectarse a ciertos servicios que se encuentran alojados en el data center de la empresa. Una vez que se valore cómo se adaptan los usuarios de prueba a este servicio de VPN, se extenderá a todos los usuarios de la empresa que lo requieran, en cuyo caso se trasladaría el servidor fuera del contenedor de Docker. Si, ya. Como uno de los usuarios de la PoC sea el gerente, te van a decir que añadas a los usuarios nuevos y que no toques lo demás. Y lo sabes
  • Tienes un VPS muerto de risa alojado en un hosting por el que pagas 5 eurillos al mes, que apenas utilizas salvo para hacer el mal algunas pruebas y demás. Ya que lo estás pagando, te gustaría montar un servidor VPN para que cuando estás en el Starbucks (yo sólo tomo carajillos, y en el bar no tienen wifi para que los clientes hablen entre ellos), en un centro comercial, de viaje, o en cualquier red pública o semi pública que no sea de tu total confianza utilizar una conexión VPN para proteger tus datos. No, no, yo me conecto a las redes abiertas a pelo, que me gusta vivir al límite. ¡Y que vivan las piñas!

Vale, que sí, que a lo mejor me estoy extendiendo mucho con estas cosas, pero aún no hemos visto nada de chicha. Pero, como ingeniero que soy, entiendo que uno tiene que ser capaz de justificar adecuadamente sus decisiones de diseño, aunque pocas veces lo vea. O no, o lo más importante es utilizar una tecnología cool (guay que mola, lo que mola es pasarlo guay).

Después de ver por qué utilizar Docker para un servidor VPN es perfectamente válido para dar un servicio con poca concurrencia de usuarios, vamos a empezar a darle a la tecla. En este caso, para mis pruebas he utilizado un servidor con sistema operativo Debian 9 Stretch. ¿Debian? Menudo lammer. ¡Los juakers de verdad utilizan Kali! Lo que tú digas, trastornao, pero al final Kali no deja de ser un sistema operativo Debian y poco más. Whisky Tango Foxtrot!! Are you kidding me? Bueno, venga, que no es cuestión de discutir con un loco (contigo mismo querrás decir). En fin, como comentaba, en este caso voy a utilizar un Debian 9, aunque podéis utilizar el sistema operativo que os pete que más se ajuste a vuestros intereses:

root@debian9:~# uname -a
Linux debian9 4.9.0-6-amd64 #1 SMP Debian 4.9.88-1+deb9u1 (2018-05-07) x86_64 GNU/Linux

Por otro lado, al igual que no vamos a explicar cómo se instala y configura el sistema operativo, tampoco vamos a explicar cómo se instala y configura Docker en el sistema operativo, ya que las dos cosas se encuentran fuera del objetivo de este post. En mi caso, para instalar Docker en mi sistema operativo Debian, yo seguí la información que se encuentra en el blog oficial. Por lo tanto, partiremos de la base de que Docker está instalado y funcionando:

root@debian9:~# docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
9db2ca6ccae0: Pull complete
Digest: sha256:4b8ff392a12ed9ea17784bd3c9a8b1fa3299cac44aca35a85c90c5e3c7afacdc
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. (amd64)
 3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/engine/userguide/

Una vez que tenemos Docker correctamente instalado y funcionando, lo que hacemos es ejecutar un container de Docker con la imagen “Busybox”. BusyBox combina pequeñas versiones de muchas utilidades comunes de UNIX en un único ejecutable. Proporciona las mismas funcionalidades que normalmente se encuentran en fileutils, shellutils, etc, de GNU. Generalmente, las utilidades que se encuentran en BusyBox tienen menos opciones que sus ‘primas’ de GNU completas; no obstante, las opciones que se incluyen proporcionan la funcionalidad esperada y se comportan de manera muy similar a sus contrapartes de GNU. Básicamente, en román paladino y sin ser estricto técnicamente, BusyBox proporciona un mini Unix/Linux bastante completo para que podamos instalar cosas. En un alarde de originalidad, vamos a llamar al volumen vpn-server. Apuntad el nombre, que no se os olvide:

root@debian9:~# docker run --name vpn-server -v /etc/openvpn busybox
Unable to find image 'busybox:latest' locally
latest: Pulling from library/busybox
07a152489297: Pull complete
Digest: sha256:141c253bc4c3fd0a201d32dc1f493bcf3fff003b6df416dea4f41046e0f37d47
Status: Downloaded newer image for busybox:latest

En el caso del ejemplo, he partido de una instalación limpia, por lo que, al no encontrarla en local, se la ha descargado automágicamente. Bien. Muy bonito. Tenemos la imagen de BusyBox. De gratis. Pero, ¿y ahora que hosti*s hago con eso? Entiendo tu frustración, p*to loco: hemos ejecutado un comando, pero no he explicado que era la opción –v ésa tan bonita. Para ello, vamos a comentar cómo se administran los datos y la persistencia en Docker. De forma predeterminada, todos los archivos creados dentro de un contenedor se almacenan en una capa en la que se puede escribir. Esto significa que los datos no persisten cuando ese contenedor ya no se está ejecutando, y puede ser difícil sacar los datos del contenedor si otro proceso lo necesita.

Docker tiene dos opciones para que los contenedores almacenen archivos en el equipo host, de modo que los archivos permanezcan incluso después de que el contenedor se detenga: volúmenes y bind mounts. Los volúmenes son el mecanismo preferido para los datos persistentes generados y utilizados por los contenedores Docker. Si bien los bind mounts dependen de la estructura de directorios de la máquina host, Docker administra los volúmenes completamente. Los volúmenes tienen varias ventajas sobre los soportes de enlace. Por ejemplo, un volumen no aumenta el tamaño de los contenedores que lo usan y el contenido del volumen existe fuera del ciclo de vida de un contenedor determinado. En cualquier caso, si usted tiene alguna pregunta o necesita ampliar cualquier información, no dude en consultar en la documentación oficial cómo administrar datos en Docker.

Volviendo a como estábamos, habíamos ejecutado un contenedor con la imagen BusyBox a la que hemos llamado vpn-server (¿os acordáis que os dije que apuntarais el nombre?). Para darle persistencia a la información que contiene el contenedor (lo que vendría siendo en nuestro caso la instalación/configuración de OpenVPN), lo hemos hecho mediante el volumen /etc/openvpn. Entonces, lo que vamos a hacer ahora es:

  • Ejecutar la imagen del servidor VPN (kylemanna/openvpn) que tenemos en los repositorios de docker.
  • Dentro de esa imagen, ejecutamos el script “ovpn_genconfig” que genera la configuración básica del servidor openvpn.
  • Todo esto lo hacemos montando los volúmenes de nuestro contenedor vpn-server.
root@debian9:~# docker run --volumes-from vpn-server --rm kylemanna/openvpn ovpn_genconfig -u udp://vpn.fwhibbit.com:1194
Unable to find image 'kylemanna/openvpn:latest' locally
latest: Pulling from kylemanna/openvpn
ff3a5c916c92: Pull complete 
b6cde4ed15e4: Pull complete 
d08fcd0e02b1: Pull complete 
e5a0572f36c6: Pull complete 
80ffa18008eb: Pull complete 
Digest: sha256:27d56a67c9ee8a92455c7f97e31a245485a90d6ed40fd9d6f93e80ed81234a4a
Status: Downloaded newer image for kylemanna/openvpn:latest
Processing PUSH Config: 'block-outside-dns'
Processing Route Config: '192.168.254.0/24'
Processing PUSH Config: 'dhcp-option DNS 8.8.8.8'
Processing PUSH Config: 'dhcp-option DNS 8.8.4.4'
Processing PUSH Config: 'comp-lzo no'
Successfully generated config
Cleaning up before Exit ...

Por supuesto, hemos especificado un nombre de dominio que, en vuestro caso, podría ser diferente. Vamos que, seguramente, lo será 😉. Hay que especificar un dominio que se resuelva a vuestra máquina o, si os viene mejor, la IP. El número de puerto también lo podéis cambiar para no poner el puerto por defecto, pero yo paso en este caso lo vamos a dejar así.

Ahora lo que tenemos que hacer es generar la PKI de la autoridad de certificación. Ah, ¿así que la movida ésta de las VPNs va con certificados y esas mierdas? Qué pasada, ¿no? Sí. Una pasada. Nos pedirá una clave durante la generación, así que os aconsejo que elijáis bien la clave, ya que de ella dependerá la seguridad de nuestra VPN (la tengo: password1234. No, no, espera, mejor 4321password, que esa fijo que no la adivina nadie):

root@debian9:~# docker run --volumes-from vpn-server --rm -it kylemanna/openvpn ovpn_initpki
init-pki complete; you may now create a CA or requests.
Your newly created PKI dir is: /etc/openvpn/pki
Generating a 2048 bit RSA private key
.........................................................................+++
...............................+++
writing new private key to '/etc/openvpn/pki/private/ca.key.XXXXPGPKbe'
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.
-----
Common Name (eg: your user, host, or server name) [Easy-RSA CA]:Rabbit

CA creation complete and you may now import and sign cert requests.
Your new CA certificate file for publishing is at:
/etc/openvpn/pki/ca.crt

Generating DH parameters, 2048 bit long safe prime, generator 2
This is going to take a long time
...........................................................+.................................................................................................................................+.................................................++*++*

DH parameters of size 2048 created at /etc/openvpn/pki/dh.pem

Generating a 2048 bit RSA private key
..........................................................................+++
......................................+++
writing new private key to '/etc/openvpn/pki/private/vpn.fwhibbit.com.key.XXXXBALnnd'
-----
Using configuration from /usr/share/easy-rsa/openssl-1.0.cnf
Enter pass phrase for /etc/openvpn/pki/private/ca.key:
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
commonName            :ASN.1 12:'vpn.fwhibbit.com'
Certificate is to be certified until Jul 13 17:29:59 2028 GMT (3650 days)

Write out database with 1 new entries
Data Base Updated
Using configuration from /usr/share/easy-rsa/openssl-1.0.cnf
Enter pass phrase for /etc/openvpn/pki/private/ca.key:

An updated CRL has been created.
CRL file: /etc/openvpn/pki/crl.pem

Una vez que ya tenemos nuestro servidor de VPN preparado, lo que haremos va a ser crear un servicio en systemd para que se ejecute automáticamente el servidor VPN de Docker al arrancar el servidor. Mejor, no vaya a ser que la gente empiece a decir que eso no funciona, que la PoC ésa no sirve y decidan contratar un producto de pago que te descuenten del sueldo. Para ello, vamos a utilizar nuestro editor favorito (vi es dios, y punto):

root@debian9:~# vi /lib/systemd/system/docker-openvpn.service

Y dentro de ese fichero introducimos el siguiente contenido:

[Unit]
Description=Docker container for OpenVPN server
Requires=docker.service
After=docker.service

[Service]
Restart=always
ExecStart=/usr/bin/docker run --volumes-from vpn-server --rm -p 1194:1194/udp --cap-add=NET_ADMIN kylemanna/openvpn
ExecStop=/usr/bin/docker stop -t 2 kylemanna/openvpn

[Install]
WantedBy=default.target

Ahora sólo nos queda registrar el servicio para que arranque cada vez que se reinicie la máquina, lo ejecutamos, y comprobamos que se está ejecutando correctamente:

root@debian9:~# systemctl enable docker-openvpn.service
Created symlink /etc/systemd/system/default.target.wants/docker-openvpn.service → /lib/systemd/system/docker-openvpn.service.
root@debian9:~# service docker-openvpn start
root@debian9:~# docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                    NAMES
f410f6a0af82        kylemanna/openvpn   "ovpn_run"          3 seconds ago       Up 3 seconds        0.0.0.0:1194->1194/udp   happy_payne

Bueno, ¿ya está? ¿Recogemos y para casa? Hombre, pues no. Todavía no. Por ejemplo, si tenemos un firewall en nuestro servidor, tendremos que abrir dicho puerto. También tenemos que generar los certificados y ficheros de configuración para los clientes que se van a conectar a nuestra VPN. En principio un fichero de configuración para cada cliente. Por temas de seguridad y tal, ya sabéis. Pero, ey, ¿quién soy yo para juzgaros? Pedirá la contraseña de nuestra CA. ¿Lo qué? La que hemos creado antes. Ojo, la ponemos sin equivocarnos:

root@debian9:~# docker run --volumes-from vpn-server --rm -it kylemanna/openvpn easyrsa build-client-full juanvelasco nopass
Generating a 2048 bit RSA private key
.....+++
.+++
writing new private key to '/etc/openvpn/pki/private/juanvelasco.key.XXXXoLbIdK'
-----
Using configuration from /usr/share/easy-rsa/openssl-1.0.cnf
Enter pass phrase for /etc/openvpn/pki/private/ca.key:
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
commonName            :ASN.1 12:'juanvelasco'
Certificate is to be certified until Jul 13 18:08:17 2028 GMT (3650 days)

Write out database with 1 new entries
Data Base Updated

También tenemos que crear el fichero de configuración .ovpn que tendremos que importar luego en el cliente:

root@debian9:~# docker run --volumes-from vpn-server --rm kylemanna/openvpn ovpn_getclient juanvelasco > juanvelasco.ovpn

Una cosa más. Por defecto, al generar la configuración del cliente con el contenedor kylemanna/openvpn, en el fichero de configuración se añade la opción redirect-gateway, que hace que todo el tráfico de red IP originado en el cliente vaya a través del servidor OpenVPN. Normalmente, si tenemos el cliente instalado en un ordenador en nuestra casa y el servidor está en la red de la empresa, el direccionamiento de la red de nuestra casa será de clase C (192.168.0.0/16) mientras que en la red de la empresa tendremos direccionamiento de clase A (10.0.0.0/8) o B (172.16.0.0/12), así que tal vez lo lógico sería añadir la opción push route para que sólo el tráfico a estas direcciones de red se envíen hacia la conexión VPN. Para ello habría que utilizar la opción -p al generar la configuración del cliente.

Finalmente, tendríamos que instalar el cliente en nuestro dispositivo e importar el fichero juanvelasco.ovpn que hemos creado antes. Esto dependerá de qué cliente elijáis para vuestro dispositivo. Vamos, que le estás diciendo a esta buena gente que se busque la vida. Muy bonito. Para importarlo, lo ideal sería copiarlo de forma segura al dispositivo cliente. Copiando y pegando en pastebin por ejemplo. Tened en cuenta que la forma de transferir el fichero de configuración del cliente podría poner en compromiso las conexiones VPN entre ese cliente y nuestro servidor. Sí, sí. Lo que tú digas. Pero vamos, que yo no pienso picármelo a mano…

Por mi parte, creo que eso es todo. Espero que os haya gustado el post y que os haya servido de ayuda. Os agradeceré cualquier comentario que tengáis al respecto, me podéis encontrar en el grupo de Telegram de la Comunidad de Fwhibbit o en Twitter.

Consérvate sano.

10 comentarios en «Contén tu información en una VPN utilizando Docker»

  1. Estupenda entrada! Hace unos mese probé esta imagen precompilada en dos máquina virtuales con Ubuntu 16 y, como dices, es una estupenda opción para efectuar alguna prueba de concepto. Añadir también que el autor proporciona distintos scripts para mejorar la gestión de usuarios permitiendo listarlos, revocarlos, o separar las claves y certificados que requerirán para conectarse en archivos independientes. Es un solución muy completa!

  2. ‘Por ejemplo, un volumen no aumenta el tamaño de los contenedores que lo usan y el contenido del volumen existe fuera del ciclo de vida de un contenedor determinado’
    si esto es asi no puedes llegar a petar el servidor al no restringirte al limite del docker?

    1. Hola Javier.
      Efectivamente, podrías llegar a llenar la unidad de almacenamiento en la que esté almacenada el volumen. Aunque en principio depende de en qué «disco» almacenes cada cosa, te puede interesar hacerlo así o no por espacio. En cualquier caso, lo que más interesante veo del tema de volúmenes es que se pueda manejar su persistencia fuera del ciclo de vida del contenedor…
      Un saludo.

  3. Muy buen articulo Loren! Yo le habria quitado la discusion interior, pero es cuestion de estilo. Un ejercicio interesante (aunque no estoy seguro de si tiene mucho sentido desde un punto de vista de seguridad) seria desplegar el docker en los servicios de AWS de modo que desde la linea de comando de nuestro maquina local pudieramos desplegar y configurar el numero de VPN que queramos en dockers dentro de EC2….
    (es que estoy con temas de integracion continua y tal ahora mismo y se me ocurren pichadas asi)
    Sigue asi tio! Y mandanos tu proximo articulo.

    1. Muchas gracias Javier, me alegro de que te haya gustado. La verdad es que la idea de «las voces de mi cabeza» es algo que he hecho para hacerlo más divertido (y me consta que a alguna gente le ha gustado, casi tanto como a mí escribirlo así) pero, como tú dices, es cuestión de estilo…
      Me parece muy interesante tu idea, yo te animo a que la pruebes cuando tengas un rato. Entiendo que podría ser interesante si queremos generar VPNs de forma ‘dinámica’, aunque no le veo mucho sentido, ya que tampoco aportaría ninguna ventaja a generar y eliminar los certificados de forma dinámica si queremos limitar en el tiempo las posibilidades de conexión.

  4. Muy buen how to. Lo he montado en mi servidor de AWS. Excelente artículo pero creo que las «voces interiores» y la ironía sobran, pero esto es solo mi opinión.

  5. Genial, gracias por el artículo. Me preguntaba un amigo el otro día lo de: y por qué lo haces en docker? Porque puedo.

    Y no calles nunca las voces de tu cabeza, mejor fuera que dentro.

Los comentarios están cerrados.