Cunas de seguridad: Docker



Los contenedores Docker son la tecnología de contenedorización más popular. Inicialmente, se usaba principalmente para entornos de desarrollo y prueba, y con el tiempo cambió a producción. Los contenedores Docker comenzaron a reproducirse en el entorno de producción, como los hongos después de la lluvia, pero pocos que usan esta tecnología han pensado en cómo publicar de manera segura los contenedores Docker.

Basado en OWASP , hemos preparado una lista de reglas, cuya implementación protegerá significativamente su entorno, construido en contenedores Docker.

Regla 0

La máquina host y Docker deben contener todas las actualizaciones actuales.


Para protegerse contra vulnerabilidades conocidas que conducen a escapar del entorno del contenedor al sistema host, lo que generalmente resulta en una escalada de privilegios en el sistema host, es extremadamente importante instalar todos los parches para el sistema operativo host, el motor Docker y la máquina Docker.

Además, los contenedores (a diferencia de las máquinas virtuales) usan el kernel junto con el host, por lo que el exploit del kernel que se ejecuta dentro del contenedor se ejecuta directamente en el kernel del host. Por ejemplo, un exploit de escalada de privilegios del kernel (como Dirty COW) que se ejecuta dentro de un contenedor bien aislado dará como resultado el acceso a la raíz en el host.

Regla 1

No le dé acceso al socket del demonio Docker


El servicio Docker (daemon) usa el socket /var/run/docker.sock de UNIX para las conexiones API entrantes. El propietario de este recurso debe ser el usuario root. Y de ninguna otra manera. Cambiar los derechos de acceso a este socket es esencialmente equivalente a otorgar acceso de root al sistema host.

Además, no debe manipular el zócalo /var/run/docker.sock con contenedores, donde puede prescindir de él, porque en este caso, comprometer el servicio en el contenedor conducirá a un control completo sobre el sistema host. Si tiene contenedores que usan algo como esto:

-v /var/run/docker.sock://var/run/docker.sock 

o para docker-compose:

 volumes: - "/var/run/docker.sock:/var/run/docker.sock" 

urgente necesidad de cambiar esto.

Y lo último: nunca escuche, nunca use el socket Docker TCP sin la absoluta certeza de que lo necesita, especialmente sin el uso de métodos de protección adicionales (al menos autorización). De manera predeterminada, el socket Docker TCP abre un puerto en la interfaz externa 0.0.0.0:2375 (2376, en el caso de HTTP) y permite el control total de los contenedores, y con él el posible sistema host.

Regla 2

Configurar un usuario sin privilegios dentro del contenedor


Configurar un contenedor para usar un usuario sin privilegios es la mejor manera de evitar un ataque de escalada de privilegios. Esto se puede hacer de varias maneras:

1. Usando la opción "-u" del comando "docker run":

 docker run -u 4000 alpine 

2. Durante la construcción de la imagen:

 FROM alpine RUN groupadd -r myuser && useradd -r -g myuser myuser <      root-, ,  > USER myuser 

3. Habilite la compatibilidad con el "espacio de nombres de usuario" (entorno de usuario) en Docker daemon:

 --userns-remap=default 

Lea más sobre esto en la documentación oficial .

En Kubernetes, este último se configura en el contexto de seguridad a través de la opción runAsNonRoot:

 kind: ... apiVersion: ... metadata: name: ... spec: ... containers: - name: ... image: .... securityContext: ... runAsNonRoot: true ... 

Regla 3

Limitar las capacidades del contenedor


En Linux, comenzando con el kernel 2.2, hay una manera de controlar las capacidades de los procesos privilegiados llamados Capacidades del kernel de Linux (para más detalles, vea el enlace).

Docker usa un conjunto predefinido de estas características del núcleo de forma predeterminada. Y le permite cambiar este conjunto utilizando los comandos:

 --cap-drop —     --cap-add —     

