Continuando con lo que se ha explicado en el primer post de Buenas prácticas en Docker – Parte 1 de 2 se listarán ahora algunas buenas prácticas adicionales que representan los conocimientos mínimos que se deben de tener para utilizar esta tecnología de forma efectiva.

Evitar la ejecución de contenedores con root.

La mejor forma de evitar ataques de elevación de privilegios desde el interior de un contenedor es configurar las aplicaciones que se ejecuten en él con privilegios de usuarios regulares (sin privilegios administrativos de ningún tipo). El cliente de docker cuenta con la opción “-u” o “–user” para especificar un nombre de usuario o ID, esto es importante tenerlo en cuenta ya que por defecto todos los contenedores se ejecutan con privilegios de root y aunque el contenedor está debidamente aislado, un atacante tendrá pleno control sobre dicho contenedor en el caso de que consiga comprometerlo.

Utilizar la instrucción USER en los ficheros DockerFile.

En el diseño de imágenes la instrucción USER en el fichero DockerFile permite especificar un usuario distinto a “root” en un momento concreto de la ejecución. De ésta forma, es posible ejecutar instrucciones como “root” en el DockerFile cuando sea necesario  y posteriormente cambiar de usuario a uno sin privilegios con dicha instrucción. Por ejemplo:


En el caso anterior, es necesario contar con privilegios de root para instalar paquetes y otras cuestiones de configuración, pero una vez dicha operación termina, se cambia el contexto de usuario para ejecutar los contenedores que se creen partiendo de la imagen anterior con un usuario concreto (definido por su identificador).

Especificar únicamente las capabilities necesarias y evitar  la flag “privileged”.

Por defecto, todos los contenedores en Docker son “no privilegiados”. Esto quiere decir que por ejemplo, dentro de un contenedor no es posible ejecutar otro servicio Dockerd o manipular características importantes del sistema host. Sin embargo, un contenedor privilegiado, es aquel que tiene acceso a todos los dispositivos y puede manipular características importantes del sistema anfitrión como la configuración de SELinux o AppArmor, lo que le permitirá al contenedor tener prácticamente el mismo nivel de acceso sobre el sistema host.
Para crear un contenedor privilegiado se utiliza la flag “–privileged” y por defecto permite el acceso a todos los dispositivos del host anfitrión. Para limitar dicho nivel de acceso se puede combinar con la flag “–device”

docker run –privileged –device=/dev/snd:/dev/snd
docker run –device=/dev/sda:/dev/xvdc –rm -it ubuntu fdisk /dev/xvdc
docker run –device=/dev/sda:/dev/xvdc:r –rm -it ubuntu fdisk /dev/xvdc
docker run –device=/dev/sda:/dev/xvdc:w –rm -it ubuntu fdisk /dev/xvdc

Además de la opción “–privileged”, en Docker también existe la posibilidad de controlar las “capabilities” de Linux por medio de las opciones “–cap-add” y “–cap-drop”, lo cual permite tener un control muy fino sobre lo que se puede y no se puede hacer dentro del contenedor.  La mejor política de seguridad consiste precisamente en eliminar todas las capabilities y añadir solamente aquellas que sean necesarias:

docker run –cap-drop=ALL –cap-add=NET_ADMIN –cap-add=CAP_NET_BIND_SERVICE

El listado completo de capabilities soportadas se encuentra disponible en la documentación oficial: http://man7.org/linux/man-pages/man7/capabilities.7.html

Limitar el acceso a recursos desde el contenedor (memoria y CPUs).

Por defecto, no hay restricciones de forma implícita en los recursos que puede consumir un contenedor y aunque gracias a cgroups es posible evitar condiciones de denegación de servicio, es altamente recomendable utilizar las opciones disponibles en Docker para poner limites de forma explicita a los contenedores creados. Las principales opciones se listan a continuación:

Gestión de la memoria.

-m or –memory=

Memoria máxima que el contenedor podrá usar. El valor mínimo para esta opción es 4m (4 megabytes).

–memory-swap*

La cantidad de memoria que el contenedor puede “swapear” al disco.

–memory-reservation

Permite especificar un límite blando (soft limit) más pequeño que el valor indicado con la opción “-m”. Este límite se activa cuando Docker detecta contención o poca memoria en el sistema host.

–kernel-memory

Indica la cantidad máxima de memoria del kernel  que podrá usar el contenedor. El valor minimo permitido es de 4M.

Gestión de la CPU.

–cpus=<value>

Especifica la cantidad de recursos disponibles en la CPU puede usar el contenedor. Por ejemplo, si el host tiene 2 CPUs y se establece la opción con –cpus=”1.5″, el contenedor tendrá garantizado el uso de al menos uno y medio de CPUs.

–cpuset-cpus

Especifica los cores concretos que el contenedor puede usar. Cada CPU está numerada desde 0. Es posible indicar que el contenedor puede usar los cores de 0 a 4 con el valor “0-3” o que el contenedor puede usar los cores 2 y 4 especificando los valores “1,3”.

En este post has podido ver algunas de las buenas prácticas a la hora de crear y gestionar contenedores en Docker, sin embargo pueden haber muchas más. Puedes dejar un comentario en este post con las que sueles utilizar para que todos aprendamos un poco más.

Un saludo y Happy Hack!
Adastra.