NETinVM - Una red en una máquina virtual

Una herramienta para la enseñanza y el aprendizaje de SSOO, redes y seguridad

Autores: Carlos Pérez y David Pérez
Fecha: 11 de julio de 2013

Contenido

Introducción

NETinVM es una imagen de máquina virtual VMware que permite trabajar como si uno dispusiera de una red de computadores a su disposición. De esta forma, NETinVM se puede utilizar para el aprendizaje de SSOO y redes, incluyendo aspectos tan relevantes como la seguridad de sistemas y redes.

Al mismo tiempo, NETinVM, al ser una imagen de VMware permite que un profesor la utilice como base para la realización de ejercicios dirigidos, para demostraciones en clase que pueden después ser reproducidas por los alumnos tanto en un laboratorio docente como en un portátil y, por lo tanto, en su casa, la biblioteca... Es por esto que presentamos NETinVM como una herramienta adecuada para la enseñanza.

Descripción de NETinVM

NETinVM es una imagen de máquina virtual VMware que contiene, preparadas para ejecutarse, una serie de máquinas virtuales User-mode Linux (UML). Al ponerlas en marcha, las máquinas virtuales UML forman una red de computadores. De ahí el nombre NETinVM, que es la abreviatura de NETwork in Virtual Machine (red en una máquina virtual). A la red virtual así formada la hemos denominado 'example.net' y, de hecho, los nombres cualificados de las máquinas son del tipo 'base.example.net', 'fw.example.net'...

Todas las máquinas virtuales utilizan el sistema operativo Linux. La máquina virtual VMware recibe el nombre 'base' y ejecuta la versión 10.3 de openSUSE. Las máquinas User-mode Linux usan la versión 4 de Debian y reciben nombres diferentes en función de su ubicación en la red virtual, ya que están agrupadas en tres subredes, que desempeñan el papel de las redes corporativa, perimétrica y externa de una organización. Estas redes reciben en NETinVM los nombres 'int' (por red interna), 'dmz' (por DMZ o zona desmilitarizada, que suele usarse como sinónimo de red perimétrica) y 'ext' (por red externa).

Una de las máquinas UML, 'fw', conecta entre sí las tres redes permitiendo la comunicación y el filtrado de paquetes. Las demás UMLs tienen una única interfaz de red conectada a la red que les da nombre:

int<X>
UMLs conectadas a la red interna. <X> puede tomar los valores de 'a' a 'f', ambos inclusive. Estas máquinas sólo ofrecen el servicio SSH.
dmz<X>

UMLs conectadas a la red perimétrica (DMZ). Pensadas como nodos bastión. En esta red hay dos máquinas con alias:

  • 'dmza' tiene el alias 'www.example.net' y ofrece los servicios HTTP y HTTPS.
  • 'dmzb' tiene el alias 'ftp.example.net' y ofrece FTP.
ext<X>
UMLs conectadas a la red externa de la organización (por ejemplo, el equivalente de 'Internet').

Como una imagen vale más que mil palabras, la siguiente figura muestra NETinVM con todas las máquinas virtuales en ejecución.

img/netinvm_general.png

Vista general de NETinVM. El documento example-net.pdf permite observar los detalles de la figura.

En la imagen están representados todos los elementos a los que se ha hecho referencia anteriormente con sus direcciones IP y ethernet. Para la asignación de direcciones se han seguido las siguientes reglas:

Además de las redes y máquinas descritas previamente, en la figura también aparece el ordenador en el que se ejecuta NETinVM, etiquetado como 'ORDENADOR REAL' y la red virtual 'vmnet8' característica de VMware Player, que proporciona (opcionalmente) conectividad entre las redes de NETinVM y el mundo exterior.

Al arrancar, todas las máquinas virtuales UML obtienen su configuración de red de 'base', que ofrece los servicios DHCP y DNS a todas las redes a través de las interfaces 'tap0', 'tap1' y 'tap2'.

El encaminamiento se produce de la siguiente forma:

