# ============================================================ # lab-linux-caps — el mismo servicio con distintos niveles de # privilegio, dentro de contenedores de laboratorio. # ------------------------------------------------------------ # El objetivo es ver, con el MISMO programa (un socket que escucha # en el puerto 80), la diferencia entre correr como root, correr # sin root, correr sin root pero con UNA capability concreta, y # correr con un perfil seccomp que bloquea una syscall. # # Ningun servicio publica puertos: el bind ocurre DENTRO del # contenedor y se observa en los logs. La red no tiene salida. # # docker compose up -d # docker compose logs como-root # escucha: es root # docker compose logs sin-root # falla: no es root y no puede el 80 # docker compose logs con-cap # escucha sin ser root: capability # docker compose logs con-seccomp # una syscall bloqueada por seccomp # docker compose down -v # ============================================================ name: lab-linux-caps networks: # Sin salida a internet: el bind se comprueba dentro del propio # contenedor, no hace falta hablar con nadie. aislada: driver: bridge internal: true # Un unico programa reutilizado por todos los servicios: intenta # escuchar en el puerto 80 (privilegiado: <1024) y dice si pudo. x-listener: &listener >- python3 -c "import socket,sys; s=socket.socket(); s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1); s.bind(('0.0.0.0', 80)); s.listen(); print('OK: escuchando en el puerto 80 como uid', __import__('os').geteuid(), flush=True); __import__('time').sleep(1e9)" services: # 1) COMO ROOT: puede con el puerto 80 porque root ignora la # restriccion de puertos privilegiados. Funciona, pero es # demasiado poder para "solo escuchar en un puerto". como-root: image: python:3.12-slim container_name: caps-como-root networks: [aislada] command: ["sh", "-c", *listener] restart: "no" # 2) SIN ROOT: un usuario normal, sin capabilities. No puede # escuchar en el 80. El log muestra PermissionError. Este es # el problema que empuja a la gente a "correr como root". sin-root: image: python:3.12-slim container_name: caps-sin-root networks: [aislada] user: "1000:1000" cap_drop: ["ALL"] command: ["sh", "-c", *listener] restart: "no" # 3) CON LA CAPABILITY EXACTA: sigue sin ser root, pero se le # concede SOLO CAP_NET_BIND_SERVICE. Escucha en el 80 sin # ninguno de los demas poderes de root. Esto es minimo # privilegio: la capability justa, nada mas. con-cap: image: python:3.12-slim container_name: caps-con-cap networks: [aislada] user: "1000:1000" cap_drop: ["ALL"] cap_add: ["NET_BIND_SERVICE"] command: ["sh", "-c", *listener] restart: "no" # 4) CON SECCOMP: un perfil que bloquea la familia de syscalls # chmod. El servicio intenta cambiar permisos de un archivo y # el kernel se lo niega (Operation not permitted), aunque por # permisos de usuario pudiera. Filtrar syscalls confina lo que # un proceso puede pedirle al kernel, no solo lo que puede leer. con-seccomp: image: python:3.12-slim container_name: caps-con-seccomp networks: [aislada] cap_drop: ["ALL"] security_opt: - seccomp=./seccomp-sin-chmod.json command: - sh - -c - | echo "== intento de chmod con la syscall bloqueada por seccomp ==" chmod 0777 /etc/hostname 2>&1 || echo "chmod bloqueado (asi debe ser)" echo "== el proceso sigue vivo; el resto de syscalls funciona ==" sleep 1000000 restart: "no"