Security Context
Container vs Host vs Privilegios
Primero vamos a entender un poco de seguridad en containers, pues solo tiene sentido en Kubernetes si entendemos containers.
El host que hospeda los containers tiene sus propios procesos en ejecución que cargan el sistema operativo, el daemon de Docker si tiene Docker o algún otro container runtime.
Para hacer un análisis vamos a ejecutar un container cualquiera y ponerlo a dormir por 1 hora para que tenga algo sosteniendo el proceso y no muera.
docker run --name ubuntu ubuntu sleep 3600
El container comparte el mismo kernel del host, pero son aislados a través de namespaces en Linux. El host tiene su namespace y cada container tiene el suyo separado.
Por eso el container solamente puede ver sus procesos y nada fuera de él.
Dentro del container:
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
764da973f0fe ubuntu "sleep 3600" About a minute ago Up About a minute ubuntu
docker exec -it ubuntu ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 2792 1408 ? Ss 08:04 0:00 sleep 3600
root 7 0.0 0.0 7064 2944 pts/0 Rs+ 08:05 0:00 ps aux
El proceso que sostiene el container funcionando tiene el pid 1.
En el host.
git:(main) ✗ ps -aux | grep sleep
david-p+ 1628755 0.0 0.0 2066812 25536 pts/2 Sl+ 05:04 0:00 docker run --name ubuntu ubuntu sleep 3600
root 1628843 0.0 0.0 2792 1408 ? Ss 05:04 0:00 sleep 3600
david-p+ 1631734 0.0 0.0 9220 2560 pts/5 R+ 05:08 0:00 grep --color=auto sleep
En el host podemos ver el proceso del container que es el sleep 3600 pero con un pid diferente. Esto porque los procesos pueden tener diferentes PIDs en diferentes namespaces, es así como Docker funciona.
Si creamos otro container ¿qué podemos ver?
docker run --name centos centos sleep 3600
docker exec -it centos ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 23048 2560 ? Ss 08:14 0:00 /usr/bin/coreutils --coreutils-prog-shebang=sleep /usr/bin/sleep 3600
root 7 1.0 0.0 44668 3456 pts/0 Rs+ 08:16 0:00 ps aux
# y en el host
ps -aux | grep sleep
david-p+ 1628755 0.0 0.0 2066812 24948 pts/2 Sl+ 05:04 0:00 docker run --name ubuntu ubuntu sleep 3600
root 1628843 0.0 0.0 2792 1408 ? Ss 05:04 0:00 sleep 3600
david-p+ 1634805 0.0 0.0 1992824 26052 pts/5 Sl+ 05:14 0:00 docker run --name centos centos sleep 3600
root 1634985 0.0 0.0 23048 2560 ? Ss 05:14 0:00 /usr/bin/coreutils --coreutils-prog-shebang=sleep /usr/bin/sleep 3600
david-p+ 1639928 0.0 0.0 9220 2560 pts/4 S+ 05:17 0:00 grep --color=auto sleep
Podemos analizar aquí que dentro del container estamos ejecutando el proceso como root user.
Podemos forzar que sea otro user que ejecute el proceso pasando en el comando --user 1000. Generalmente el 1000 es el primer usuario creado fuera el root. En mi máquina personal 1001 es el id de mi usuario.
docker run --name ubuntu --user 1001 ubuntu sleep 3600
docker exec -it ubuntu ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
1001 1 0.0 0.0 2792 1536 ? Ss 08:23 0:00 sleep 3600
1001 7 0.0 0.0 7064 2944 pts/0 Rs+ 08:23 0:00 ps aux
O definir en el Dockerfile de la imagen que vamos a construir creando una imagen personalizada.
FROM ubuntu
USER 1001
Ahora en el host tenemos.
ps aux | grep sleep
david-p+ 1658884 0.0 0.0 1845040 25728 pts/9 Sl+ 05:38 0:00 docker run --name ubuntu --user 1001 ubuntu sleep 3600
david-p+ 1658972 0.0 0.0 2792 1408 ? Ss 05:38 0:00 sleep 3600
david-p+ 1660399 0.0 0.0 9220 2688 pts/8 S+ 05:40 0:00 grep --color=auto sleep
¿Y quién está ejecutando el proceso? david-p....
Lo que podemos percibir es que usamos el mismo usuario dentro del container y del host. El mismo root del container es el root del sistema y el mismo usuario 1001 del container es el 1001 del host que es el mío propio y apareció allí como el sleep siendo un proceso de mi usuario.
¿Pero no era para ser aislado? Si esto es verdad ¿no es peligroso?
¿El proceso dentro del container puede hacer cualquier cosa que el usuario root pueda hacer en el host? En realidad no. Docker utiliza un recurso de Linux llamado capabilities que limita los poderes del root dentro del container o del usuario pasado, pero es el mismo usuario con poderes limitados.
El usuario root puede hacer cualquier cosa en el sistema host como ya sabemos.
Para ver lo que un usuario puede hacer podemos analizar el archivo /usr/include/linux/capability.h
Si quisiéramos quitar esta limitación para que el usuario root del container pueda hacer más cosas podemos hacer.
# Este container ya es ejecutado como root y --privileged habilita todos los privilegios del root del container dentro del host.
docker run --name ubuntu --privileged ubuntu sleep 3600
# o solamente un permiso específico --cap-drop quitaría un permiso
docker run --name ubuntu --cap-add CHOWN ubuntu sleep 3600
Aquí lista.
| Clave de capacidad | Descripción de la capacidad |
|---|---|
| AUDIT_WRITE | Grabe registros en el log de auditoría del kernel. |
| CHOWN | Haga alteraciones arbitrarias en los UIDs y GIDs de los archivos (vea chown(2)). |
| DAC_OVERRIDE | Ignore verificaciones de permiso de lectura, escritura y ejecución de archivos. |
| FOWNER | Ignore verificaciones de permiso en operaciones que normalmente exigen que el UID del sistema de archivos del proceso corresponda al UID del archivo. |
| FSETID | No limpie los bits de permiso set-user-ID y set-group-ID cuando un archivo sea modificado. |
| KILL | Ignore verificaciones de permiso para envío de señales. |
| MKNOD | Cree archivos especiales usando mknod(2). |
| NET_BIND_SERVICE | Vincule un socket a puertos privilegiados de dominio de Internet (números de puerto menores que 1024). |
| NET_RAW | Use sockets RAW y PACKET. |
| SETFCAP | Defina recursos de archivo. |
| SETGID | Haga manipulaciones arbitrarias de GIDs de proceso y lista de GID suplementar. |
| SETPCAP | Modifique los recursos del proceso. |
| SETUID | Haga manipulaciones arbitrarias de UIDs de procesos. |
| SYS_CHROOT | Use chroot(2), altere el directorio raíz. |
| AUDIT_CONTROL | Habilite y deshabilite la auditoría del kernel; alterar reglas de filtro de auditoría; recuperar status de auditoría y reglas de filtrado. |
| AUDIT_READ | Permitir la lectura del log de auditoría vía socket netlink multicast. |
| BLOCK_SUSPEND | Permitir evitar suspensiones del sistema. |
| BPF | Permitir la creación de mapas BPF, carga de datos BPF Type Format (BTF), recuperación de código JITed de programas BPF y mucho más. |
| CHECKPOINT_RESTORE | Permitir operaciones relacionadas a puntos de verificación/restauración. Introducido en el kernel 5.9. |
| DAC_READ_SEARCH | Ignore verificaciones de permiso de lectura de archivo y verificaciones de permiso de lectura y ejecución de directorio. |
| IPC_LOCK | Bloquear memoria (mlock(2), mlockall(2), mmap(2), shmctl(2)). |
| IPC_OWNER | Ignore verificaciones de permiso para operaciones en objetos IPC del System V. |
| LEASE | Establezca concesiones en archivos arbitrarios (vea fcntl(2)). |
| LINUX_IMMUTABLE | Defina las señalizaciones de i-node FS_APPEND_FL y FS_IMMUTABLE_FL. |
| MAC_ADMIN | Permitir configuración MAC o alteraciones de estado. Implementado para el Smack LSM. |
| MAC_OVERRIDE | Sustituya el control de acceso obligatorio (MAC). Implementado para el Módulo de Seguridad Smack Linux (LSM). |
| NET_ADMIN | Ejecute varias operaciones relacionadas a la red. |
| NET_BROADCAST | Haga transmisiones de socket y escuche multicasts. |
| PERFMON | Permitir operaciones privilegiadas de desempeño y observabilidad del sistema usando perf_events, i915_perf y otros subsistemas del kernel |
| SYS_ADMIN | Ejecute una serie de operaciones de administración del sistema. |
| SYS_BOOT | Use reboot(2) y kexec_load(2), reinicie y cargue un nuevo kernel para ejecución posterior. |
| SYS_MODULE | Cargar y descargar módulos del kernel. |
| SYS_NICE | Aumente el valor legal del proceso (nice(2), setpriority(2)) y altere el valor legal para procesos arbitrarios. |
| SYS_PACCT | Use acct(2), active o desactive la contabilidad del proceso. |
| SYS_PTRACE | Rastree procesos arbitrarios usando ptrace(2). |
| SYS_RAWIO | Ejecute operaciones de puerto de E/S (iopl(2) e ioperm(2)). |
| SYS_RESOURCE | Sustituir límites de recursos. |
| SYS_TIME | Definir reloj del sistema (settimeofday(2), stime(2), adjtimex(2)); definir el reloj en tiempo real (hardware). |
| SYS_TTY_CONFIG | Utilice vhangup(2); emplear varias operaciones ioctl(2) privilegiadas en terminales virtuales. |
| SYSLOG | Ejecute operaciones syslog(2) privilegiadas. |
| WAKE_ALARM | Active algo que activará el sistema. |
Un user con esos permisos podría simplemente matar procesos del sistema, rebootear la máquina, cambiar configuraciones de red, crear nuevos users, cambiar permisos de archivos, etc.
docker run -it --name ubuntu --privileged ubuntu bash
root@a40542f4c76e:~# reboot
bash: reboot: command not found
La imagen de ubuntu es una imagen mínima sin muchos utilitarios lo que ya ayuda en la seguridad. Alpine es aún peor, con mucho menos. ¡Es por eso que alpine es top! ¡Lo menos es más!
docker run -it --name centos --privileged centos:8 bash
reboot
System has not been booted with systemd as init system (PID 1). Can't operate.
Failed to connect to bus: Host is down
Failed to talk to init daemon.
# Dentro del container
[root@5622c6f14e5e /]# reboot -f
Rebooting.
#
docker ps -a | grep centos
5622c6f14e5e 5d0da3dc9764 "bash" 2 minutes ago Exited (129) 2 minutes ago centos
En este caso el container reinició no el host, pero él reinició, o sea, tuvo privilegios para ejecutar el comando.
El uso de la flag --privileged al iniciar un contenedor Docker concede privilegios adicionales al contenedor, permitiendo que tenga acceso a dispositivos y recursos del sistema host. Sin embargo, esta opción no concede acceso directo al sistema host fuera del contenedor.
Cuando ejecutas comandos dentro de un contenedor Docker, están aislados del sistema hospedador, incluso cuando el contenedor es iniciado con la opción --privileged. Esto es una característica fundamental de la tecnología de contenedores, que visa garantizar la seguridad y el aislamiento entre los procesos del contenedor y los procesos del host.
Por lo tanto, incluso que tengas privilegios adicionales dentro del contenedor usando --privileged, el acceso al host aún es restringido. Esto significa que, incluso como usuario root dentro del contenedor, no tendrás acceso directo al sistema host, a menos que utilices técnicas específicas, como montar el sistema de archivos del host dentro del contenedor (lo que puede ser arriesgado y no es recomendado, a menos que sea necesario).
Entendiendo esto ahora todo lo que podemos hacer en Kubernetes.
El securityContext puede ser aplicado en todos los containers del pod si declarado a nivel de spec, solamente a un container específico si declarado a nivel del container o ambos siendo que el nivel de container tiene preferencia al nivel de pod. Pero, las capabilities solamente son aplicadas a nivel de container.
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: ubuntu
name: ubuntu
spec:
# Nivel de pod
securityContext:
runAsUser: 1000
containers:
- name: ubuntu
command: ["sleep","3600"]
image: ubuntu
# securityContext:
# runAsUser: 1001 << cambiando el usuario
# capabilities:
# add: ["NET_ADMIN","SYSLOG"]
# Manteniendo el mismo usuario pero añadiendo capabilities
# securityContext:
# capabilities:
# add: ["NET_ADMIN","SYSLOG"]
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
Para saber con cual usuario el container dentro del pod está ejecutando
# PODS # CONTAINER # COMMAND
kubectl exec pods/nginx-david-c8644f94d-nlgch nginx -- whoami