Por tanto, el tráfico entre las máquinas de las tres redes siempre circula a través de 'fw', mientras que el tráfico hacia fuera de la máquina virtual VMware atraviesa 'fw' si y sólo si proviene de las redes interna o perimétrica. En cualquier caso, ese tráfico hacia el mundo exterior sale a través de 'base', que a su vez, como 'fw', también tiene activado el reenvío IP y NAT.

La comunicación entre 'base' y cualquier máquina UML, en cualquiera de los dos sentidos, se realiza directamente, sin pasar por 'fw' (siempre que se utilice la IP de 'base' correspondiente a la red de la máquina UML de que se trate). Esta configuración resulta conveniente, pues permite el acceso desde 'base' a todas las máquinas UML usando SSH independientemente de la configuración del filtrado de paquetes en 'fw'.

Como nota adicional nótese que la configuración de SNAT en 'fw' descrita anteriormente es necesaria para que las respuestas a las conexiones salientes hacia Internet originadas en las redes interna y perimétrica vuelvan a través de 'fw' y no sean enviadas por 'base' directamente a las máquinas UML a través de tap1 o tap2 sin pasar por 'fw'.

Trabajando con NETinVM

Puesta en marcha inicial

Para poner en marcha NETinVM simplemente hay que descargarse la imagen VMware, descomprimirla y ejecutarla con el programa VMware Player, que puede obtenerse de forma gratuita de VMware.

Una vez arrancada la imagen VMware se tiene en marcha base.example.net, que ofrece el escritorio (KDE) del usuario unp (el nombre proviene de abreviar "usuario no privilegiado"). Su contraseña, al igual que la del usuario administrador (root) es: "4xcinco=veinte".

Aunque base se ejecuta conectada al commutador virtual de VMware (vmnet8), que utiliza NAT, el cortafuegos está activo y solamente permite conexiones entrantes desde el exterior (interfaz eth0) al puerto 22 (SSH). Las interfaces tap0, tap1 y tap2 se consideran internas y el tráfico a través de ellas no tiene restricción alguna.

La idea es que base sea el escritorio en el que trabajar y por eso está instalada OpenOffice.org y otras herramientas habituales de ofimática. También está pensada para poder monitorizar el funcionamiento de las redes internas (a través de tap0, tap1 y tap2) y por eso tiene instaladas aplicaciones como wireshark, tcpdump o nmap.

Puesta en marcha completa

La orden uml_run_all.sh es la palabra mágica que pone en marcha casi toda la capacidad de NETinVM. En concreto, lanza los siguientes elementos:

  • los conmutadores virtuales para las redes externa, perimétrica e interna
  • las máquinas virtuales UML fw, exta, inta, dmza y dmzb

Aunque NETinVM está preparada para que se puedan ejecutar seis máquinas virtuales por red (de la a 'a' la 'f'), con estas cuatro es posible desarrollar un amplio abanico de actividades (y, lógicamente, cuantas menos UMLs haya en marcha menos cargado estará el sistema).

Cada máquina UML arranca en un escritorio virtual diferente:

  • exta en el 2
  • fw en el 4
  • dmza en el 5
  • dmzb en el 6
  • inta en el 8

En cada escritorio se pueden identificar los siguientes elementos, que también se muestran en la figura:

  • una xterm que contiene los mensajes de lo que sería la consola del computador que está arrancando
  • una konsole (aparece al final del proceso de arranque) que permite conectarse directamente a la UML (es el equivalente de una terminal directamente conectada al computador)
  • dos konsoles más pero minimizadas (aparecen a continuación de la primera); también terminales virtuales
img/netinvm_exta_60.png

Vista del escritorio 2 una vez ha arrancado exta.

Una vez arrancan todas las máquinas virtuales UML es posible localizarlas de forma sencilla utilizando la lista de ventanas de KDE, que es posible obtener presionando el botón central del ratón o apretando la combinación de teclas 'Alt-F5'. El resultado debería ser similar al de la figura.

img/netinvm_lista_ventanas.png

