De privileged a least-privilege: endurecí mi homelab sin romper nada
Mi contenedor 'de seguridad' era el más peligroso de todos: corría privileged con host networking. Lo bajé a una sola capability, moví los secrets a SOPS, y rechacé un shortcut tentador. La receta y los trade-offs honestos.
Tenía un contenedor llamado security-backend — el que sirve mi dashboard de seguridad. Irónicamente, era el contenedor más peligroso de todo el homelab: corría privileged: true con network_mode: host. Acceso total al kernel y a la red del host.
Osea, mi servicio de seguridad era el hueco de seguridad.
Lo endurecí sin romper nada. Aquí está el antes/después y — más importante — los trade-offs que tomé, incluyendo un shortcut tentador que decidí no tomar.
El problema: privilegios que nadie necesita
privileged: true le da al contenedor todas las capabilities de Linux, acceso a todos los devices, y básicamente control del host. La gente lo pone porque “así funciona” y ya. Pero ese servicio solo necesitaba una cosa: leer un archivo de log (/var/log/auth.log) para detectar intentos de SSH.
Leer un archivo no necesita el kernel entero.
El después: una sola capability
security-backend:
cap_drop:
- ALL # tira TODAS las capabilities
cap_add:
- DAC_READ_SEARCH # solo bypass de lectura, para ese log
security_opt:
- "no-new-privileges:true"
volumes:
- /var/log/auth.log:/var/log/auth.log:ro # solo ese archivo, read-only
De todas las capabilities a una de solo-lectura. Además: quité host networking (el contenedor ya no ve/toca la red del host) y estreché el mount de /var/log (todo) a un solo archivo :ro.
El blast radius pasó de “control del kernel” a “leer archivos”.
El trade-off: perdí algo, y está bien
Al quitar network_mode: host, el contenedor dejó de ver los puertos y el tráfico de red del host — solo ve los suyos. ¿Se perdió esa métrica? No: la moví a node-exporter, que es la fuente correcta para métricas del host de todas formas.
Ganar seguridad casi siempre cuesta algo. La pregunta no es “¿pierdo algo?” sino “¿el trade-off vale la pena y hay un lugar mejor para eso?”. Aquí sí.
El shortcut que NO tomé
Para recuperar las métricas del host sin host networking, la tentación era montar el /proc del host dentro del contenedor:
volumes:
- /proc:/host/proc:ro # ❌ tentador, pero no
Se ve inofensivo (:ro). No lo es. Con /proc del host montado, un proceso root en el contenedor puede leer /proc/<PID>/environ de otros procesos del host — es decir, las variables de entorno de otros contenedores. Ahí viven secrets.
Montar /host/proc para arreglar una métrica me habría abierto un camino a los secrets de todo lo demás. Lo dejé fuera. node-exporter da esas métricas sin ese riesgo.
Los secrets: fuera del texto plano
Tenía ~40 secrets en un .env en texto plano. Los pasé a SOPS + age: el archivo va cifrado, la llave privada puede vivir fuera de la caja, y el deploy desencripta al vuelo.
sops secrets.enc.env # edita descifrado en tu editor, re-cifra al guardar
./deploy.sh # sops -d ... > .env y levanta
Y aquí soy honesto, porque casi nadie lo dice: en un solo host, la llave y el ciphertext conviven — si alguien tiene root en la caja, tiene ambos. El beneficio runtime es modesto. La ganancia real es cifrado-en-reposo, versionado sin miedo, y disaster recovery. Prefiero decir eso a vender SOPS como algo que no es en mi contexto.
La lección
Seguridad “de verdad” en un homelab no es agregar más herramientas (WAF, IDS, el juguete de moda). Es:
- Reducir el blast radius — que cada contenedor tenga el mínimo privilegio que necesita, y ni uno más.
- Rechazar shortcuts que se ven inofensivos pero abren superficie.
- Nombrar los trade-offs en vez de taparlos.
Eso es lo que separa “tengo Docker” de “opero sistemas”. El resto del stack — observabilidad, alertas, el agente de IA que lee todo esto — está en otro post y en el case study.