Capítulo 10: El Jailer — Seguridad y Aislamiento

Por: Artiko
firecrackerjailerseguridadchrootnamespacescgroupsseccomp

Capítulo 10: El Jailer — Seguridad y Aislamiento

El jailer es un binario complementario a Firecracker que proporciona una capa adicional de aislamiento de seguridad. En producción, siempre deberías arrancar Firecracker a través del jailer.

Modelo de seguridad en capas

graph TD
    A["Guest vCPU threads<br/>(código no confiable)"]
    B["KVM (instrucciones privilegiadas)"]
    C["VMM thread (seccomp)"]
    D["Jailer (chroot + namespaces + cgroups)"]
    E["Kernel del host (más confiable)"]

    A -->|instrucciones privilegiadas| B
    B --> C
    C -->|syscalls filtradas| D
    D -->|syscalls permitidas| E

Qué hace el jailer

Cuando ejecutas jailer ... -- firecracker ..., el jailer:

  1. Crea un entorno chroot en /srv/jailer/<binary-name>/<vm-id>/root/
  2. Copia el binario de Firecracker al jail (evita memoria compartida entre VMs)
  3. Crea nodos de dispositivo con mknod (solo /dev/kvm y /dev/net/tun)
  4. Aplica cgroups v2 para limitar CPU, memoria e I/O
  5. Hace pivot_root para cambiar la raíz del filesystem
  6. Descarta privilegios — cambia a un uid/gid no-root especificado
  7. Une a un network namespace (opcional) para aislamiento de red
  8. Ejecuta Firecracker dentro del jail con privilegios mínimos

Uso básico del jailer

# Prerequisito: uid/gid que usará el jailer
FC_UID=$(id -u)
FC_GID=$(id -g)

sudo jailer \
  --id "vm-001" \
  --exec-file /usr/local/bin/firecracker \
  --uid "${FC_UID}" \
  --gid "${FC_GID}" \
  --cgroup-version 2 \
  -- \
  --api-sock /run/firecracker.socket

Todo lo que va después de -- son argumentos pasados directamente a Firecracker.

Estructura del chroot

Después de ejecutar el jailer, la estructura en el host es:

/srv/jailer/firecracker/vm-001/root/
├── firecracker          ← copia del binario
├── dev/
│   ├── kvm              ← mknod del jailer
│   └── net/
│       └── tun          ← mknod del jailer
└── run/
    └── firecracker.socket   ← socket de la API

Los archivos que necesita Firecracker (kernel, rootfs) deben estar dentro o enlazados al chroot:

JAIL_DIR="/srv/jailer/firecracker/vm-001/root"

# Mover o enlazar el kernel al chroot
sudo ln "${HOME}/firecracker-lab/vmlinux" "${JAIL_DIR}/vmlinux"
sudo ln "${HOME}/firecracker-lab/rootfs.ext4" "${JAIL_DIR}/rootfs.ext4"

Parámetros del jailer

--id <ID>                 Identificador único de la VM (string)
--exec-file <PATH>        Path al binario de Firecracker
--uid <UID>               UID sin privilegios para el proceso
--gid <GID>               GID sin privilegios
--cgroup-version <1|2>    Versión de cgroups (recomendado: 2)
--netns <PATH>            Path al network namespace a usar
--new-pid-ns              Crear nuevo PID namespace
--cgroup <key=value>      Configurar parámetro de cgroup
--resource-limit <key=value>  Configurar ulimit
--daemonize               Ejecutar en background
--parent-cgroup <PATH>    Cgroup padre (para jerarquías)

Cgroups: limitar recursos

sudo jailer \
  --id "vm-001" \
  --exec-file /usr/local/bin/firecracker \
  --uid 1000 --gid 1000 \
  --cgroup-version 2 \
  --cgroup "memory.max=2G" \
  --cgroup "cpu.max=200000 1000000" \
  -- \
  --api-sock /run/firecracker.socket

Network namespaces

Para aislamiento completo de red, cada VM puede tener su propio namespace:

# Crear namespace
sudo ip netns add vm-001-ns

# Crear TAP dentro del namespace
sudo ip netns exec vm-001-ns ip tuntap add dev tap0 mode tap
sudo ip netns exec vm-001-ns ip addr add 172.16.0.1/30 dev tap0
sudo ip netns exec vm-001-ns ip link set tap0 up

# Arrancar jailer en ese namespace
sudo jailer \
  --id "vm-001" \
  --exec-file /usr/local/bin/firecracker \
  --uid 1000 --gid 1000 \
  --cgroup-version 2 \
  --netns /var/run/netns/vm-001-ns \
  -- \
  --api-sock /run/firecracker.socket

Seccomp: filtrado de syscalls

Firecracker aplica filtros seccomp (Secure Computing Mode) por defecto en cada thread antes de ejecutar código del guest:

Si una syscall no está en la lista blanca, el proceso termina inmediatamente con SIGSYS.

Para deshabilitar seccomp (solo en desarrollo):

# NO usar en producción
firecracker --api-sock /tmp/fc.sock --no-seccomp

Limitaciones del jailer

Script de arranque completo con jailer

#!/bin/bash
# start-vm.sh — arrancar una microVM con el jailer

VM_ID="$(uuidgen)"
FC_UID=1000
FC_GID=1000
KERNEL_PATH="/opt/firecracker/kernels/vmlinux-5.10"
ROOTFS_PATH="/opt/firecracker/images/ubuntu-22.04.ext4"
JAIL_BASE="/srv/jailer/firecracker/${VM_ID}/root"

# Preparar archivos en el jail
sudo mkdir -p "${JAIL_BASE}"
sudo ln "${KERNEL_PATH}" "${JAIL_BASE}/vmlinux"
sudo cp "${ROOTFS_PATH}" "${JAIL_BASE}/rootfs.ext4"
sudo chown "${FC_UID}:${FC_GID}" "${JAIL_BASE}/rootfs.ext4"

# Crear network namespace
sudo ip netns add "${VM_ID}"
sudo ip netns exec "${VM_ID}" ip tuntap add dev tap0 mode tap
sudo ip netns exec "${VM_ID}" ip addr add 172.16.0.1/30 dev tap0
sudo ip netns exec "${VM_ID}" ip link set tap0 up

# Arrancar VM via jailer
sudo jailer \
  --id "${VM_ID}" \
  --exec-file /usr/local/bin/firecracker \
  --uid "${FC_UID}" --gid "${FC_GID}" \
  --cgroup-version 2 \
  --netns "/var/run/netns/${VM_ID}" \
  --daemonize \
  -- \
  --api-sock "/run/firecracker.socket" \
  --config-file "/root/vm_config.json"

echo "VM ${VM_ID} iniciada"

Referencia