Lista de ventanas tras haber arrancado las UMLs.

Ejemplo de uso: capturar una conexión HTTP

Como ejemplo de la utilización de NETinVM para la enseñanza y el aprendizaje, a continuación se describe cómo capturar el tráfico de una conexión HTTP.

Una vez puesta en marcha completamente NETinVM usando el guión 'uml_run_all.sh', es tan sencillo como si fuera un sistema real. Vamos a capturar el tráfico de una conexión HTTP desde 'exta' a 'www.example.net', suponiendo que estamos interesados en comprobar el tráfico en la red perimétrica.

Nota

Nótese que 'base' tiene acceso tanto a la red externa (a través de 'tap0') como a la perimétrica (a través de 'tap1'). Por lo tanto podemos capturar cualquiera de los dos lados de la conversación (o los dos).

  1. Se lanza 'wireshark' en 'base' y se le configura para que capture el tráfico de la red perimétrica (interfaz 'tap1').

    img/netinvm_wireshark_interfaces.png
  2. Entramos en la terminal de 'exta' (escritorio 2) y tecleamos:

    $ wget www.example.com
    
  3. Volvemos al 'wireshark' y detenemos la captura. Seguramente se parecerá a la siguiente figura, en la que se puede observar la sesión HTTP establecida entre exta (10.5.0.10) y dmza (www.example.com, 10.5.1.10).

    img/netinvm_wireshark_captura_60.png

Una descripción más detallada

Almacenamiento en las máquinas UML

Para ahorrar espacio, todas las máquinas UML (todas, incluyendo 'fw') utilizan la misma imagen básica del sistema de ficheros raíz, alojada en el fichero '/home/unp/uml/data/uml_root_fs' de 'base'. Como este sistema de ficheros incluye una instalación bastante completa de Debian 4.0, ocupa unos 500 MB, tal y como puede verse aquí:

unp@base:~> ls -lsh /home/unp/uml/data/uml_root_fs
593M -rw-r--r-- 1 unp users 1,0G dic  7 23:58 /home/unp/uml/data/uml_root_fs
unp@base:~>

El tamaño máximo de este sistema de ficheros es de 1 GB, lo que deja suficiente espacio a las máquinas UML para alojar programas y datos temporales (403 MB disponibles en /dev/ubda):

exta:~# df -h
S.ficheros          Tamaño Usado  Disp Uso% Montado en
/dev/ubda             992M  549M  403M  58% /
tmpfs                  62M     0   62M   0% /lib/init/rw
tmpfs                  62M     0   62M   0% /dev/shm
tmpfs                 768M     0  768M   0% /tmp
none                   12G  4,1G  6,5G  39% /mnt/tmp
none                   12G  4,1G  6,5G  39% /mnt/config
none                   12G  4,1G  6,5G  39% /mnt/data
exta:~#

Además, las máquinas UML disponen de 4,1 GB adicionales en 'base', accesibles bien por red (ej: scp), bien mediante el directorio '/mnt/tmp', montado automáticamente en todas las máquinas UML y que establece una correspondencia con el directorio '/home/unp/uml/mntdirs/tmp' de 'base' (ver 'TODO').

Los cambios que cada UML hace en su sistema de ficheros raíz se almacena en el fichero '/home/unp/uml/machines/machine/cow' de base (sustituir 'machine' por el nombre de la máquina) utilizando el mecanismo de copia en escritura disponible en UML. Lógicamente, el tamaño realmente ocupado por este fichero dependerá de los cambios realizados, pero si uno se limita a arrancar la máquina y cambiar pequeñas cosas de la configuración, ocupa realmente poco:

unp@base:~> ls -lsh /home/unp/uml/machines/exta/cow
6,0M -rw-r--r-- 1 unp users 1,1G feb  7 18:26 /home/unp/uml/machines/exta/cow
unp@base:~>

Nótese que el tamaño que realmente ocupa 'cow' es 6,0 MB (y no 1,1 GB), pues es un fichero disperso (sparse file).

