Sandboxes
Kubernetes solo es seguro si el container runtime es seguro.
Un contenedor se ejecuta como un proceso en el kernel de Linux como cualquier otro proceso del propio host. La diferencia es que este proceso está limitado en un kernel group a través de los namespaces como vimos anteriormente en review container. Si un contenedor logra salir de su kernel group y gestionar directamente el kernel, podría tener acceso a todos los demás procesos que se ejecutan en el host. De manera diferente, en una VM tenemos un aislamiento completo entre kernels.

¿Qué es sandbox? Ya hemos escuchado este término anteriormente en diferentes contextos, pero en el caso de contenedores estamos hablando de ejecutar un contenedor con una capa extra de seguridad para reducir ataques.
- Kernel Space
- Tenemos el hardware en la capa inferior.
- En una capa superior controlando el hardware tenemos el kernel.
- El kernel provee una capa de API o interfaces (syscall o system call) para que los procesos se comuniquen con él. Algunos ejemplos:
pspara ver los procesos.ip linkpara listar las interfaces de red.reboot
Podemos hacer esto a través de línea de comandos o usando bibliotecas en cualquier lenguaje. Veremos mejor sobre esto más adelante en el curso.
- User Space
- Cualquier tipo de proceso que pueda utilizar syscalls.
- Lo que no se ejecuta en kernel space se ejecuta en user space.
Lo que vamos a hacer es incluir una nueva capa de seguridad para controlar las llamadas de sistema. El contenedor (proceso) hará una llamada al sandbox y este hará la llamada de syscall actuando como un proxy en el cual podemos crear restricciones.
| Normal | Sandbox |
|---|---|
![]() |
Obviamente tendremos un costo de rendimiento y recursos porque estamos colocando una capa lógica adicional en el camino.
Sandboxes no es útil para cualquier tipo de aplicación.
- Mejor utilizado en contenedores pequeños. En contenedores grandes puede causar sobrecarga.
- No es bueno para workloads con muchas llamadas de syscall generaría mucho retraso.
- No tiene acceso directo al hardware.
Es útil para la mayoría de las aplicaciones, no todas.
Vamos a mostrar una llamada de syscall simple que es uname.
root@cks-master:~# k get pods
NAME READY STATUS RESTARTS AGE
nginx 1/1 Running 0 11h
root@cks-master:~# k exec nginx -it -- bash
# Dentro del contenedor retornó el kernel usado
root@nginx:/# uname -r
5.15.0-1067-gcp
root@nginx:/# exit
exit
# Dentro del host retornó el kernel
root@cks-master:~# uname -r
5.15.0-1067-gcp
# Usando strace podemos ver qué syscalls fueron usadas.
# Voy a reducir la salida para facilitar la lectura
root@cks-master:~# strace uname -r
execve("/usr/bin/uname", ["uname", "-r"], 0x7ffd0d749d98 /* 29 vars */) = 0
brk(NULL) = 0x5636fe7ac000
arch_prctl(0x3001 /* ARCH_??? */, 0x7ffc99764540) = -1 EINVAL (Invalid argument)
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=23857, ...}) = 0
mmap(NULL, 23857, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f5d304d2000
close(3) = 0
...
close(3) = 0
openat(AT_FDCWD, "/usr/lib/locale/C.UTF-8/LC_CTYPE", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=201272, ...}) = 0
mmap(NULL, 201272, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f5d2fe49000
close(3) = 0
uname({sysname="Linux", nodename="cks-master", ...}) = 0 # <<<<
fstat(1, {st_mode=S_IFCHR|0600, st_rdev=makedev(0x88, 0), ...}) = 0
write(1, "5.15.0-1067-gcp\n", 165.15.0-1067-gcp
) = 16
close(1) = 0
close(2) = 0
exit_group(0) = ?
+++ exited with 0 +++
Varias llamadas de sistema fueron usadas incluyendo close, write, etc.
Dirt Cow era una vulnerabilidad crítica de Linux para escalamiento de privilegios en el kernel, un bug relacionado al proceso copy-on-write (COW). Esta vulnerabilidad permitía que usuarios locales sin privilegios consiguieran obtener acceso de escritura en partes de la memoria que deberían ser solo lectura y era explotado para obtener acceso root. Todo esto hecho usando llamada de syscall.
Open Container Initiative (OCI)
OCI es un proyecto de la Linux Foundation para diseñar estándares para virtualización de contenedores.
-
Especificación: Crea y mantiene una especificación para containers runtimes, definiendo qué es un runtime, una imagen, una distribución y por eso tenemos diferentes containers runtimes que siguen el mismo estándar y pueden ejecutar las mismas imágenes de contenedor. El kubelet puede usar diferentes containers runtime siempre que sigan las especificaciones de OCI. -
Runtime: runc es el container runtime mantenido por OCI que implementa las especificaciones definidas.
Docker no crea contenedores directamente. Se comunica en diferentes capas, siendo una de ellas containerd que se comunica con runc. El runc implementa las especificaciones de OCI y crea los contenedores comunicándose con libcontainer que interactúa con recursos del kernel de Linux como namespaces cgroups, chroot, para gestionar contenedores sin depender de procesos externos como LXC.

El CRI, Container Runtime Interface permite la comunicación del kubelet con diferentes runtimes. La configuración del container runtime default puede ser definida en el archivo de configuración del kubelet en /var/lib/kubelet/config.yaml.
cat /var/lib/kubelet/config.yaml | grep container
containerRuntimeEndpoint: ""
root@cks-master:~# ps -aux | grep kubelet
root 939 2.4 1.6 2119304 66152 ? Ssl Aug27 18:00 /usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --config=/var/lib/kubelet/config.yaml --container-runtime-endpoint=unix:///var/run/containerd/containerd.sock --pod-infra-container-image=registry.k8s.io/pause:3.9 --container-runtime-endpoint unix:///run/containerd/containerd.sock
...
root@cks-master:~# cat /usr/lib/systemd/system/kubelet.service
[Unit]
Description=kubelet: The Kubernetes Node Agent
Documentation=https://kubernetes.io/docs/
Wants=network-online.target
After=network-online.target
[Service]
ExecStart=/usr/bin/kubelet
Restart=always
StartLimitInterval=0
RestartSec=10
[Install]
WantedBy=multi-user.target
root@cks-master:~# cat /etc/default/kubelet
KUBELET_EXTRA_ARGS="--container-runtime-endpoint unix:///run/containerd/containerd.sock"
Comparación Sandboxes
Tenemos aquí una imagen que muestra algunos containers runtime que podemos utilizar.

Como hablamos anteriormente, container sandboxes ofrecen una capa adicional de aislamiento y seguridad para contenedores, siendo especialmente útiles en ambientes multi-tenant o cuando hay necesidad de un aislamiento más fuerte del que es ofrecido por contenedores tradicionales. Algunas opciones:
Kata Containers
Desarrollado por OpenStack Foundation, Kata Containers combina la velocidad de los contenedores con el aislamiento de máquinas virtuales. Ejecuta cada contenedor dentro de una VM ligera, usando tecnologías de virtualización para aislar el contenedor del sistema host, sin sacrificar el rendimiento.
- Capa fuerte de separación.
- Fácil de gestionar esas vms porque son creadas automáticamente.
- Cada contenedor se ejecuta en una máquina virtual privada, pero es necesario que el host tenga un hypervisor.
- QEMU es el default y necesita una virtualización, la misma utilizada en cloud.
- El problema es que en cloud, virtualizar encima de otra virtualización es un poco más complicado necesitando activar ese recurso y no siendo tan fácil de configurar.

Perfecto para workloads que exigen un aislamiento más fuerte, como servicios críticos en nube y ambiente multi tenant. No es muy recomendado para kubernetes.
gVisor

Desarrollado por Google, gVisor es un runtime de contenedores que proporciona una capa extra de aislamiento entre el contenedor y el kernel del sistema host, implementando un kernel user-space que intercepta y manipula llamadas del sistema.
- Otra capa de separación.
- No necesita un hypervisor.
- Simula las syscalls del kernel con limitación de funcionalidades. En verdad gVisor implementa su propio Kernel en golang que acepta las llamadas y manipula (transforma e ignora) haciendo la llamada real al kernel en el host.
- Se ejecuta en user space separado del kernel linux.
- Runtime se llama runsc.

Perfecto para workloads en que la compatibilidad con Kubernetes y Docker es importante, pues no sacrifica mucho el rendimiento. Es ligero, fácil de integrar en infraestructuras existentes y ofrece una capa de aislamiento extra sin necesidad de ejecutar VMs completas como Kata.
Nabla Containers
Nabla Containers, desarrollado por IBM, usa un enfoque unikernel-like, donde el contenedor es ejecutado con un conjunto extremadamente reducido de llamadas de sistema. Esto minimiza la superficie de ataque, pues el contenedor tiene acceso a mucho menos funcionalidades del kernel. Con menos llamadas de sistema expuestas, la posibilidad de explotación de vulnerabilidades es reducida. Esto hace de Nabla una opción muy segura, especialmente en escenarios donde la superficie de ataque mínima es esencial.
El enfoque extremadamente restrictivo puede ser limitante para muchos workloads. Aplicaciones que necesitan más interacciones con el sistema operativo pueden no funcionar bien en Nabla. Su utilización es más especializada y puede requerir modificaciones en las imágenes y en las bibliotecas utilizadas por la aplicación.
Indicado para ambientes donde el mínimo de superficie de ataque es deseado. Un host en kubernetes que utiliza Nabla quedaría muy reducido en compatibilidad, necesitaría ser algo muy específico.
SCONE
Curiosidad solamente, en caso de que algún día lo necesites, pero es una tecnología de pago.
SCONE (Secure Computing Node Environment) ofrece contenedores seguros que utilizan enclaves Intel SGX para garantizar que los datos sean protegidos incluso en memoria. Es una solución robusta para proteger datos sensibles y aplicaciones.
Los datos son encriptados dentro de los enclaves (áreas de memoria protegida que están aisladas del resto del sistema, incluso si el kernel o el hypervisor son comprometidos), garantizando que hasta los administradores del sistema u operadores de Kubernetes no puedan acceder a los datos sin autorización.
- La imagen necesita ser sconetizada justo después del build.
- El node necesita poseer soporte a SGX generando costo extra.
- Conocimientos específicos para la configuración y desarrollo de las aplicaciones.
Uso específico para aplicaciones que manipulan datos altamente sensibles o que requieren seguridad confidencial pues todo es encriptado siendo difícil hasta para los propios administradores. No es una solución gratuita y su uso es extremadamente restringido.
Analizando lo que tenemos arriba, lo que ofrece el mejor rendimiento/compatibilidad sin necesidad de mucha intervención es gVisor. Claro que estamos hablando de una situación genérica.