La mejor configuración de seguridad es deshabilitar primero todas las funciones (--cap-drop all), y luego conectar solo las necesarias. Por ejemplo, así:

 docker run --cap-drop all --cap-add CHOWN alpine 

Y lo más importante (!): ¡Evite ejecutar contenedores con la bandera privilegiada!

En Kubernetes, la restricción de capacidades del kernel de Linux se configura en el contexto de seguridad a través de la opción de capacidades:

 kind: ... apiVersion: ... metadata: name: ... spec: ... containers: - name: ... image: .... securityContext: ... capabilities: drop: - all add: - CHOWN ... 

Regla 4

Utilice el indicador de no-nuevos-privilegios


Al iniciar un contenedor, es útil usar el indicador --security-opt = no-new-privileges que evita la escalada de privilegios dentro del contenedor.

En Kubernetes, la restricción de capacidades del kernel de Linux se configura en el contexto de seguridad mediante la opción allowPrivilegeEscalation:

 kind: ... apiVersion: ... metadata: name: ... spec: ... containers: - name: ... image: .... securityContext: ... allowPrivilegeEscalation: false ... 

Regla 5

Apague la comunicación entre contenedores


De manera predeterminada, la comunicación entre contenedores está habilitada en Docker, lo que significa que todos los contenedores pueden comunicarse entre sí (utilizando la red docker0). Esta característica se puede deshabilitar ejecutando el servicio Docker con el indicador –icc = false.

Regla 6

Utilice los módulos de seguridad de Linux (Módulo de seguridad de Linux - seccomp, AppArmor, SELinux)


Por defecto, Docker ya usa perfiles para módulos de seguridad de Linux. Por lo tanto, ¡ nunca deshabilite los perfiles de seguridad! Lo máximo que se puede hacer con ellos es ajustar las reglas.

El perfil predeterminado para seccomp está disponible aquí .

Docker también usa AppArmor para protección, y Docker Engine genera un perfil predeterminado para AppArmor cuando se inicia el contenedor. En otras palabras, en lugar de:

 $ docker run --rm -it hello-world 

se pone en marcha:

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

La documentación también proporciona un ejemplo de un perfil de AppArmor para nginx, que es bastante posible (¡necesario!) Usar:

 #include <tunables/global> profile docker-nginx flags=(attach_disconnected,mediate_deleted) { #include <abstractions/base> network inet tcp, network inet udp, network inet icmp, deny network raw, deny network packet, file, umount, deny /bin/** wl, deny /boot/** wl, deny /dev/** wl, deny /etc/** wl, deny /home/** wl, deny /lib/** wl, deny /lib64/** wl, deny /media/** wl, deny /mnt/** wl, deny /opt/** wl, deny /proc/** wl, deny /root/** wl, deny /sbin/** wl, deny /srv/** wl, deny /tmp/** wl, deny /sys/** wl, deny /usr/** wl, audit /** w, /var/run/nginx.pid w, /usr/sbin/nginx ix, deny /bin/dash mrwklx, deny /bin/sh mrwklx, deny /usr/bin/top mrwklx, capability chown, capability dac_override, capability setuid, capability setgid, capability net_bind_service, deny @{PROC}/* w, # deny write for all files directly in /proc (not in a subdir) # deny write to files not in /proc/<number>/** or /proc/sys/** deny @{PROC}/{[^1-9],[^1-9][^0-9],[^1-9s][^0-9y][^0-9s],[^1-9][^0-9][^0-9][^0-9]*}/** w, deny @{PROC}/sys/[^k]** w, # deny /proc/sys except /proc/sys/k* (effectively /proc/sys/kernel) deny @{PROC}/sys/kernel/{?,??,[^s][^h][^m]**} w, # deny everything except shm* in /proc/sys/kernel/ 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/** rwklx, deny /sys/kernel/security/** rwklx, } 

Regla 7

Limite los recursos del contenedor