Finalmente, aunque no es espacio de almacenamiento, las máquinas UML disponen de 256 MB de espacio de intercambio (swap) que se almacena en el fichero '/home/unp/uml/machines/machine/swap' de 'base' y que, al menos que se produzca paginación, apenas ocupa espacio en 'base':

unp@base:~> ls -lsh /home/unp/uml/machines/exta/swap
16K -rw-r--r-- 1 unp users 256M ene 31 13:04 /home/unp/uml/machines/exta/swap
unp@base:~>

Nuevamente es conveniente resaltar que el tamaño que realmente ocupa 'swap' es 6,0 MB (y no 1,1 GB), pues también es un fichero disperso (sparse file).

Configuración de las máquinas UML

Que todas las máquinas UML compartan el mismo sistema de ficheros raíz, al que llamaremos "sistema de ficheros de referencia" (SFR) es muy positivo, por varios motivos:

  1. Ahorra espacio. Efectivamente, usando copia en escritura se pueden tener funcionando 19 máquinas UML ocupando poco más de 0'5 GB de 'base'.
  2. Simplifica el uso. Actualizar todas las máquinas con los últimos parches de seguridad o añadir un paquete a todas ellas simplemente requiere hacerlo en una de ellas. (Ver TODO).
  3. Simplifica el uso. Todas las UML son similares y tienen el mismo software instalado.

Sin embargo, para no perder estas ventajas es necesario que las máquinas se puedan adaptar a desempeñar diferentes papeles. Por ejemplo, 'fw' tiene tres interfaces de red y realiza filtrado de paquetes, 'dmza' ofrece servicio HTTP y HTTPS, 'exta' sólo ofrece SSH...

El mecanismo de configuración de las máquinas es el siguiente:

  • El fichero '/etc/fstab' del sistema de ficheros de referencia (SFR) contiene entradas para que las UML monten los subdirectorios 'config' y 'data' de '/home/unp/uml/mntdirs' de 'base' en los directorios del mismo nombre de '/mnt' en la UML, como se puede ver a continuación:

    exta:~# mount | egrep 'config|data'
    none on /mnt/config type hostfs (ro,uml/mntdirs/config)
    none on /mnt/data type hostfs (ro,uml/mntdirs/data)
    exta:~#
    
  • El guión '/etc/init.d/configure_uml_machine.sh', presente en el SFR, comprueba si existe el guión '/mnt/config/bin/configure.sh' y, si es ejecutable, lo ejecuta.

  • El guión 'configure.sh' realiza los siguientes pasos:

    1. Comprueba si la UML ya ha sido configurada. Si es así, termina.
    2. La marca como configurada.
    3. Aplica la configuración por defecto (si existe, enseguida se detalla).
    4. Aplica la configuración específica de la red (si existe, enseguida se detalla).
    5. Aplica la configuración específica de la máquina (si existe, enseguida se detalla).

    De esta forma es posible cambiar la configuración de todas las UML, de las UML de una red y de una máquina en concreto.

    La configuración (ya sea por defecto, de la red o de la máquina) tiene a su vez dos pasos:

    1. Habilitar servicios: lista de nombres de guiones para '/etc/init.d'. Los guiones pueden existir en Debian (por ejemplo, 'apache') o pueden estar alojados en el subdirectorio 'init.d' del directorio donde está el fichero con la lista. (Detalles más adelante).

      'configure.sh' usa 'update-rc.d' para cambiar permanentemente la configuración de la máquina UML. (Nótese que la configuración no se realiza en cada arranque).

    2. Ejecutar órdenes: para todas aquellas modificaciones que no encajan bien en la abstracción de servicio. (En la configuración actual no ha sido necesario usarlo).

  • Por lo tanto, la configuración de las máquinas UML depende exclusivamente de una serie de ficheros alojados en el directorio '/home/unp/uml/mntdirs/config' de base, y puede ser modificada sin arrancar ninguna máquina UML y sin necesidad de convertirse en el administrador de 'base'. La estructura de directorios es la siguiente:

    bin

    Directorio con 'configure.sh'. no es necesario modificarlo.

    templates

    Plantillas para la creación del resto de los directorios.

    default

    Configuración por defecto, aplicable a todas las UML.

    network_{ext,dmz,int}

    Configuración para las redes externa ('ext'), perimétrica ('dmz') e interna ('int').

    machine

    Configuración específica para la máquina 'machine'.

    La estructura de todos los directorios de configuración es siempre la misma, tomaremos 'fw' como ejemplo:

    fw/services_to_enable.sh

    Lista de servicios a habilitar en la UML. El servicio puede ser un guión que exista en '/etc/init.d' en el SFR (como 'apache' o 'vsftpd') o puede ser un guión que esté alojado en 'fw/init.d' (como 'add_ips.sh').

    Si existe 'fw/init.d/guión' (sustituir guión por lo que corresponda), entonces durante la configuración se crea un enlace simbólico en '/etc/init.d' con el mismo nombre y que apunta a '/mnt/config/fw/init.d/guión'. (Si también existía '/etc/init.d/guión', antes de crear el enlace se renombra el original como '/etc/init.d/guión.replaced').

    fw/init.d

    Directorio que contiene los guiones a añadir (ver punto anterior).

    fw/commands_to_run.sh

    Órdenes a ejecutar durante la configuración. Este guión es simplemente ejecutado por 'configure.sh', así que hay libertad total. Eso sí, hay que recordar que no son órdenes que se ejecuten cada vez que arranca la máquina UML, sino sólo durante la configuración (una vez). Para que se ejecuten siempre, 'commands_to_run.sh' deberá añadir las órdenes a un fichero de arranque de la UML (pero en muchos casos es mejor añadir un servicio, como 'add_ips.sh', por ejemplo).

