Patrón Sidecar en Seguridad
El patrón sidecar se presenta como una herramienta muy potente en el nuevo mundo de los contenedores y se le puede encontrar en muchos casos de uso. En el presente post nos centramos en analizar algunos de los casos de uso más interesantes desde una perspectiva de la seguridad en IT.
¿Qué es?
El patrón sidecar se basa en extender y/o mejorar la funcionalidad de un proceso (la aplicación principal) mediante procesos auxiliares que se ejecutan en paralelo sin apenas acoplamiento entre ellos. Es decir, en vez de implementar la funcionalidad en librerías, se implementa con procesos o contenedores independientes, lo que proporciona mayor encapsulación y aislamiento. El sidecar comparte, en tiempo de ejecución, el mismo ciclo de vida que la aplicación principal (se arranca y para a la misma vez) así como otros recursos de computación (almacenamiento, red, ...). El sidecar es un componente separado con su propio ciclo de vida software que es incorporado por el equipo de la aplicación principal en tiempo de despliegue.
Este patrón ya existía antes de los contenedores (mediante el uso de túneles netcat y ssh, pipes, rotatelog, ...), pero su implementación no era tan sencilla ni tan completa como ahora, con la llegada de los contenedores.
Tradicionalmente, los dos modelos para extender aplicaciones eran librerías y servicios, pero con la llegada de los contenedores ahora contamos con otro método adicional. Veamos los pros y contras de los distintos métodos disponibles:
- Intraproceso (librerías)
+ Sin latencia
- Sin aislamiento, problema en un componente puede afectar a los demás
- Una versión por cada lenguaje
- Gestión de dependencias, problemas de integración, ...
- Servicios externos
+ Gestión de dependencias
- Latencia
- Distinto ciclo de vida, problemas de integración (interfaces)
- Seguridad, Access Control, ...
- El patrón sidecar
+ Gestión de dependencias
+ No compartido, sin problemas de integración
+ Transparencia (casi siempre), sin problemas de integración
- Compite por recursos
¿Cuándo usarlo?
El patrón sidecar no es la panacea, hay ciertos criterios que nos ayudarán a elegir cuándo es conveniente usarlo y cuándo se desaconseja su uso:
+ El componente pertenece a un equipo remoto al de la aplicación principal
+ Se requiere que estén en el mismo host
+ La aplicación principal no permite la extensibilidad
+ Siempre con un orquestador de contenedores
+ Necesidad de compartir ciclo de vida y actualización por separado
- Latencia es crítica
- Cuando el costo en recursos no compensa las ventajas en aislamiento
- Distintas necesidades de escalado aplicación-componentes
Casos de uso
El patrón sidecar nos permite proporcionar una gran cantidad de capacidades a las aplicaciones que complementan, entre ellas:
- Administración de la aplicación: observa el entorno por cambios en la configuración y reinicia|notifica la aplicación para re-cargarla.
- Servicios de infraestructura (gestión de credenciales, configuración, DNS, adaptadores, seguridad, control de acceso)
- Monitorización (recursos, tráfico de red, ...)
- Adaptadores de protocolo
Análisis
Después del análisis preliminar surgieron tres casos de uso que nos parecieron interesantes desde el punto de vista de seguridad y que pasamos a describir a continuación.
Gestión de credenciales
Usamos un sidecar para obtener las credenciales necesarias para acceder a un servicio externo, dichas credenciales se almacenan en un fichero para, posteriormente, ser consumidas por el contenedor
principal. De esta manera la aplicación principal dispone de un interfaz muy simple (leer un fichero) que es agnóstico de la manera en la que se gestionan las credenciales.
Hay dos variantes, una en la que las credenciales son recuperadas una vez y el sidecar muere después de almacenarlas, y otra en la que el sidecar permanece activo buscando actualizaciones de las credenciales. En ambos casos los contenedores comparten un volumen, que reside en el docker host en el que ambos se ejecutan, y donde las credenciales quedan almacenadas.
Desde el punto de vista de seguridad, la amenaza en este escenario es que una persona no autorizada pueda tener acceso a las credenciales; los mecanismos de ataque que tenemos son:
- Administradores del docker host podrían acceder sin autorización a las credenciales.
- Personas autorizadas a desplegar servicios podrían acceder sin autorización a las credenciales.
En el análisis hemos probado dos productos diferentes para el despliegue de la aplicación principal y del sidecar para poder analizar ambos mecanismos de ataque:
- docker-compose (y por extensión clústeres Swarm que tienen un comportamiento similar): Se levanta un proyecto (stack en Swarm) con los dos contenedores.
- Deployment Kubernetes: Que contiene un pod con los dos contenedores.
Hemos usado dos contenedores para el test. Un contenedor con Nginx para simular la aplicación principal y un contenedor con Alpine como el sidecar que escribirá el secreto en el fichero.
main.dockerfile
sidecar.dockerfile
En el caso de docker-compose hemos usado dos versiones distintas del formato de fichero que nos proporcionan dos maneras distintas de generar el volumen compartido, con sus ventajas e inconvenientes (que detallaremos en la conclusión):
Compose files
Finalmente tenemos otro proyecto para simular el contenedor malicioso que intenta acceder al secreto desplegado en el volumen compartido:
compose-malicioso.yml
Para el caso de Kubernetes hemos usado un deployment simple con los dos contenedores y un volumen compartido:
Kubernetes deployment
Conclusiones
Con ambas soluciones comprobamos que es posible acceder a las credenciales a través del sistema de archivos del docker host si el usuario tiene los permisos necesarios (como seguramente es el caso para un administrador de la plataforma), tan sólo es cuestión de buscar un poco (mediante docker inspect) para encontrar las rutas donde están los ficheros y acceder a su contenido. Además, en el caso de Compose, tenemos un problema adicional, ya que por defecto, la eliminación del proyecto no borra los volúmenes que permanecen en el sistema de archivos hasta que se ejecute un comando prune que los elimine. Hay que usar un flag adicional (-v) al eliminar el proyecto para que desaparezcan.
docker inspect Compose
docker inspect Kubernetes
En el segundo escenario, podemos comprobar que Kubernetes proporciona mejor aislamiento, incluso dentro de un namespace no es posible acceder a elementos entre pods mediante nombrado en el descriptor. Con docker-compose, cuando usamos named volumes, se puede declarar un volumen externo en un proyecto malicioso y usar el nombre generado por Compose (nombreProyecto_nombreVolumen) para poder acceder a él. Hasta la versión 3.0, era posible usar volúmenes anónimos (importados vía volume-from) que impedían este comportamiento al no tener nombre y no ser referenciables desde fuera del proyecto local.
Como hemos visto, el uso del patrón sidecar en este caso provoca que las credenciales puedan ser comprometidas, bien sea mediante el acceso al sistema de archivos del docker host o desplegando un contenedor malicioso (con Compose), lo cual no hace recomendable su uso.
Alternativas
Como alternativas al uso del patrón sidecar con almacenamiento compartido, se podrían plantear las siguientes opciones:
- No usar sidecar. Ejecutar el contenedor con un script que recupere las credenciales y se las proporcione a la aplicación como variables de entorno. De esta forma seguimos manteniendo una interfaz simple de cara a la aplicación principal eliminando el riesgo de revelación de información. Esta alternativa sólo es válida si las credenciales no cambian en el tiempo.
startup script
- Usar un sidecar exponiendo un servicio que devuelve las credenciales, con lo que la aplicación accede a una URL en vez de a un fichero. Al igual que en el caso anterior eliminamos el riesgo de compromiso, al no haber almacenamiento de datos, pero en cambio nos obliga a mantener el sidecar en ejecución aunque no sea necesario actualizar las credenciales.
- Usar mecanismos del orquestador que permiten definir secretos que luego son publicados al contenedor vía variables de entorno o ficheros tmpfs.
- No usar credenciales en la aplicación … ;-), la credencial más segura es la que no existe. Veremos como más adelante...
Proxy TLS (patrón adapter)
El patrón adapter nos permite presentar a los consumidores externos una vista distinta de nuestra aplicación. En este caso, proporcionamos a los consumidores un interfaz HTTPS sin necesidad de modificar el contenedor principal (ni siquiera la configuración), que sigue exponiendo su funcionalidad vía HTTP. Este patrón de despliegue aporta muchas ventajas, ya que el par de claves y el certificado no deben ser gestionadas por el equipo de desarrollo o las herramientas de CI/CD, sino que se obtienen en tiempo de despliegue. Esto simplifica mucho el proceso y permite más control a los equipos de seguridad al poder definir todos los parámetros TLS, gestionar las renovaciones y revocaciones y controlar totalmente la distribución.
En este caso el sidecar se encarga de la recuperación de la configuración necesaria (claves, certificados, configuración TLS, configuración proxy, ...) al arrancar y actúa como proxy frente a peticiones externas que luego redirige al contenedor principal una vez se ha establecido el túnel TLS.
Para el estudio se han probado dos opciones distintas:
Ambos productos implementan un service mesh y permiten inyectar un sidecar en el deployment que proporciona funciones para gestión de red, seguridad, monitorización, login, ... Istio tiene la gestión de TLS completamente integrada, en Linkerd está en fase experimental.
Como parte de la solución se despliega una entidad certificadora que proveerá los certificados y pares de claves. Cada vez que un componente es desplegado el sidecar contacta con el control plane y obtiene toda la configuración necesaria para el proxy. Como los contenedores comparten la pila de red, el proxy gestiona las conexiones entrantes, una vez establecido el túnel TLS redirige el tráfico del contenedor principal vía el interfaz de loopback.
Con esta solución los desarrolladores despliegan las aplicaciones exponiendo el puerto HTTP. Queda en manos de los equipos de seguridad la construcción y el despliegue de la imagen del sidecar y la configuración de los parámetros necesarios, ésta se realiza una sola vez, por lo que sus tareas se simplifican al no tener que revisar/configurar múltiples despliegues sino sólo la imagen. En caso de encontrar una vulnerabilidad sólo debe parchearse una imagen, y la sustitución es casi instantánea, sólo debe reiniciarse el sidecar con la nueva versión.
AuthN y AuthZ (patrón ambassador)
El patrón ambassador nos permite dar a nuestra aplicación una vista distinta|simplificada de los servicios externos que consume. De nuevo, mediante el uso de un sidecar, proporcionamos a la aplicación la capacidad de conectar con sus dependencias externas sin aportar explícitamente las credenciales, la comunicación es interceptada por el proxy en el sidecar, que es el que se encarga de establecer la comunicación y autenticarse recuperando las credenciales y, una vez establecido el canal, el flujo de datos entre aplicación principal y servicio externo se inicia.
En este caso esta opción sólo está disponible en Istio (experimental en Linkerd 2.2), y sólo se soporta mTLS. El control plane del service mesh nos permite configurar la relación de servicios y sus dependencias. Para que funcione se asigna a cada servicio una identidad que se usará para gestionar la AuthN y AuthZ.
Una vez que la autenticación se ha producido una autorización basada en RBAC puede aplicarse mediante la definición de reglas que pueden establecerse a nivel de namespace, servicio o método.
El servicio de CA del service mesh proporciona las claves y el certificado de la identidad correspondiente al sidecar que los usa en el proceso de conexión con el otro servicio. De esta manera se pueden establecer reglas de acceso para aumentar la seguridad dentro del cluster donde se ejecutan los servicios permitiendo sólo las conexiones autorizadas.
Actualmente casi todas los servicios (incluidas muchas BB.DD.) soportan mecanismos de AuthN y AuthZ basados en certificados digitales, haciendo que esta opción sea muy interesante al simplificar la gestión de las credenciales, ya que ahora no es necesario gestionar secretos sino sólo identidades y por tanto la configuración de backends y servicios puede ser realizada por administradores sin temor a la revelación de secretos.
Istio también soporta la autenticación de peticiones entrantes mediante la validación de tokens JWT contenidos en las peticiones.