Seguridad en contenedores Docker en producción

Seguridad para contenedores Docker en producción

Este es uno de los post que teniamos que haber hecho antes pero es de los que da pereza ponerte. Supongo que como todas las cuestiones de seguridad, son complejas y siempre pensamos que eso no va con nosotros.

A ver, si no soy la CIA o Hacienda para que preocuparse de eso.

Puede que tu sitio no sea tan importante pero cualquier hacker, cracker o simplemente alguien que haya seguido un tutorial puede dejarte tu servicio ”patasarriba” o lo que es peor tirar tu reputación por los suelos.

En el caso de la seguridad no hay un sistema infalible sino que tendremos que seguir una serie de recomendaciones para conseguir mitigar o dificultar los ataques. Si alguien nos va a atacar se lo va a tener que currar.

En cuanto a los tipos de ataque que vamos a intentar evitar están:

  • Perdida de disponibilidad, ataque Denial of service.
  • Perdida de confidencialidad, ataque Software & crypto exploit.
  • Escalado de privilegios dentro del host, ataque Container scape.
  • Host comprometido, ataque Root/Kernel exploit.

Seguidad dentro del host y construcción de contenedores.

Lo primero es empezar por el host donde vamos a correr nuestros contenedores. Una de las primeras recomendaciones es aplicar los parcher de Grsecurity. Grsecurity incluye protecciones activas sobre el kernel haciendolo más seguro. Aunque tiene un soporte comercial podemos descargarnos un paquete llamado PaX e instalarlo.

Otro de los sistemas de seguridad que podemos incluir en nuestro host es un “firewall” como SELinux o AppArmor (esto no es realmente un firewall) con los que podremos decidir políticas de seguridad a Docker.

Imagen del kernel y los módulos

También podemos configurar el sistema propiamente para hacer algunas limitaciones como el número de ficheros que se pueden manejar por usuario.

>_ vim /etc/security/limits.conf
docker soft nofile 4096
docker hard nofile 10240

El kernel de Linux tiene cientos de opciones, como no soy un experto tampoco os propongo muchas cosas en este aspecto pero básicamente de lo que se trata es de no darle privilegios a Docker más que lo que necesita.

Por último, aunque parece muy obvia pero suele dar pereza es tener los sistemas actualizados tanto el sistema operativo del host como las versiones de Docker. Así evitaremos algún susto.

Servicio de Docker

En este punto tenemos que tener en consideración como va a funcionar el servicio. Lo primero que tendremos que hacer es aplicar las restricciones propias del servicio Docker.

Por ejemplo, vamos a definir unas políticas para AppArmor para que no pueda acceder a algunas carpetas de sistema o que no pueda montar sistemas de ficheros.

#include <tunables/global>

