Un servidor de laboratorio, tres roles de confianza, un solo dominio de fallo
Una entrada VPN, servicios privados y tareas de CI necesitan privilegios separados incluso en un solo servidor. Separa los roles, aísla las redes y conserva la recuperación por consola.
Todo funciona hasta que el ejecutor de compilación necesita el mismo socket del host que usa el invitado VPN, y entonces nadie puede explicar por qué una tarea puede leer las claves de la pasarela. Todo se instaló en una sola máquina porque cabía, y a cada invitado se le dio acceso amplio porque era la forma más rápida de hacerlo funcionar. El resultado es un host cuya frontera de confianza es el cable de alimentación. Separar los roles cuesta menos trabajo de lo que parece, y empieza por ponerles nombre.
Tres roles que ahora comparten todo
Una entrada VPN, un conjunto de servicios privados y un ejecutor de compilación quieren cosas distintas. El primero necesita un puerto público y una red estrecha en el lado privado. El segundo necesita almacenamiento persistente y secretos de aplicación. El tercero ejecuta código que escribió otra persona.
Dar a los tres el mismo acceso es lo que hace confuso el síntoma, porque entonces cualquiera de ellos puede afectar a los demás. Una tarea de compilación que alcanza el socket del hipervisor es una tarea que puede detener la VPN. Un invitado de servicios con una ruta a la red de gestión puede cambiar la configuración del conmutador. Los privilegios son el problema, y la lista de procesos solo es donde se ve. Nombrar primero los roles da a cada privilegio un lugar donde vivir.
El esquema de abajo es un punto de partida para esa separación, no una promesa de alta disponibilidad. Trátalo como una hipótesis que verificar, no como el diseño terminado.
Separa tres roles, no tres procesos
Tres invitados en un host dan tres conjuntos de privilegios y tres posiciones de red. No dan tres dominios de fallo, y el diseño debería decirlo en voz alta.
La separación solo ayuda si un compromiso en un invitado no concede automáticamente los demás, y eso es un ajuste del hipervisor, no una convención de nombres. Llamar pasarela a una VM no impide que otro invitado llegue al host. Lee la configuración del conmutador virtual en lugar del nombre del host y luego confirma que cada invitado está donde crees.
host
├─ vpn-gw 10.60.10.2/24 public 51820/udp only
├─ services 10.60.0.10/24 no route to the management network
└─ ci-runner 10.60.20.2/24 outbound only, no host socket
Escribe la topología y luego verifícala con las herramientas que la poseen. Un invitado que aparece en el puente equivocado es un hallazgo, no un detalle. Corrige la red antes de cambiar cualquier configuración de servicio.
Deja que el invitado VPN abra solo las puertas que posee
Publica el puerto del túnel en el enrutador y nada más. Los puertos de administración de invitados pertenecen a una ruta de gestión, no a internet, y la propia interfaz del hipervisor también pertenece allí.
La red del lado privado debería llegar a los servicios que necesitan los usuarios remotos y detenerse ahí. La autenticación de la aplicación sigue activada, porque un túnel es transporte y no un inicio de sesión. Mantén la tabla de rutas del invitado VPN lo bastante pequeña para describirla en una frase. Si no puedes describirla con esa sencillez, la frontera ya se ha difuminado.
virsh net-list --all
virsh net-dumpxml lab-private
sudo nft list ruleset | grep -n -E '51820|10.60'
Cualquier cosa que escuche en el host y no se necesite para la gestión no debería escuchar en absoluto. Eso incluye la API del hipervisor, que es fácil dejar expuesta en una interfaz pública.
Las tareas de compilación necesitan otro tipo de frontera
Una tarea de CI ejecuta órdenes controladas por el repositorio, así que su confianza depende de quién puede subir código. Un ejecutor de shell ejecuta esas órdenes como el usuario del ejecutor en el host, y esa es la frontera más amplia disponible.
Los ejecutores de contenedores son más estrechos, y un contenedor privilegiado no es de los estrechos. Mantén el ejecutor en su propio invitado con su propio ámbito de red, y dale solo el acceso a artefactos y despliegue que necesitan las tareas. Los trabajadores desechables reducen el estado retenido; no reducen la accesibilidad ni la exposición de secretos. Ambas cosas siguen necesitando límites explícitos.
El invitado de servicios posee los datos, así que su plan de copia de seguridad es el que tiene que funcionar. Mantén los repositorios, las subidas y la base de datos en el mismo calendario. Prueba una restauración en lugar de leer el estado de la tarea.
Planifica el fallo que comparten los tres
Un host es un dominio de fallo. Un reinicio elimina el túnel y todos los servicios privados en el mismo momento, y es justo cuando una vía de entrada importa más.
Conserva un acceso por consola que no dependa del invitado VPN, ya sea una consola física, una interfaz de gestión fuera de banda o una consola del proveedor. Una instantánea guardada en el mismo disco no es una copia independiente, y una copia de seguridad que se ejecuta dentro del invitado que protege tampoco. Luego prueba el arranque en frío como una secuencia y no como una lista: host, redes virtuales, ruta del túnel y una aplicación concreta bajo una cuenta ordinaria.
virsh list --all
systemctl is-active libvirtd
curl -sS -m 5 -o /dev/null -w '%{http_code}\n' https://10.60.0.10/
ss -ltnp | grep -E ':51820|:443'
El orden de arranque de los procesos no demuestra nada sobre rutas y DNS, así que prueba la ruta en lugar de los estados de las unidades. La ruta es lo que un usuario remoto necesita de verdad.