Mejores prácticas para el aislamiento de contenedores
Maryann Agofure
29 de agosto de 2022
0 minutos de lecturaLos contenedores son un formato estandarizado para empaquetar software que ofrece una forma predecible y reproducible de ejecutar aplicaciones. El aislamiento de contenedores es uno de los principales beneficios de las aplicaciones en contenedores. El uso de contenedores nos permite aislar el software de su entorno, lo que aumenta la consistencia y la confiabilidad en los entornos de desarrollo y preproducción.
Probablemente ya conozcas los contenedores de Docker o los estés usando. Docker logra el aislamiento mediante funciones de Linux, como los grupos de control (conocidos comúnmente como cgroups), los filtros del modo de computación segura (seccomp) y los espacios de nombres del kernel. También existen otros tipos de contenedores, como los contenedores en sandbox, por ejemplo gVisor, y los contenedores virtualizados, como AWS Firecracker.
Aunque los contenedores están aislados por diseño, aún debemos implementar las mejores prácticas para garantizar que los aislemos de manera eficaz y mantenerlos seguros. Este artículo analiza las implicaciones del aislamiento de contenedores y presenta las mejores prácticas de seguridad y metodología para contenedores de Linux, en sandbox y virtualizados.
¿Qué es el aislamiento de contenedores y cómo funciona?
Como su nombre indica, el aislamiento de contenedores consiste en separar el entorno de ejecución de una aplicación en contenedor del sistema operativo anfitrión y de los demás procesos que se ejecutan en él.
Este aislamiento adopta varias formas: aislamiento del sistema de archivos, de la red y de las llamadas al sistema, así como de los recursos utilizados, como la CPU y la memoria.
Los detalles técnicos del funcionamiento del aislamiento dependen del tipo de contenedor que usemos. Como veremos, hay varias opciones.
Distintos enfoques para el aislamiento de contenedores
Como mencionamos antes, en este artículo analizaremos el aislamiento de contenedores y su relación con tres tipos distintos: los contenedores de Linux, como Docker; los contenedores en sandbox, como gVisor; y la virtualización ligera basada en KVM, como AWS Firecracker.
Cada tipo de contenedor aborda el aislamiento de manera diferente y aísla distintas partes del sistema. Además, cada tipo tiene sus propias mejores prácticas para aislar los contenedores.
Exploremos las mejores prácticas para aislar cada tipo de contenedor.
Contenedores de Linux
Los contenedores de Linux, como Docker, usan cgroups, filtros seccomp y espacios de nombres del kernel para aislar los contenedores. Los cgroups permiten limitar el uso de recursos de un grupo de procesos. Por ejemplo, pueden establecer límites para distintos recursos, como la E/S de disco, la memoria, la red, el tiempo de CPU e incluso las CPU individuales de un sistema multinúcleo. Seccomp permite filtrar todas las llamadas al sistema que realiza un proceso. Este filtrado funciona junto con la técnica de espacios de nombres del kernel y permite que las funciones dentro de un contenedor tengan una vista aislada del sistema.
Este enfoque es el más sencillo, pero también significa que Docker debe garantizar que todas las aplicaciones en contenedores configuren correctamente las marcas de sus espacios de nombres al crearse.
El enfoque de Docker es relativamente simple, por lo que es fácil de usar. Sin embargo, esta simplicidad implica que su aislamiento no es tan sólido como el de alternativas como gVisor. Docker debe permitir algunas llamadas al sistema a través del límite entre espacios de nombres. Esto significa que una aplicación aún puede acceder a información del anfitrión si existe un argumento válido para una llamada determinada en el filtro seccomp.
Por suerte, podemos seguir algunas mejores prácticas para aprovechar al máximo el aislamiento de contenedores en Docker:
Usa un usuario dedicado para cada contenedor y especifica una cuenta de usuario en tu Dockerfile. Así evitarás que un contenedor acceda a los recursos de otro o los modifique (por ejemplo, archivos y directorios). Al crear imágenes nuevas de Docker y configuraciones de runc, usa usuarios con los privilegios mínimos posibles para los contenedores.
Limita las capacidades de los contenedores mediante las capacidades de Linux mencionadas anteriormente. Aplica el principio de mínimo privilegio: dale a cada contenedor solo lo que necesita para realizar sus tareas. Este principio limita lo que una aplicación dentro de un contenedor puede ver o hacer fuera de su entorno aislado.
Impide que se configuren dispositivos de red innecesarios para cada contenedor. Esto limita lo que un contenedor puede ver y hacer en la infraestructura de red del anfitrión.
Usa las restricciones de cgroups para limitar los recursos disponibles para cada contenedor, como las cuotas de CPU, las páginas de memoria, el ancho de banda de E/S de bloques y otros.
Configura parámetros del kernel, como la cantidad de PID, el tamaño máximo de la pila y la cantidad máxima de subprocesos que puede generar un contenedor. Esto ayuda a evitar que un contenedor tome el control del espacio de nombres de PID de otro o provoque una falla del kernel del anfitrión al generar un pánico.
En conjunto, estas prácticas reducen la superficie de ataque de los contenedores al limitar la cantidad de elementos que un atacante puede explotar. Por supuesto, la mejor defensa de seguridad es no ejecutar nunca código que no sea de confianza en los contenedores. Sin embargo, para hacerlo debemos saber exactamente qué se ejecuta en cada contenedor, algo que rara vez es realista para los equipos de desarrollo y DevOps que trabajan contra plazos ajustados.
Si sabemos que necesitamos ejecutar un contenedor que no es de confianza, la siguiente mejor práctica que debemos considerar es usar un entorno de ejecución de contenedores con un modelo de seguridad más sólido.
Contenedores en sandbox
Los contenedores en sandbox ofrecen los mismos mecanismos de aislamiento que los contenedores de Linux convencionales y agregan capas adicionales de protección.
gVisor, un excelente contenedor en sandbox, implementa un minikernel personalizado en el espacio de usuario, ubicado entre las aplicaciones en contenedores y el kernel del anfitrión. Intercepta todas las llamadas al sistema del contenedor y verifica las políticas antes de pasar cada llamada al kernel del anfitrión. gVisor también implementa una pila TCP/IP personalizada para tener un mayor control sobre la forma en que las cargas de trabajo en contenedores interactúan con la red. Por último, gVisor implementa un proxy del sistema de archivos entre el contenedor y el sistema de archivos del anfitrión. Estas verificaciones en capas permiten que gVisor reduzca la superficie de ataque de un contenedor al aplicar límites firmes entre contenedores y mantener la compatibilidad con la mayoría de las aplicaciones. Además, como gVisor está escrito en Go, un lenguaje seguro para la memoria, es mucho menos probable que sufra desbordamientos de búfer y otros ataques que las aplicaciones escritas en C, como el kernel de Linux.
Sin embargo, el enfoque de gVisor también tiene algunas desventajas. Una de ellas es que puede ser difícil depurar las aplicaciones que se ejecutan en gVisor, ya que no podremos usar la mayoría de las herramientas diseñadas para el kernel de Linux. Otra desventaja es que, como gVisor no usa un kernel estándar de Linux, quizá debamos volver a implementar algunas funciones para admitir ciertas cargas de trabajo.
Aunque gVisor y otros contenedores en sandbox ofrecen más seguridad que los contenedores de Linux convencionales, quizá la mejor práctica sea pasar a un modelo de contenedor con un aislamiento aún más sólido.
Máquinas virtuales ligeras
A diferencia de los contenedores de Linux y los contenedores en sandbox, los contenedores ligeros basados en máquinas virtuales (VM), como AWS Firecracker, adoptan un enfoque completamente distinto. Usan un hipervisor, como KVM o qemu, para crear VM ligeras (normalmente llamadas microVM) para cada contenedor. Este sólido aislamiento reduce al mínimo la superficie de ataque dentro de las microVM invitadas y facilita su control.
Esta técnica dificulta mucho que un atacante acceda a información privilegiada del anfitrión, pero también reduce las formas en que un contenedor puede interactuar con él. Por ejemplo, los contenedores que se ejecutan en microVM no pueden compartir archivos directamente con el anfitrión.
Las microVM tienen una superficie de ataque menor que los contenedores que se ejecutan en VM tradicionales, porque solo necesitan admitir un subconjunto de los dispositivos de hardware compatibles con los kernels de Linux convencionales.
En general, las microVM son la mejor opción cuando necesitamos un aislamiento muy sólido entre contenedores, por ejemplo, al ejecutar código que no es de confianza de varios inquilinos en el mismo servidor.
Cómo abordar el aislamiento de contenedores
Independientemente del tipo de contenedor que usemos, no existe una solución única para el aislamiento de contenedores. Por eso, debemos equilibrar los siguientes factores según los requisitos específicos de cada proyecto.
Rendimiento. Los contenedores de Linux ofrecen el mejor rendimiento porque tienen menos capas de intermediación entre el contenedor y el sistema operativo anfitrión. Los contenedores en sandbox son considerablemente más lentos debido a las capas adicionales entre el contenedor y el sistema operativo anfitrión. Las microVM tienen el mayor impacto en el rendimiento, ya que deben proporcionar una interfaz de hardware virtualizada a cada contenedor.
Seguridad. Cuanto mayores sean las capacidades de aislamiento de los contenedores, más seguros serán. Como vimos, los contenedores en sandbox son más seguros que los contenedores de Linux, y los contenedores basados en microVM son más seguros que ambos.
Complejidad y tiempo de desarrollo. Los contenedores de Linux son relativamente simples y representan la mayoría de los contenedores que se ejecutan en producción. Hay muchas herramientas y documentación excelentes, lo que reduce el tiempo de desarrollo y la complejidad de implementar aplicaciones en contenedores. Los contenedores en sandbox y las microVM son mucho menos comunes, por lo que muchas herramientas habituales no son compatibles con ellos. Como son menos populares, menos herramientas y plataformas los admiten. Esto aumenta el tiempo de desarrollo y la complejidad, porque los equipos de desarrollo y DevOps deben hacer más trabajo manual en lugar de depender de las herramientas.
En definitiva, tendremos que encontrar un equilibrio entre el rendimiento, la seguridad y la complejidad. Por ejemplo, priorizar el rendimiento podría implicar renunciar a cierto nivel de seguridad. Por otro lado, priorizar la seguridad podría implicar sacrificar rendimiento y dedicar más tiempo al desarrollo.
Seguridad de contenedores
Como desarrolladores y profesionales de DevOps, debemos saber cómo se aíslan nuestros contenedores de las máquinas anfitrionas para entender las limitaciones de ese aislamiento. Aunque los contenedores permiten ciclos de desarrollo rápidos, es importante aplicar las mejores prácticas para mantener seguros tanto los contenedores como las aplicaciones.
Todos los equipos de desarrollo y DevOps deben aplicar una estrategia de defensa en profundidad, y el aislamiento de contenedores es solo una pieza del rompecabezas. Puedes mejorar la seguridad de tus sistemas y asegurarte de que tu código no tenga vulnerabilidades con un analizador de código y un analizador de contenedores para mantener seguro el contenido de tus contenedores. Al fin y al cabo, el aislamiento de contenedores debe ser una línea de defensa, no la única.
Seguridad de contenedores centrada en los desarrolladores
Snyk encuentra y corrige automáticamente vulnerabilidades en imágenes de contenedores y cargas de trabajo de Kubernetes.