Después de esta explicación, el lector puede preguntarse si la configuración no se ha hecho demasiado compleja. Seguramente sea posible simplificar el proceso, pero antes hay que tener en cuenta las ventajas que aporta el sistema actual:

  1. Se puede cambiar la configuración completamente sin necesidad de poner en marcha ninguna máquina virtual. Esto incluye el propio proceso de configuración, ya que el sistema de ficheros de las UML sólo busca 'configure.sh' (que está almacenado en 'base') y lo ejecuta.
  2. Como la configuración sólo se realiza una vez por máquina virtual, los cambios tienen que quedar reflejados en su sistema de ficheros. Por ejemplo, si se añade un servicio en 'fw', cuando uno lista el contenido de los directorios '/etc/rc<X>.d' (<X> es el nivel de ejecución), allí están los enlaces que uno puede esperar.
  3. Es posible guardar configuraciones diferentes para diferentes ejercicios en directorios 'config.ej1' o en ficheros 'config_ej1.tar.gz', por ejemplo, y comenzar cada ejercicio con máquinas limpias y configuradas de la manera apropiada.

La principal alternativa, que vimos en el proyecto Netkit [*], en la que básicamente se ejecutan órdenes externas cada vez que arranca una UML no nos parece tan educativo. El motivo es que si cada vez que arranca la UML hay órdenes que se ejecutan sin seguir la estructura propia de Debian, entonces se rompe la ilusión de estar trabajando sobre una máquina real. Es cierto que para algunas actividades esto no tendrá importancia, pero como plataforma para experimentar y aprender, preferimos la solución que hemos elegido.

[*]Netkit nos parece un gran proyecto. No lo hemos usado porque no se ajustaba exactamente a lo que deseábamos. Nosotros queríamos tener una red concreta con IPs, MACs, nombres de máquinas, etc. que fueran fáciles de recordar, tanto para aprender como para enseñar.

Ejercicios de ejemplo

En los documentos siguientes se plantean algunos ejemplos de ejercicios que pueden realizarse sobre NETinVM.

Barrido de puertos con nmap
Realizar un barrido de puertos sobre "www.example.net" desde "exta.example.net".