#!/usr/bin/env bash # ============================================================================= # demo-01 · Diagnóstico de tu motor # ============================================================================= # A qué punto del guion sirve: puntos 1, 2, 10 y 11 # · 1 «Dos mundos: Podman en mi portátil, Docker en el servidor» # → `podman --version` y `docker --version`: dos binarios, dos diseños. # · 2 «Y ahora quiero el servidor sin root» # → cómo se sabe si tu contenedor corre rootless: `podman info` / # `docker info`, y el mapa de identificadores del espacio de usuario. # · 10 «Qué cambia rootless de verdad» # → la línea `rootlessPortForwarder` de `podman info`: el reenviador # que hace de proxy y se come la IP de origen. # · 11 «No es cosa de Podman: es cosa de rootless» # → el mismo dato, buscado en `docker info`. # # Qué se ve en pantalla: cuatro bloques, uno por idea. Cada bloque lanza el # comando exacto y comenta en una línea lo que significa lo que ha salido. # # Estado: # · Podman (versiones, rootless, mapa de UID, reenviador) → VERIFICADO en # local con Podman 6.1.4 rootless. La salida real está en # `verificacion/demo-01-salida.txt`. # · Docker → NO VERIFICADO aquí: en esta máquina no hay binario `docker`. # El bloque está escrito para que funcione si lo hay, y si no, imprime la # salida ESPERADA y qué línea hay que buscar. # # Seguridad de la demo: solo LEE. No crea contenedores, no toca la red del # sistema y no modifica nada. `podman unshare` abre un espacio de nombres de # usuario, ejecuta `cat` y sale. # ============================================================================= set -u titulo() { printf '\n\033[1m── %s\033[0m\n' "$1"; } orden() { printf '\n\033[1m$ %s\033[0m\n' "$1"; } nota() { printf ' ↳ %s\n' "$1"; } esperado() { printf '\n\033[1m[ESPERADO, no ejecutado aquí]\033[0m\n'; } printf '\033[1mDiagnóstico de tu motor de contenedores\033[0m (puntos 1, 2, 10 y 11)\n' printf 'Máquina: %s · %s\n' "$(hostname)" "$(date -Is)" # ----------------------------------------------------------------------------- titulo "1 · ¿Qué motor tengo? · punto 1" # ----------------------------------------------------------------------------- # La versión dice mucho más de lo que parece: aquí no se elige "Podman o # Docker" por gusto, son dos arquitecturas. Podman no tiene demonio; Docker sí. if command -v podman >/dev/null 2>&1; then orden "podman --version" podman --version nota "Un binario más que contesta. No hay servicio con privilegios detrás:" nota "el contenedor es un hijo de esta sesión y muere con ella." else nota "no hay 'podman' en el PATH de esta máquina." fi if command -v docker >/dev/null 2>&1; then orden "docker --version" docker --version nota "Mismo software por debajo, distinto modelo: hay un 'dockerd'" nota "arrancado como root al que le pides el contenedor." else esperado printf '$ docker --version\n' printf 'Docker version 28.x.y, build xxxxxxx\n' nota "La versión en sí no te dice si es rootless: para eso, el bloque 2." fi # ----------------------------------------------------------------------------- titulo "2 · ¿Mi motor es rootless? · punto 2" # ----------------------------------------------------------------------------- # Pregunta útil, sobre todo en el servidor: si es rootless, el usuario cero # dentro del contenedor NO es el root de la máquina. if command -v podman >/dev/null 2>&1; then orden "podman info --format '{{.Host.Security.Rootless}}'" podman info --format '{{.Host.Security.Rootless}}' nota "'true' = rootless. 'false' = los contenedores corren como root de verdad." orden "podman info | grep -i rootless" podman info 2>/dev/null | grep -i rootless nota "Tres líneas, y ninguna sobra: 'rootlessNetworkCmd' (con qué se monta la" nota "red: aquí 'pasta'), 'rootlessPortForwarder' (quiénes publican los puertos:" nota "aquí 'rootlessport') y 'rootless: true', que es la tercera y la misma" nota "respuesta del comando anterior. El reenviador del bloque 10 es la segunda." else nota "no hay 'podman' en el PATH: se salta la comprobación rootless." fi # ----------------------------------------------------------------------------- titulo "3 · Qué significa rootless por dentro · punto 2" # ----------------------------------------------------------------------------- # El truco es del kernel: espacios de nombres de usuario. Dentro, el proceso se # cree root; fuera, es un usuario sin privilegios con un rango de identificadores # prestados. Se ve en el mapa de UID. # Se usa 'podman unshare' para NO tener que crear ningún contenedor: abre el # espacio de nombres, ejecuta 'cat' y sale. if command -v podman >/dev/null 2>&1; then orden "podman unshare cat /proc/self/uid_map" podman unshare cat /proc/self/uid_map 2>&1 nota "El 0 de la izquierda (dentro) es un 1000 cualquiera (fuera):" nota "quien escape del contenedor no encuentra el root del host, encuentra un don nadie." nota "En un contenedor ya en marcha, el mismo dato se ve con:" nota " cat /proc/self/uid_map (dentro del contenedor)" else nota "sin podman no hay espacio de nombres que enseñar." fi # ----------------------------------------------------------------------------- titulo "4 · El reenviador: donde se pierde la IP · punto 10" # ----------------------------------------------------------------------------- # En rootless nadie abre el puerto en el kernel por ti: lo abre un proceso tuyo # que escucha y reenvía hacia el contenedor. Ese proceso es un proxy, y por eso # dentro del contenedor la conexión no llega del visitante sino del mensajero. if command -v podman >/dev/null 2>&1; then orden "podman info --format '{{.Host.RootlessPortForwarder}}'" podman info --format '{{.Host.RootlessPortForwarder}}' 2>&1 orden "podman info --format '{{.Host.RootlessNetworkCmd}}'" podman info --format '{{.Host.RootlessNetworkCmd}}' 2>&1 nota "Esos dos nombres son la pieza de la que habla el guion: el reenvío" nota "de puertos ocurre en el espacio de usuario, no en el NAT del kernel." else nota "sin podman no hay reenviador que enseñar." fi # ----------------------------------------------------------------------------- titulo "5 · ¿Y en Docker? Exactamente lo mismo · punto 11" # ----------------------------------------------------------------------------- # La frase del episodio: no es cosa de Podman, es cosa de rootless. Docker # rootless arrastra el mismo comportamiento porque se apoya en RootlessKit. if command -v docker >/dev/null 2>&1; then orden "docker info --format '{{.SecurityOptions}}'" docker info --format '{{.SecurityOptions}}' 2>&1 nota "Si aparece 'rootless' en la lista, es rootless. Si no aparece, no lo es." orden "docker context inspect --format '{{.Endpoints.docker.Host}}'" docker context inspect --format '{{.Endpoints.docker.Host}}' 2>&1 nota "Segunda pista: un socket en /run/user//docker.sock es rootless;" nota "unix:///var/run/docker.sock es el demonio de root de toda la vida." else esperado printf '$ docker info --format '"'"'{{.SecurityOptions}}'"'"'\n' printf '[name=seccomp,profile=builtin name=rootless name=cgroupns]\n\n' printf '$ docker context inspect --format '"'"'{{.Endpoints.docker.Host}}'"'"'\n' printf 'unix:///run/user/1000/docker.sock\n' nota "Docker rootless se instala aparte y se arranca como usuario;" nota "si no lo has hecho, tu Docker es rootful aunque no te lo diga la versión." fi # ----------------------------------------------------------------------------- titulo "Cierre del bloque" # ----------------------------------------------------------------------------- printf ' · Dos binarios, dos diseños: sin demonio (Podman) / con demonio (Docker).\n' printf ' · Rootless se comprueba con %s, no con la versión.\n' "'info'" printf ' · Y en rootless los puertos los publica un reenviador: ese es el que\n' printf ' hace que dentro del contenedor veas al mensajero y no al visitante.\n\n' exit 0