Esta regla es bastante simple: para evitar que los contenedores devoren todos los recursos del servidor durante el próximo ataque DoS / DDoS, podemos establecer límites de uso de memoria para cada contenedor individualmente. Puede limitar: cantidad de memoria, CPU, número de reinicios del contenedor.

Así que vamos en orden.

El recuerdo

La opción -m o --memory

La cantidad máxima de memoria que puede usar un contenedor. El valor mínimo es de 4 m (4 megabytes).

Opción - intercambio de memoria

Opción para configurar el intercambio (archivo de intercambio). Configurado astutamente:

  • Si --memory-swap> 0, también se debe establecer el indicador –memory. En este caso, el intercambio de memoria muestra la cantidad de memoria total disponible para el contenedor junto con el intercambio.
  • Un ejemplo más simple. Si --memory = "300m", y --memory-swap = "1g", entonces el contenedor puede usar 300MB de memoria y 700MB de intercambio (1g - 300m).
  • Si --memory-swap = 0, la configuración se ignora.
  • Si --memory-swap se establece en el mismo valor que --memory, entonces el contenedor no tendrá swap.
  • Si --memory-swap no se especifica, pero --memory se especifica, entonces el número de intercambio será igual al doble de la cantidad de memoria especificada. Por ejemplo, si --memory = "300m", y --memory-swap no está configurado, entonces el contenedor usará 300MB de memoria y 600MB de intercambio.
  • Si --memory-swap = -1, el contenedor usará todo el intercambio que sea posible en el sistema host.

Nota para la anfitriona: la utilidad gratuita lanzada dentro del contenedor no muestra el valor real del intercambio disponible para el contenedor, sino el número de intercambio de host.

Opción --oom-kill-disable

Le permite habilitar o deshabilitar el asesino OOM (sin memoria).

Atencion Puede desactivar OOM Killer solo con la opción --memory especificada, de lo contrario puede suceder que con falta de memoria dentro del contenedor, el núcleo comenzará a matar los procesos del sistema host.

Otras opciones de configuración de administración de memoria, como --memory-swappiness, --memory-reservetion, y --kernel-memory, son más para ajustar el rendimiento del contenedor.

CPU

Opción --cpus

La opción establece cuántos recursos de procesador disponibles puede usar el contenedor. Por ejemplo, si tenemos un host con dos CPU y configuramos --cpus = "1.5", se garantiza que el contenedor utilizará procesadores de uno y medio.

Opción --cpuset-cpus

Configura el uso de núcleos o CPU específicos. El valor se puede especificar con un guión o una coma. En el primer caso, se indicará el rango de núcleos permitidos, en el segundo - núcleos específicos.

Número de reinicios del contenedor

 --restart=on-failure:<number_of_restarts> 

Esta configuración establece cuántas veces Docker intentará reiniciar el contenedor si se bloquea inesperadamente. El contador se reinicia si el estado del contenedor ha cambiado a en ejecución.

Se recomienda establecer un pequeño número positivo, por ejemplo, 5, que evitará reinicios interminables de un servicio que no funciona.

Regla 8

Utilice sistemas de archivos y volumen de solo lectura


Si el contenedor no debe escribir nada en alguna parte, debe usar el sistema de archivos de solo lectura tanto como sea posible. Esto complicará en gran medida la vida de un intruso potencial.

Un ejemplo de inicio de un contenedor con sistema de archivos de solo lectura:

 docker run --read-only alpine 

Un ejemplo de conexión de volumen en modo de solo lectura:

 docker run -v volume-name:/path/in/container:ro alpine 

Regla 9

Usar herramientas de análisis de seguridad de contenedores


Las herramientas deben usarse para detectar contenedores con vulnerabilidades conocidas. Todavía no hay muchos, pero son:

• Gratis:


• Comercial:


Y para Kubernetes, hay herramientas para detectar errores de configuración:

Source: https://habr.com/ru/post/448704/


All Articles