Seguridad del runtime de containers: protegiendo containers en ejecución
Guía de seguridad del runtime de containers: primitivas de aislamiento, flags de hardening, vectores de escape y detección en containers en producción.
La seguridad del runtime de containers es la práctica de proteger los containers mientras están efectivamente en ejecución, y no mientras se están construyendo o almacenando. Importa porque un container que se desplegó a partir de una imagen limpia y completamente escaneada no está seguro para siempre. En el momento en que empieza a servir tráfico se convierte en un objetivo en vivo: sus dependencias en ejecución pueden ser alcanzadas por un zero-day recién divulgado, su filesystem puede sufrir drift a medida que un atacante escribe nuevos binarios, y cualquier debilidad en el aislamiento del host puede convertirse en una ruta de salida del container. Esta guía es una inmersión profunda en la protección de containers en runtime — las primitivas de aislamiento que confinan un container, las flags de hardening del runtime de Docker que las ajustan, los vectores de container escape que necesitas cerrar, y la detección y respuesta que atrapan lo que se escapa.
Este artículo forma parte de nuestro clúster de Docker container security. Si aún no has endurecido la imagen en sí, combínalo con nuestra guía de Dockerfile security best practices, y si te ejecutas sobre Kubernetes, léelo junto con Kubernetes pod security.
Por qué importa la seguridad en runtime
Es tentador pensar que la seguridad termina cuando una imagen escaneada pasa el pipeline. No es así. Un escaneo en tiempo de build es una instantánea de un momento que ya quedó en el pasado para cuando el container está en operación.
Tres cosas cambian una vez que un container está en ejecución. Primero, se divulgan nuevas vulnerabilidades constantemente. La biblioteca que estaba limpia cuando construiste la imagen el mes pasado puede tener una CVE crítica anunciada hoy, y el container en ejecución queda expuesto hasta que se reconstruye y se redespliega. Segundo, los containers sufren drift. Un atacante que logra ejecución de código dentro de un container puede instalar herramientas, escribir payloads, abrir shells y abrir conexiones de red — nada de lo cual existía en la imagen original. La brecha entre lo que desplegaste y lo que ahora está en ejecución es exactamente donde vive una intrusión. Tercero, el aislamiento entre container y host es tan fuerte como su configuración. Un container no es una máquina virtual; comparte el kernel del host, y una sola flag demasiado amplia puede colapsar por completo la frontera.
La seguridad en runtime es la capa que asume que el compromiso ocurrirá tarde o temprano y trabaja para mantenerlo contenido y visible.
Las primitivas de aislamiento — y cómo ajustarlas
Los containers se aíslan mediante funciones del kernel de Linux, no mediante un hypervisor. Entender estas primitivas es todo el juego, porque endurecer un container significa ajustar cada una de ellas más allá de su valor predeterminado.
Los namespaces le dan a un container su propia visión del sistema — su propio árbol de procesos, stack de red, tabla de montaje e IDs de usuario. Son lo que hace que un container se sienta como una máquina separada. El valor predeterminado suele ser sólido, pero no lo debilites: compartir el PID, la red o el namespace de IPC del host con el container reintroduce exactamente la visibilidad que los namespaces existen para eliminar.
Los cgroups (control groups) limitan cuánta CPU, memoria e I/O puede consumir un container. Esto es un control de seguridad, no solo de operaciones: un container sin límites puede agotar un host y dejar sin recursos a todos los vecinos, lo cual es una condición de denegación de servicio, ya llegue por un bug o por malicia. Establece límites explícitos:
docker run --memory=256m --cpus=0.5 --pids-limit=100 myapp:1.4.2
Las capabilities de Linux dividen el poder de root en privilegios discretos. Por defecto, Docker le concede a un container un conjunto moderado, pero la mayoría de las aplicaciones casi no necesita ninguno de ellos. La postura correcta es eliminar todo y volver a agregar solo lo que sea genuinamente necesario:
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE myapp:1.4.2
Y nunca recurras a --privileged, que desactiva la mayoría de estas protecciones de una sola vez (más sobre esto abajo).
El seccomp filtra las llamadas al sistema que un container puede hacer. Docker viene con un profile de seccomp predeterminado que ya bloquea decenas de syscalls peligrosas; el error es ejecutar con --security-opt seccomp=unconfined, que apaga ese filtro. Mantén el predeterminado, o proporciona un profile personalizado más restrictivo para cargas de trabajo sensibles.
El AppArmor y el SELinux son sistemas de control de acceso obligatorio que restringen qué archivos y recursos puede tocar un proceso, independientemente de los permisos Unix. Docker aplica un profile de AppArmor predeterminado donde está disponible; en hosts con SELinux, las labels imponen una frontera similar. Déjalos habilitados y escribe un profile personalizado cuando una carga de trabajo lo justifique.
Dos flags más pertenecen a todo docker run endurecido:
docker run \
--read-only \
--tmpfs /tmp \
--security-opt no-new-privileges \
--cap-drop=ALL \
myapp:1.4.2
La `--read-only` hace que el filesystem raíz del container sea inmutable, de modo que un atacante no pueda soltar un payload ni sobrescribir un binario; monta un `--tmpfs` para el puñado de rutas que genuinamente necesitan poder escribirse. La `--security-opt no-new-privileges` impide que un proceso obtenga más privilegios que su padre — por ejemplo, mediante un binario setuid —, lo que cierra un paso común de escalada.
El socket de Docker es un riesgo de runtime
Una configuración incorrecta merece su propia sección porque es a la vez común y catastrófica: montar el socket de Docker.
El daemon de Docker escucha en /var/run/docker.sock, y cualquier cosa capaz de comunicarse con ese socket puede controlar el daemon — lo que significa que puede iniciar un nuevo container, montar el filesystem raíz del host dentro de él y ejecutarse como root en el host. Montar el socket dentro de un container (-v /var/run/docker.sock:/var/run/docker.sock) equivale, por tanto, a concederle a ese container el control total del host y de todos los demás containers en él.
Los CI runners, los agentes de monitoreo y las herramientas de dashboard suelen pedir el socket por comodidad. Trata cada solicitud de ese tipo como un riesgo de toma del host. Prefiere un socket proxy que exponga solo las llamadas de API específicas y de solo lectura que una herramienta realmente necesita, ejecuta la carga de trabajo de forma rootless, o usa un enfoque de tooling sin daemon. Si un container realmente debe gestionar containers, aíslalo y nunca expongas esa capacidad a nada que maneje entrada no confiable.
Runtime rootless y user namespaces
Por defecto, el daemon de Docker se ejecuta como root, y un proceso que escapa de un container a menudo cae con poder equivalente a root en el host. Dos mecanismos reducen ese radio de impacto.
Los user namespaces reasignan el root del container (UID 0) a un UID sin privilegios en el host. Aunque un proceso crea que es root dentro del container, para el kernel del host es un usuario nobody sin privilegios significativos — de modo que un escape rinde mucho menos. Habilita esto con la configuración userns-remap del daemon.
El rootless mode va más allá y ejecuta todo el runtime del container como un usuario no-root, de modo que el daemon en sí nunca tiene root. Cierra la clase de ataques que apuntan directamente al daemon privilegiado. El rootless mode tiene algunas concesiones de funcionalidad, pero para muchas cargas de trabajo es el paso de hardening de runtime de mayor apalancamiento disponible.
Vectores de container escape y sus mitigaciones
Un container escape ocurre cuando un proceso rompe su aislamiento y obtiene acceso al host o a otros containers. Casi todos los escapes del mundo real provienen de una de unas pocas fuentes:
- Containers privilegiados. El
--privilegedconcede casi todas las capabilities, desactiva el confinamiento por seccomp y AppArmor y expone los dispositivos del host. Desde un container privilegiado, escapar al host suele ser trivial. Mitigación: nunca ejecutes privilegiado; elimina capabilities y vuelve a agregar el mínimo. - Montajes del host. Hacer bind mount de una ruta sensible del host —
/,/etc,/var/runo un nodo de dispositivo — permite que un container comprometido lea o manipule el host directamente. Mitigación: monta solo lo necesario, en solo lectura siempre que sea posible, y nunca montes el filesystem raíz ni el socket de Docker en cargas de trabajo no confiables. - El socket de Docker expuesto. Cubierto arriba — una ruta directa al root del host. Mitigación: mantén el socket fuera de los containers, o ponlo detrás de un proxy restrictivo.
- Exploits de kernel. Como todo container comparte el kernel del host, una vulnerabilidad a nivel de kernel puede explotarse desde dentro de un container para romper la frontera. Mitigación: aplica parches al kernel del host con prontitud, mantén el seccomp habilitado para reducir la superficie de syscall alcanzable, y considera un runtime en sandbox que agregue una capa entre container y kernel para cargas de trabajo de alto riesgo.
El patrón en los cuatro casos es el mismo: el escape solo es posible porque se aflojó un valor predeterminado. El hardening de runtime es, en gran medida, la disciplina de no aflojarlos.
Detección y respuesta en runtime
El hardening reduce las probabilidades de un ataque exitoso; no las elimina, y no te dice nada cuando uno está en curso. Ese es el trabajo de la detección en runtime.
El monitoreo de comportamiento y de syscall observa lo que un container hace realmente frente a un modelo de lo que debería hacer. Un servidor web que de repente abre un shell, lanza un gestor de paquetes o ejecuta un compilador se está comportando de forma anómala, y un monitor a nivel de syscall puede señalarlo o bloquearlo en tiempo real. Este es el modelo popularizado por las herramientas open-source de monitoreo de syscall: define reglas para comportamiento sospechoso — una conexión de salida inesperada, una escritura en una ruta sensible, un intento de escalada de privilegios — y alerta en el instante en que una de ellas se dispara.
La detección de anomalías construye una línea base de la actividad normal de procesos, archivos y red para cada carga de trabajo y expone las desviaciones respecto de ella, lo cual es útil para atrapar los ataques que ninguna regla explícita anticipó.
La detección de drift compara el container en ejecución con la imagen a partir de la cual se construyó. Como un container bien construido debería ser inmutable en runtime, cualquier binario nuevo, archivo modificado o proceso inesperado es una señal fuerte de que algo cambió que no debería haber cambiado — y la detección de drift convierte ese principio en una alerta.
Nada de esto es útil sin logging. Envía los logs del container y del host — ejecución de procesos, conexiones de red, cambios en archivos y eventos del daemon — a un almacenamiento central que los propios containers no puedan alcanzar, de modo que un atacante que comprometa un container no pueda borrar sus rastros. Cuando ocurre un incidente, ese rastro de logs es la diferencia entre una investigación limpia y una conjetura. Construye un plan de respuesta a incidentes en torno a él: cómo aíslas un container sospechoso, preservas su estado para forense y rotas cualquier credencial que pudiera haber tocado, antes de terminarlo.
El vínculo con el runtime de Kubernetes
Todo lo aquí expuesto se aplica igualmente cuando un container se ejecuta dentro de un pod de Kubernetes — las primitivas son las mismas, solo cambia la interfaz. Las flags de docker run de este artículo se corresponden con campos del securityContext de un pod: eliminar capabilities, ejecutar como no-root, un filesystem raíz de solo lectura, allowPrivilegeEscalation: false y un profile de seccomp. Si operas sobre Kubernetes, lee esto junto con nuestra guía de Kubernetes pod security, que cubre cómo la plataforma impone estas mismas protecciones de runtime en todo un cluster.
Cómo ayuda Rainforest
Rainforest lleva la seguridad relevante para el runtime al flujo de trabajo del desarrollador, en lugar de acoplarla después del despliegue. Nuestro escaneo de container security inspecciona tus imágenes y su configuración en busca exactamente de los problemas que este artículo aborda — flags privilegiadas, falta de eliminación de capabilities, un socket de Docker expuesto, límites de recursos ausentes, ejecución como root y dependencias que cargan vulnerabilidades conocidas — y los expone en el pull request, antes de que el container siquiera se ejecute. Como se divulgan nuevas vulnerabilidades después de que una imagen se despliega, ese escaneo es continuo: una dependencia que se vuelva vulnerable la próxima semana se señala contra imágenes que ya están en tu registry, para que sepas qué reconstruir.
El resultado es una imagen consistente a lo largo del ciclo de vida: el mismo hardening que impones en runtime se verifica como código, de modo que una configuración incorrecta se detecta mientras todavía es un cambio pequeño y aún no un incidente en vivo. Es una parte de nuestra plataforma más amplia de application security testing.
Si quieres ver cómo encaja esto en tu pipeline, agenda una demo y recorreremos tus propias imágenes y configuraciones de ejecución.
Preguntas frecuentes
¿Qué es la seguridad del runtime de containers?
La seguridad del runtime de containers es la protección de los containers mientras están en ejecución en producción, a diferencia de proteger la imagen en tiempo de build o en el registry. Abarca ajustar las primitivas de aislamiento del kernel de las que depende un container — namespaces, cgroups, capabilities, seccomp y control de acceso obligatorio — y detectar y responder a comportamiento malicioso en containers que ya están en vivo. Existe porque una imagen limpia todavía puede ser atacada en runtime mediante vulnerabilidades recién divulgadas, drift de filesystem o una frontera de aislamiento débil.
¿Qué es un container escape?
Un container escape ocurre cuando un proceso rompe el aislamiento de su container y obtiene acceso al sistema host o a otros containers en él. Como los containers comparten el kernel del host, un escape frecuentemente significa el compromiso del host, y el compromiso del host pone en riesgo a todos los demás containers de ese host. Las causas comunes son ejecutar containers privilegiados, montar rutas sensibles del host, exponer el socket de Docker y vulnerabilidades de kernel sin parchear.
¿Por qué es peligroso montar el socket de Docker?
El daemon de Docker se controla a través de /var/run/docker.sock, y cualquier cosa que pueda alcanzar ese socket puede comandar el daemon — incluido iniciar un nuevo container que monta el filesystem del host y se ejecuta como root. Montar el socket dentro de un container, por tanto, le concede en la práctica a ese container el control total del host y de todos los containers en él. Si una herramienta lo necesita, pon el socket detrás de un proxy restrictivo que exponga solo las llamadas específicas requeridas, en lugar de montarlo directamente.
¿Qué hace --privileged y por qué evitarlo?
La flag --privileged le da a un container casi todas las capabilities de Linux, desactiva el confinamiento predeterminado por seccomp y AppArmor y expone los dispositivos del host. En efecto, elimina la mayoría de las fronteras que separan al container del host, lo que hace trivial escapar al host para un atacante que logre ejecución de código dentro. En lugar de ejecutar privilegiado, elimina todas las capabilities con --cap-drop=ALL y vuelve a agregar solo las específicas que una carga de trabajo genuinamente necesita.
¿Cómo detecto ataques en containers en ejecución?
Usa detección en runtime: monitoreo de comportamiento y de syscall que señala a un container haciendo algo anómalo, como abrir un shell o abrir una conexión de red inesperada; detección de anomalías que aprende el comportamiento normal de cada carga de trabajo y expone las desviaciones; y detección de drift que compara el container en ejecución con su imagen original para atrapar nuevos binarios o archivos modificados. Combina todo ello con logging centralizado que los containers no puedan alcanzar, para que tengas un rastro confiable para la respuesta a incidentes.

Escrito por
Bruno Baldo
CMO
Um pouco de marketing e um pouco de curiosidade e temos a receita pra criar um apaixonado por cyber!

Seguridad de contenedores Docker: la guía completa
Guía completa de seguridad de contenedores Docker: modelo de amenazas, superficie de ataque del ciclo de vida, hardening del daemon y mejores prácticas.

Mejores prácticas de seguridad en Dockerfile
Mejores prácticas de seguridad en Dockerfile: fija imágenes base confiables, ejecuta como non-root, usa multi-stage, deja los secretos fuera y escanea en CI.

Seguridad de container registry: proteger la distribución de imágenes
La seguridad de container registry protege el punto donde se almacenan y descargan las imágenes. Conozca control de acceso, firma, escaneo y proveniencia.
