Skip to main content

2.3 Conceptos Avanzados

2.3.1 USBs y otros dispositivos

Docker nos ofrece con el argumento --device la posibilidad de trabajar con comunicación serial de una forma efectiva. Para ello deberemos conectar el dispositivo (memorias, arduinos, sistemas embebidos, smartphones...) a la máquina anfitrión y después lanzar nuestro contenedor de Docker.

En este ejemplo conectamos un dispositivo en la máquina anfitrión al puerto /dev/ttyUSB0 y permitimos el acceso desde el contenedor de Docker.

docker run -t -i --device=/dev/ttyUSB0 ubuntu bash

Evidentemente la máquina anfitrión estará expuesta a ese dispositivo y por ello debemos seguir las mismas políticas habituales al trabajar con dispositivos USB y entender que Docker no nos ofrece una protección adicional en este aspecto a nivel de seguridad y que seguimos exponiendo nuestra máquina anfitrión al dispositivo que hayamos conectado.

2.3.2 Permisos para los ficheros de configuración del demonio de Docker

Es una buena estrategía reducir los permisos de los ficheros y carpetas que hace uso el demonio de Docker. En la siguiente tabla podemos ver la configuración inicial recomendada por Docker, pero sería interesante intentar en la medida de lo posible reducir aún más esos permisos.

RecursoUsuario:GrupoPermisos
/etc/default/dockerroot:root644
/etc/docker (directory)root:root755
/etc/sysconfig/dockerroot:root644
daemon.jsonroot:root644
Docker server certificateroot:root444
Docker server certificate keyroot:root400
Docker socketroot:docker660
docker.serviceroot:root644
docker.socketroot:root644
Registry certificateroot:root444
TLS CA certificateroot:root444

2.3.3 Verificar las imágenes

Por defecto el demonio de Docker nos permite descargarnos cualquier imagen que esté disponible dentro de un registro válido. Esta funcionalidad puede convertirse rápidamente en un problema si no podemos confiar en la autoría y la integridad del contenido por el canal de comunicación que usamos.

Para mitigar este tipo de escenarios podemos hacer uso de la validación de imágenes que incluye el propio demonio de Docker de la siguiente forma:

export DOCKER_CONTENT_TRUST=1

Desde este momento el demonio de Docker solo permitirá descargar aquellas imágenes que estén firmadas por sus autores a través del mecanismo de Notary que ofrece Docker.

Atención

En ocasiones tendremos otros entornos auto hosteados o similar y necesitamos definirlo de forma explícita de la siguiente forma:

export DOCKER_CONTENT_TRUST_SERVER=https://<URL>:<PORT>

2.3.4 Blindando el Daemon Socket

Es común que nos encontremos escenarios donde necesitemos exponer el Socket del demonio para que una aplicación pueda gestionar nuestros contenedores. Por ejemplo cuando hacemos uso de Portainer (capítulo 4.2), donde se nos pide que hagamos un bindeo entre el socket del host y el del contenedor de Portainer -v /var/run/docker.sock:/var/run/docker.sock.

Este paso hace que nuestro contenedor tome el control de los contenedores que está gestionando la máquina anfitrión, si confiamos en el software en el que estamos delegando esto no debería ser un problema, pero cuando esta relación ocurre fuera de la máquina anfitrión y por ejemplo queremos controlar el Socket de una máquina remota debemos ser proactivos y garantizar la comunicación a traves de SSH o TLS (HTTPS)

2.3.5 Privilegios y capacidades

Hace algunos años se decidió granularizar el poder de Root y dividirlo en una serie de capacidades que pueden ser activadas o desactivadas de forma independiente, esto se conoce como capabilities.

Docker nos ofrece una interfaz con tres argumentos para poder gestionarlo --privileged, --cap-drop, --cap-add.

En general el uso de --privileged es altamente desaconsejado porque concede todas las capacidades sin restricciones, son muy pocos los casos donde esto tenga una justificación real en un entorno productivo.

Carlos Polop de HackTricks presenta uno de los ejemplos más ilustrativos de lo fácil que resulta escalar privilegios rápidamente cuando no entendemos bien el alcance de --privileged

docker run --rm -it --privileged ubuntu bash
$ fdisk -l
#....
$ mkdir -p /mnt/hola
$ mount /dev/sda1 /mnt/hola

En realidad estamos permitiendo montar el volumen /dev/sda1 del host como /mnt/hola en el contendor porque implícitamente hemos permitido esas capacidades al usar --privileged.

Una forma más moderna de enfocar las capacidades desde el punto de vista de la seguridad sería retirar todos los permisos de forma completa e ir gradualmente aperturando aquellos que sean estrictamente necesarios.

Un ejemplo sería este, donde quitamos todas las capacidades para finalmente permitir kill haciendo que un proceso que no es dueño de otro puedo terminar la ejecución:

docker run -d --cap-drop=all --cap-add=kill ubuntu /bin/bash

2.3.6 Políticas con AppArmor

AppArmor es un módulo de seguridad de Linux disponible desde la versión 2.6.36 del Kernel y que nos permite definir políticas que luego podemos agrupar en perfiles y vincularlos a la ejecución de programas.

Docker aplica por defecto una política moderada pensada de forma que no rompa la compatibilidad con la gran diversidad de imágenes disponibles. Esta política se conoce como docker-default.

Podemos hacer uso del argumento --security-opt para cargar otras políticas más restrictivas y acordes a los proyectos que tenemos entre manos.

info

En la propia documentación de Docker podemos encontrar un ejemplo bastante extenso sobre Nginx

2.3.7 Políticas Seccomp

Seccomp es una funcionalidad del Kernel de Linux que permite establecer políticas sobre las llamadas que un proceso puede realizar al sistema.

Docker aplica por defecto una política moderada que bloquea activamente 44 de las más de 300 directivas disponibles.

Podemos hacer uso del argumento --security-opt también en este caso para cargar políticas personalizadas.

info

En la propia documentación de Docker podemos encontrar un ejemplo bastante extenso sobre Nginx

docker run --rm -it --security-opt seccomp=/politica-personalizada.json ubuntu