Capítulo 10: El Jailer — Seguridad y Aislamiento
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:
- Crea un entorno chroot en
/srv/jailer/<binary-name>/<vm-id>/root/ - Copia el binario de Firecracker al jail (evita memoria compartida entre VMs)
- Crea nodos de dispositivo con
mknod(solo/dev/kvmy/dev/net/tun) - Aplica cgroups v2 para limitar CPU, memoria e I/O
- Hace
pivot_rootpara cambiar la raíz del filesystem - Descarta privilegios — cambia a un uid/gid no-root especificado
- Une a un network namespace (opcional) para aislamiento de red
- 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
memory.max=2G: límite de 2 GB de RAMcpu.max=200000 1000000: 20% de CPU (200ms de cada 1000ms)
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:
- API thread: filtros aplicados antes de lanzar el servidor HTTP
- VMM thread: filtros aplicados antes de ejecutar código del guest
- vCPU threads: filtros aplicados antes de
KVM_RUN
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
- Requiere ejecutarse como root inicialmente (los privilegios se descartan después del setup)
- El rendimiento degrada linealmente con el número de mount points del sistema host
- Solo escribe logs a stdout/stderr (no a archivo)
- El ID de la VM debe ser único en el host — usar UUID generados
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"