Este artículo presenta la configuración y uso de jails, la tecnología de virtualización a nivel sistema operativo (comúnmente conocida como contenedores, containers) provista por FreeBSD.

Esta tecnología permite particionar a un sistema FreeBSD en varios mini-sistemas independientes (instancias de espacios de usuario aisladas unas de otras) llamados "jails". En el mundo Linux existen tecnologías similares como LXC y Docker.

Los jails se implementan sobre el concepto de chroot, creando un entorno seguro y aislado del resto del sistema. Así, los procesos creados en este entorno, no pueden acceder a archivos o recursos fuera del mismo. De esta forma, si se compromete un servicio corriendo en un entorno chroot, el atacante no tiene acceso al resto del sistema.

En un entorno chroot tradicional, los procesos sólo están limitados a la parte del sistema de archivos que pueden acceder. Los jails expanden este modelo virtualizando el acceso al sistema de archivos, al conjunto de usuarios, y al subsistema de red.

Cada jail está caracterizado por cuatro elementos: un subárbol de directorio (directorio raíz donde se hace chroot), una vez dentro del jail, un proceso no puede salir de este subárbol; un nombre de host (que será utilizado para identificar al jail); una dirección IP (asignada al jail y correspondiente con el nombre de host; y un comando (ruta, relativa al subárbol, a un ejecutable a lanzar dentro del jail).

Los jails cuentan con su propio conjunto de usuarios y usuario root (limitado al entorno del jail, no puede realizar operaciones fuera del mismo).

Típicamente existen jails "completos" (que contienen un sistema FreeBSD completo) y de servicio (dedicados a un único servicio o aplicación).

Este artículo explica cómo crear y utilizar jails en FreeBSD 10, pero va un paso más allá con el objetivo de optimizar el uso de disco y simplificar la administración de los mismos, a partir de la configuración que se encuentra en el artículo Updating Multiple Jails incluido en el Handbook.

La administración de múltiples jails en un mismo host puede ser compleja, ya que cada jail debe ser reconstruido de forma manual cada vez que se desea actualizar. Esta es una tarea tediosa y que consume mucho tiempo. Sin embargo, existe un método para resolver este problema, con el cual se comparte lo más que se puede del sistema base entre todos los jails, utilizando montajes nullfs de sólo lectura utilizando.

El objetivo de esta implementación es simplificar la creación y actualización de jails, así como mejorar la eficiencia de uso de disco y seguridad. Este diseño se basa en una plantilla (template) común que es montada en cada jail en modo de sólo lectura, y un dispositivo de lectura/escritura para cada jail donde almacenar los datos y configuraciones particulares de cada uno.

En este artículo en particular, se utilizan dos datasets ZFS como base para el template y los sistemas de archivo de lectura/escritura de cada jail, donde:

  • Los jails se alojan dentro del pool ZFS /zjails (previamente creado).
  • Cada jail es montado bajo el dataset /zjails/jail.
  • La plantilla y partición de sólo lectura para cada jail se encuentra en el directorio /zjails/jail/mroot.
  • Se crea un directorio para cada jail bajo /zjails/jail.
  • Cada jail cuenta con un directorio /s que enlaza a la porción de lectura/escritura del sistema.
  • La porción de lectura/escritura de cada jail se crea dentro del dataset /zjails/jails.

Creación del template

El primer paso consiste en crear la plantilla de un sistema FreeBSD base.

Para comenzar, crear el directorio base para el template:

root@fbsd10:~ # zfs create zjails/jail
root@fbsd10:~ # mkdir /zjails/jail/mroot

En este caso se utiliza un dataset ZFS, pero es posible utilizar un directorio corriente en cualquier filesystem, o incluso un dispositivo dedicado optimizado para los accesos de lectura.

Descargar y extraer el sistema base y los fuentes de FreeBSD navegando el servidor FTP download.freebsd.org/ftp/releases/:

root@fbsd10:~ # cd /zjails/jail/
root@fbsd10:/zjails/jail # fetch https://download.freebsd.org/ftp/releases/amd64/amd64/10.3-RELEASE/base.txz
root@fbsd10:/zjails/jail # fetch https://download.freebsd.org/ftp/releases/amd64/amd64/10.3-RELEASE/src.txz
root@fbsd10:/zjails/jail # cd mroot/
root@fbsd10:/zjails/jail/mroot # tar xzf ../base.txz
root@fbsd10:/zjails/jail/mroot # tar xzf ../src.txz

Inmediatamente, actualizar el sistema base a la última versión (utilizar la opción -b para cambiar el directorio base delo sistema):

root@fbsd10:/zjails/jail/mroot # freebsd-update -b /zjails/jail/mroot/ fetch
root@fbsd10:/zjails/jail/mroot # freebsd-update -b /zjails/jail/mroot/ install

Instalar los ports:

root@fbsd10:/zjails/jail/mroot # mkdir usr/ports
root@fbsd10:/zjails/jail/mroot # portsnap -p /zjails/jail/mroot/usr/ports fetch extract
root@fbsd10:/zjails/jail/mroot # pkg install cpdup
root@fbsd10:/zjails/jail/mroot # cpdup /usr/src /zjails/jail/mroot/usr/src

Se utiliza la herramienta cpdup para copiar filesystems.

Crear la porción de lectura/escritura del template. Ésta contiene todos los directorios que requieren escritura, y que son independientes para cada jail, por ejemplo /etc, /var, etc.:

root@fbsd10:/zjails/jail/mroot # cd ..
root@fbsd10:/zjails/jail # mkdir skel skel/home skel/usr-X11R6 skel/distfiles skel/packages
root@fbsd10:/zjails/jail # cd mroot/
root@fbsd10:/zjails/jail/mroot # mv usr/local ../skel/usr-local
root@fbsd10:/zjails/jail/mroot # mv tmp ../skel
root@fbsd10:/zjails/jail/mroot # mv var ../skel
root@fbsd10:/zjails/jail/mroot # mv root ../skel

Instalar los archivos de configuración que puedan faltar luego de la actualización utilizando la herramienta mergemaster:

root@fbsd10:/zjails/jail/skel # mergemaster -t /zjails/jail/skel/var/tmp/temproot -D /zjails/jail/skel -i

Luego eliminar los directorios adicionales creados por mergemaster:

root@fbsd10:/zjails/jail/mroot # cd ../skel/
root@fbsd10:/zjails/jail/skel # rm -R bin boot lib libexec mnt proc rescue sbin sys usr dev

Ahora, crear los enlaces simbólicos al sistema de archivos de lectura/escritura desde el sistema de archivos de sólo lectura:

root@fbsd10:/zjails/jail/skel # cd ../mroot/
root@fbsd10:/zjails/jail/mroot # mkdir s
root@fbsd10:/zjails/jail/mroot # ln -s s/etc etc
root@fbsd10:/zjails/jail/mroot # ln -s s/home home
root@fbsd10:/zjails/jail/mroot # ln -s s/root root
root@fbsd10:/zjails/jail/mroot # ln -s ../s/usr-local usr/local
root@fbsd10:/zjails/jail/mroot # ln -s ../s/usr-X11R6 usr/X11R6
root@fbsd10:/zjails/jail/mroot # ln -s ../../s/distfiles usr/ports/distfiles
root@fbsd10:/zjails/jail/mroot # ln -s ../../s/packages usr/ports/packages
root@fbsd10:/zjails/jail/mroot # ln -s s/tmp tmp
root@fbsd10:/zjails/jail/mroot # ln -s s/var var

Finalmente, crear un archivo make.conf genérico:

root@fbsd10:/zjails/jail # nano skel/etc/make.conf

Con el siguiente contenido:

WRKDIRPREFIX?=  /s/portbuild

Tal vez sea complicado de entender, pero veamos. Cada jail montará (como sistema de archivos raíz) el mismo template, que será de acceso de sólo lectura (lo cual es indispensable y a su vez mejora notablemente la seguridad de los jails). Luego, todos los directorios que requieren escritura, y que deben ser independientes para cada jail, son en realidad enlaces simbólicos a otro filesystem montado en /s. La independencia se logra montando diferentes filesystems para cada jail bajo /s. De esta forma, todos los jails comparten el mismo filesystem raíz de sólo lectura, pero sus enlaces a directorios que requieren escritura (y ser individuales para cada jail) apuntan a diferentes filesystems en cada jail, pues cada cual tiene su propia versión de /s.

Creación de los jails

Habiendo creado la plantilla, ya es posible crear los jails. En este ejemplo se crean dos jails llamados "www" y "db" respectivamente.

Para comenzar, crear los puntos de montaje en el host FreeBSD. Normalmente basta con editar el archivo fstab:

root@fbsd10:/zjails/jail # nano /etc/fstab

Y agregar las siguientes líneas:

/zjails/jail/mroot  /zjails/jail/www    nullfs  ro  0  0
/zjails/jail/mroot  /zjails/jail/db     nullfs  ro  0  0
/zjails/jails/www   /zjails/jail/www/s  nullfs  rw  0  0
/zjails/jails/db    /zjails/jail/db/s   nullfs  rw  0  0

Sin embargo en este caso no funciona, pues los datasets ZFS se montan luego de que el sistema de inicio de FreeBSD interprete al archivo fstab. Los sistemas de archivo ZFS (no root) son montados por el servicio zfs. Por ende la configuración anterior falla (no conoce a /zjails/jail ni /zjails/jails al momento de parsear a fstab). Es necesario sacar dichas entradas del archivo fstab y crear un servicio que los monte, el cual debe iniciar antes del servicio jail (el cual levanta los jails).

Crear el servicio, el cual será llamado "jailsffss" (abreviado de "jails file systems"):

root@fbsd10:/zjails/jail # nano /usr/local/etc/rc.d/jailsffss

Agregar el siguiente contenido:

#!/bin/sh
#
# $FreeBSD: releng/10.2/etc/rc.d/iscsid 255570 2013-09-14 15:29:06Z trasz $
#

# PROVIDE: jailsffss
# REQUIRE: FILESYSTEMS LOGIN
# BEFORE: jail

. /etc/rc.subr

name="jailsffss"
rcvar="jailsffss_enable"
extra_commands="status"
start_cmd="start_jailsffss"
stop_cmd="stop_jailsffss"
status_cmd="status_jailsffss"

start_jailsffss()
{
   mount | grep nullfs >/dev/null 2>&1
   if [ $? -eq 0 ] ; then
      echo "jails filesystems already mounted."
      return 1
   else
      echo "Mounting jails filesystems..."
      mount -o ro,noatime -t nullfs /zjails/jail/mroot /zjails/jail/www
      mount -o ro,noatime -t nullfs /zjails/jail/mroot /zjails/jail/db
      mount -o rw,noatime -t nullfs /zjails/jails/www /zjails/jail/www/s
      mount -o rw,noatime -t nullfs /zjails/jails/db /zjails/jail/db/s
   fi
}

stop_jailsffss()
{
   mount | grep nullfs >/dev/null 2>&1
   if [ $? -eq 1 ] ; then
      echo "jails filesystems not mounted."
      return 1
   else
      echo "Umounting jails filesystems..."
      umount /zjails/jail/db/s
      umount /zjails/jail/www/s
      umount /zjails/jail/db
      umount /zjails/jail/www
   fi
}

status_jailsffss()
{
   mount | grep nullfs >/dev/null 2>&1
   if [ $? -eq 1 ] ; then
      echo "jails filesystems are not mounted."
   else
      echo "jails filesystems are mounted."
   fi
}

load_rc_config $name
run_rc_command "$1"

La creación de servicios en FreeBSD la cubriré en otro artículo, pero se observa que es bastante simple. Basta con definir las acciones a tomar en las funciones start_ y stop_ (adicionalmente se puede agregar la función status_).

La verificación previa a montar es bastante precaria y debe mejorarse (lo mismo al desmontar y verificar el estado). Pero al menos implementa una verificación mínima para evitar errores por consola.

La parte más importante es la cabecera:

# PROVIDE: jailsffss
# REQUIRE: FILESYSTEMS LOGIN
# BEFORE: jail

La palabra clave BEFORE requiere que este servicio sea iniciado antes que el servicio jail.

Para verificar el orden de inicio de servicios es posible utilizar el comando service -e.

El siguiente paso (habiendo verificado que el servicio anterior funcione correctamente) consiste en habilitar los servicios:

root@fbsd10:/zjails/jail # nano /etc/rc.conf

Agregar las siguientes líneas:

# Jails
jail_enable="YES"
jail_set_hostname_allow="NO"
jail_list="www db"
jailsffss_enable="YES"

Lo más importante es la lista de jails y habilitar nuestro servicio de montado de filesystems ZFS.

A continuación, definir los jails dentro del archivo /etc/jail.conf:

root@fbsd10:~ # nano /etc/jail.conf

La página del Handbook de FreeBSD respecto a la creación de los jails en este momento está desactualizada, y los configura dentro del archivo /etc/rc.conf, lo cual es obsoleto.

Para cada jail se define su nombre de host, dirección IP, interfaz de red y directorio raíz:

www {
  path = /zjails/jail/www;
  mount.devfs;
  host.hostname = www.fbsd10.linuxito.com;
  ip4.addr = 192.168.39.204;
  interface = em0;
  exec.start = "/bin/sh /etc/rc";
  exec.stop = "/bin/sh /etc/rc.shutdown";
}

db {
  path = /zjails/jail/db;
  mount.devfs;
  host.hostname = db.fbsd10.linuxito.com;
  ip4.addr = 192.168.39.205;
  interface = em0;
  exec.start = "/bin/sh /etc/rc";
  exec.stop = "/bin/sh /etc/rc.shutdown";
}

La sintaxis de este archivo está documentada en su página de manual (man jail.conf).

Crear los puntos de montaje para los jails:

root@fbsd10:/zjails/jail # mkdir /zjails/jail/www
root@fbsd10:/zjails/jail # mkdir /zjails/jail/db

Luego crear una copia del skel (porción de lectura/escritura) inicial para cada jail:

root@fbsd10:/zjails/jail # zfs create zjails/jails
root@fbsd10:/zjails/jail # cpdup /zjails/jail/skel /zjails/jails/www
root@fbsd10:/zjails/jail # cpdup /zjails/jail/skel /zjails/jails/db

Además ,crear una copia del archivo /etc/rc.conf del host (como plantilla) para cada jail:

root@fbsd10:/zjails/jail # cp /etc/rc.conf /zjails/jails/www/etc/
root@fbsd10:/zjails/jail # cp /etc/rc.conf /zjails/jails/db/etc/

Editar la configuración de cada uno de acuerdo a sus necesidades y servicios que requiera:

root@fbsd10:/zjails/jail # nano /zjails/jails/www/etc/rc.conf

Por ejemplo, para el jail "fbsd10-www.linuxito.com":


hostname="fbsd10-www.linuxito.com"
keymap="spanish.iso.kbd"
dumpdev="NO"

ntpd_enable="YES"
sshd_enable="YES"
httpd_enable="YES"

sendmail_enable="NO"
sendmail_submit_enable="NO"
sendmail_outbound_enable="NO"
sendmail_msp_queue_enable="NO"

Inicio, verificación y gestión de jails

Habiendo configurado los jails, es posible iniciar el servicio jail:

root@fbsd10:/zjails/jail # service jail start
Starting jails: www db.

El comando jls permite listar los jails en ejecución:

root@fbsd10:/zjails/jail # jls
   JID  IP Address      Hostname                      Path
     1  192.168.39.204  fbsd10-www.linuxito.com       /zjails/jail/www
     2  192.168.39.205  fbsd10-db.linuxito.com        /zjails/jail/db

Para abrir a la consola serie de cada jail basta con ejecutar jexec junto con el número de ID del jail (JID en la salida de jls):

root@fbsd10:/zjails/jail # jexec 1
root@fbsd10-www:/ #

Voilà.

El primer paso una vez dentro del jail consiste en cambiar el password de root:

root@fbsd10-www:/ # passwd

No se recomienda utilizar el mismo password del usuario root del host.

Es posible iniciar, detener y reiniciar jails recurriendo al servicio jail, por ejemplo:

root@fbsd10:~ # service jail stop www
Stopping jails: www.
root@fbsd10:~ # service jail start www
Starting jails: www.

Dentro de los jails no es posible configurar firewalls o modificar interfaces de red, todo ello es responsabilidad del host. Para cada jail, su dirección IP es un alias en la interfaz de red definida en el archivo /etc/jail.conf.

Si se detiene un jail, su dirección IP se elimina de la interfaz de red correspondiente, por ejemplo:

root@tempdgsi8:~ # ifconfig em0
em0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
        options=9b<RXCSUM,TXCSUM,VLAN_MTU,VLAN_HWTAGGING,VLAN_HWCSUM>
        ether 00:50:56:a6:19:4a
        inet 192.168.39.100 netmask 0xffffff00 broadcast 192.168.39.255 
        inet 192.168.39.204 netmask 0xffffffff broadcast 192.168.39.205 
        inet 192.168.39.205 netmask 0xffffffff broadcast 192.168.39.204 
        nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL>
        media: Ethernet autoselect (1000baseT <full-duplex>)
        status: active
root@fbsd10:~ # service jail stop db
Stopping jails: db.
root@fbsd10:~ # ifconfig em0
em0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
        options=9b<RXCSUM,TXCSUM,VLAN_MTU,VLAN_HWTAGGING,VLAN_HWCSUM>
        ether 00:50:56:a6:19:4a
        inet 192.168.39.100 netmask 0xffffff00 broadcast 192.168.39.255 
        inet 192.168.39.204 netmask 0xffffffff broadcast 192.168.39.204 
        nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL>
        media: Ethernet autoselect (1000baseT <full-duplex>)
        status: active

Cuando se detiene el jail "db", su dirección IP (192.168.39.205) es eliminada de la interfaz em0.

Para finalizar, configurar el firewall en el host (por ejemplo IPFW o PF) para permitir el acceso a los servicios provistos por cada jail.

Referencias

man zfs
man freebsd-update
man cpdup
man mergemaster
man rc.d
man jail
man jail.conf

Virtualización a nivel de sistema operativo - Wikipedia

FreeBSD jail - Wikipedia

Jails - FreeBSD Handbook

jail(8) - FreeBSD Man Pages

Practical rc.d scripting in BSD

FreeBSD IPFW Firewall for a Host and a Jail


Tal vez pueda interesarte


Compartí este artículo