Continuamos con la segunda parte de este monográfico de seguridad que tuvo su inicio con el post de Seguridad para contenedores Docker y que dividimos en estos apartados:
- Seguidad dentro del host y construcción de contenedores.
- Servicio de Docker
- Limitación de recursos hardware
- Restricción de CPU
- Restricción de RAM
- Dispositivo entrada-salida y sistemas de ficheros.
- Redes
Ya explicamos en ese post que la seguridad suele ser una cosa secundaria en nuestro orden de prioridades a la hora de construir un servicio. Pero hay que tener en cuenta lo sencillo que es poder escalar privilegios o tumbar un servicio si no se ponen los medios adecuados. Y aún así no estamos libres de que algún gurú o hacker nos haga la pascua por diversión o dinero.
Básicamente, las reglas son confugurar Docker de menera restrictiva y limitar a lo justo la capacidad de los contenedores. Con estos factores que continuaremos en esta entrada completaremos estas nociones sobre seguridad para los contenedores Docker.
Recursos del sistema y ulimit
ulimit
nos permite limitar el número de procesos máximos que puede abrir un usuario. Esto es útil porque podemos hacer que nuestros contenedores no crezcan en cuanto a ese parámetro. Aunque algunos servicios, por ejemplo Apache disponen en su configuración de parámetros que limitan esto no está de más poner un limite adicional.
Con ulimit
podemos actuar sobre el propio demonio de Docker o sobre un contenedor individualmente.
Al arrancar el demonio podemos pasarle un parámetro:
>_ docker daemon --default-ulimit nofile=50:100
Esto limitará a un máximo de 100 cada contenedor que arranquemos. Esto nos puede proporcionar un limite que se puede ampliar escalando contenedores y creciendo de 100 en 100.
Al arrancar un contenedor también lo podemos tener en cuenta:
>_ docker run --ulimit nofile=50:100 apache
A partir de la versión 1.6 de Docker se introduce otro parámetro llamado nproc
que es muy similar a ulimit
aunque este último es obsoleto según POSIX 2008. (Diferencias entre nlimit y nproc).
Si cuentas con una versión igual o superior a 1.11 y un kernel 4.3 se puede limitar estos parámetros a nivel de cgroups con lo que podemos aislar de una manera más clara lo que usa cada contenedor.
>_ docker run --pids-limit 50 ...
Por último, existe una configuración muy sencilla y que hay que tener en mente. Se trata del restart always
. Esto puede hacer que un contenedor esté constantemente arrancado y no lo consiga. Lo mejor es tratar cada tipo por separado y no ponerle a todos ese valor.
Namespaces
Para el caso de los contenedores, los namespaces proporcionan a Docker un usuario root de mentira. Namespace se introdujo en la versión 3.5 del kernel pero no ha sido hasta la 4.3 que se ha considerado totalmente estable.
A partir de la versión 1.10 de Docker podemos arrancar el demonio como otro usuario.
>_ docker daemon --userns-remap=mentira
Esto hará que si el usuario mentira en el sistema tiene un id 1000 dentro del contenedor tendrá el id 1. Todos los ids se mapearán con esta formula:
FIRST_SUB_UID = 100000 + (UID - 1000) * 65536
Hay más opciones que se pueden utilizar Audit, cgroups, IPC, mount, NET, PID, Syslog, UID, UTS pero también hay muchas cosas que todavía no están implementadas opciones como Kernel, LSM, UID (por defecto), keyring, /proc/{sys}, /sys, /dev/{shm}.
Esto es solo una pequeña muestra de lo que puedes hacer con los namespaces. No hay mucha documentación sobre todo por la complejidad pero en esta web se matizan las opciones.
Capacidades o capabilities
Por defecto, Docker ejecuta los contenedores sin privilegios, algo que es totalmente recomendable.
¿Qué pasa si necesito tener acceso a algún recurso que requiera de privilegios?
Pues de momento no hagas esto
>_ docker run --privileged=true ....
Lo mejor es especificar concretamente el dispositivo al que queremos dar acceso:
>_ # Acceso a la tarjeta de sonido
>_ docker run --device=/dev/snd:/dev/snd ...
O acceso a disco duro:
>_ docker run --device=/dev/sda:/dev/xvdc --rm -it ubuntu fdisk /dev/xvdc
Command (m for help): q
>_ docker run --device=/dev/sda:/dev/xvdc:r --rm -it ubuntu fdisk /dev/xvdc
You will not be able to write the partition table.
Command (m for help): q
>_ docker run --device=/dev/sda:/dev/xvdc:w --rm -it ubuntu fdisk /dev/xvdc
crash....
>_ docker run --device=/dev/sda:/dev/xvdc:m --rm -it ubuntu fdisk /dev/xvdc
fdisk: unable to open /dev/xvdc: Operation not permitted
Existe toda una serie de parámetros para los que se puede habilitar --cap-add
o deshabilitar el permiso --cap-drop
Capability Key | Capability Description |
---|---|
SETPCAP | Modify process capabilities. |
MKNOD | Create special files using mknod(2). |
AUDIT_WRITE | Write records to kernel auditing log. |
CHOWN | Make arbitrary changes to file UIDs and GIDs (see chown(2)). |
NET_RAW | Use RAW and PACKET sockets. |
DAC_OVERRIDE | Bypass file read, write, and execute permission checks. |
FOWNER | Bypass permission checks on operations that normally require the file system UID of the process to match the UID of the file. |
FSETID | Don’t clear set-user-ID and set-group-ID permission bits when a file is modified. |
KILL | Bypass permission checks for sending signals. |
SETGID | Make arbitrary manipulations of process GIDs and supplementary GID list. |
SETUID | Make arbitrary manipulations of process UIDs. |
NET_BIND_SERVICE | Bind a socket to internet domain privileged ports (port numbers less than 1024). |
SYS_CHROOT | Use chroot(2), change root directory. |
SETFCAP | Set file capabilities. |
Computación Segura o Seccomp
La computación segura o Seccomp es un elemento de kernel dentro de las nuevas versiones. Para comprobar si esto está activo ejecuta:
>_ cat /boot/config-`uname -r` | grep CONFIG_SECCOMP=
CONFIG_SECCOMP=y
Se puede utilizar para restringir las acciones disponibles dentro del contenedor. La llamada al sistema Seccomp () actúa en el estado Seccomp del proceso de llamada y restringir el acceso de la aplicación.
Este parámetro tiene tantas opciones que podemos definirlas en un fichero en formato json y pasarlo en la ejecución de un contenedor. Por ejemplo:
{
"defaultAction": "SCMP_ACT_ERRNO",
"archMap": [
{
"architecture": "SCMP_ARCH_X86_64",
"subArchitectures": [
"SCMP_ARCH_X86",
"SCMP_ARCH_X32"
]
},
...
],
....
}
Ahora podemos incluirlo a la hora de arrancar el contenedor.
>_ docker run --rm -it --security-opt seccomp=seccomp_profile.json hello-world
La verdad es que las opciones y la granulización es abrumadora, además de ser demasiado tedioso el explicar todas las opciones disponibles. Lo mejor es echarla un vistazo a esta página y usar aquellos que se adecuan más a nuestro caso.
Seguridad en tiempo de ejecución.
Como ya hemos hablado en este post y en el anterior no es recomendable que ejecutes los contenedores con el parámetro --privileged=true
. Es mejor dar permiso a permiso con cualquier sistema que hemos visto anteriormente.
Dentro del contenedor no uses al usuario root. Es recomendable crear un usuario nobody
El bit SUID activo en un archivo significa que el que lo ejecute va a tener los mismos permisos que el que creó el archivo. Esto puede llegar a ser muy util en algunas situaciones pero hay que utilizarlo con cuidado, dado que puede generar grandes problemas de seguridad. Por eso no se debe usar en ninguna situación.
Y por supuesto, no hay que olvidarse de las configuraciones que las propias aplicaciones puedan tener. Por ejemplo si usamos bases de datos limitar el acceso con el parámetro bind-address
o a la hora de abrir puertos entre contenedores.
Y hasta aquí las recomendaciones para ejecutar tus contenedores en producción. Obviamente son muchísimas opciones y no es para usar todas, pero algunas de ellas pueden librarnos de muchos dolores de cabeza. También hay que pensar desde el principio que estos sistemas necesitan tener en cuenta la seguridad. Si queréis más información sobre este tema hay una web sobre la que he sacado mucha información Delve-labs. Overview of the options available for securing Docker in production environments
Comentarios recientes