profile docker-default flags=(attach**disconnected,mediate**deleted) {

  #include <abstractions/base>

  network,
  capability,
  file,
  umount,

  deny @{PROC}/{*,**^[0-9*],sys/kernel/shm*} wkx,
  deny @{PROC}/sysrq-trigger rwklx,
  deny @{PROC}/mem rwklx,
  deny @{PROC}/kmem rwklx,
  deny @{PROC}/kcore rwklx,

  deny mount,

  deny /sys/[^f]*/** wklx,
  deny /sys/f[^s]*/** wklx,
  deny /sys/fs/[^c]*/** wklx,
  deny /sys/fs/c[^g]*/** wklx,
  deny /sys/fs/cg[^r]*/** wklx,
  deny /sys/firmware/efi/efivars/** rwklx,
  deny /sys/kernel/security/** rwklx,
}

Este fichero se crea cuando instalamos Docker en la ruta /etc/apparmor.d/docker. Si queremos arrancar un contenedor con estas limitaciones usamos:

>_ docker run --rm -it --security-opt apparmor=docker-default hello-world

Podemos crear nuestro propio fichero y cargarlo en apparmor así:

>_ /etc/init.d/apparmor stop
>** apparmor**parser -R /path/to/profile
>_ /etc/init.d/apparmor start

Tienes que tener mucho cuidado con los contenedores que acceden a docker.sock estos contenedores pueden permitir hacerse a un atacante con todo el sistema. También cuando tienes que tener mucho cuidado con aquellos usuarios que pertenecen al grupo docker porque pueden ascender privilegios de una manera tan fácil como esta:

>_ # Usuario alicia puede ejecutar contenedores.
>_ cp /bin/sh /home/alicia
>_ docker run –rm –v /home/alicia:/host busybox /bin/sh –c “chown root.root /host/sh && chmod a+s /host/sh”
>_ ./sh
[root@docker] $ whoami
root

Así de sencillo.

Docker seguridad en el servicio

Si usas docker-machine es muy importante que uses TLS para las conexiones para ello arranca el servicio con la opción DOCKERTLSVERIFY=1

Como medida más pasiva, ya que no va evitar ningún ataque, envía los logs fuera del servidor. La medida tiene su lógica por dos motivos:

  1. Si tienes un ataque y te imposibilita el acceso si puedes acceder a los logs puedes saber que está pasando
  2. Dado que el enfoque de Docker son los microservicios es posible que te juntes con muchos hosts. Si tienes los logs en un solo sitio es más fácil de llegar al problema.

Para ello puedes usar los drivers que tiene docker para los logs. Puedes usar awslogs, etwlogs, fluentd, gcplogs, gelf, journald, json-file, none, splunk o syslog.

>** docker run -d --log-driver awslogs -e MYSQL**ROOT_PASSWORD=78e2?2ñkl mysql:latest

Limitación de recursos hardware

Mediante cgroups podemos limitar el acceso al hardware y no permitir a los contenedores más uso de hardware del que nosotros requerimos. En ello podemos limitar tanto en CPU como en RAM o red.

Limitación de CPU

Para la limitación de CPU podemos limitar de la siguiente forma:

  • --cpu-shares en un limitación relativa a la cantidad de CPUs y sería un tanto %. Osea si tenemos 8 CPUs podemos limitar a 50 y usariamos 4.
  • --cpu-period especifica un tiempo limite de uso de CPU y normalmente va asociado al siguiente parámetro.
  • --cpu-quota indica la cuota de uso de un CPU.

Estos parámetros forman parte de la documentación de la limitación del CFS

Ejemplo:

>_ docker run -it --cpu-period=50000 --cpu-quota=25000 ubuntu:14.04 /bin/bash

Si tenemos una CPU usará el 50% de la CPU cada 50ms

Limitación de RAM

En el caso de la RAM podemos hacer algo similar e indicarle al contenedor solo la RAM que necesita.

  • --memory=1g limitaríamos el uso total a 1GB.
  • --memory-reservation es un limite menos estricto y sería un mínimo de RAM. Esto nos sirve más para garantizar una memoria pero es necesario usar el otro límite.
  • --memory-swap indica el uso que podemos usar en un contenedor de memoria SWAP.
  • --memory-swappines especifica el porcentaje de memoria swap que puede usar sobre el total disponible. Como siempre el uso de swap no es nada recomendable por penalización del rendimiento.
  • --kernel-memory es un limite a la memoria del kernel que puede usar el contenedor. Este tipo de límite no lo he usado mucho, pero conviene echarle un vistazo. Ayuda saber cuanta memoria tienes o más bien cuanto tienes en uso grep Slab /proc/meminfo. Es conveniente poner una media del 10% de la memoria que uses para el contenedor.

Dispositivo entrada-salida y sistemas de ficheros.

Una de las principal acciones que hacer a la hora de montar un host optimizado y seguro para ejecutar contenedores es crear una partición solo para la ruta /var/lib/docker/.

También es recomendable que esa partición sea ZFS o BTRFS esto nos facilitará la posibilidad de hacer copias de seguridad y snapshots.

En cuanto al acceso a los recursos conviene limitar en todo momento. Por ejemplo tenemos que limitar que el acceso al fichero null /dev/zero sea de solo lectura para evitar que un atacante modifique su contenido.

>_ docker run --device=/dev/zero:/dev/zero:r ...

Para el caso de montar otros volumenes del disco duro, lo mejor es tener configurado SeLinux o AppArmor y limitar su uso de esta manera:

>_ docker run -v /home/user:/home/user:roz

Cuando necesitamos usar memoria temporal tmpfs lo más recomendable es identificar el propósito y limitar la ejecución de esta manera:

>_ docker run -tmpfs /run:rw,noexec,nodev,nosuid,size=8m ...

Además de limitar los permisos de acceso, es bueno limitar el uso intensivo para que no nos copen el acceso ante un ataque. Por ejemplo podemos limitar las operaciones de entrada-salida a disco por segundo. Esto nos puede sacar de un apuro si algo empieza a escribir en disco de manera masiva, sea un atacante o un error de programación. Para ello tenemos las siguientes opciones:

  • --device-read-bps limita en bytes el tamaño de lecturas a un dispositivo
  • --device-write-bps limita en bytes el tamaño de escritura a un dispositivo
  • --device-read-iops limita la veces que se lee de un dispositivo por segundo
  • --device-write-iops limita la veces que se escribe de un dispositivo por segundo

Vamos a limitar el acceso a disco con 1000 operaciones por segundo, se puede ir jugando con este número según necesidades pero lo importante es que el sistema lo soporte.

>_ docker run --device-write-iops /dev/sda:1000 ...

Redes

Docker, por defecto, nos crea una interfaz de red llamada docker0. Usando la opción --network podemos elegir entre estas opciones:

  • none sin red.
  • bridge crea una red propia para el stack usando la interfaz docker0 y proporcionando el aislamiento necesario.
  • host utiliza directamente la red del anfitrión. Algo poco recomendable y que puede llevar a problemas.
  • container:<nombre|id> reusa la red de otro contenedor.
  • <nombre-red|id-red> se conecta a una red creada por el usuario.

Esta última opción es la recomendable dado que tendremos acceso a todas las limitaciones de la red también podremos aislar los contenedores como sea necesario. En este post Redes privadas con Docker explicamos como crear redes.

Imagen de una arquitectura multitier

Otra de las recomendaciones de seguridad es que liberes a docker de la comunicación entre contenedores y hagas ese trabajo a través de iptables. Personalmente lo veo demasiado tedioso, sobre todo porque soy muy básico con iptables y es desligar a Docker de algo que ha evolucionado a mejor. Pero también es cierto que iptables es especialista en el manejo de estas conexiones.

El primer paso es configurar el ipforwarding a 1

>_ sysctl net.ipv4.conf.all.forwarding=1

Luego tenemos que arrancar los contenedores con --icc=false --ip-forwarding=1

También es recomendable poner el parámetro --userland-proxy=false para ahorrar memoria y que sea todo más rápido.

Estos parámetros han ido desapareciendo de las versiones modernas de docker, hasta llegar a la versión 1.12 que no las tiene y resuelve esto problemas con el sistema de creación de redes.

Dado que Docker requiere de más consideraciones de seguridad y que ya hemos descrito unas cuantas vamos a realizar otro post con una segunda parte en la que detallaremos más opciones de seguridad.

Ya son demasiadas para un solo día. Hasta la próxima.

Leave a comment

Tu dirección de correo electrónico no será publicada. Los campos necesarios están marcados *