Problema
En muchos homelabs y pequeños entornos de producción se combina Proxmox como hipervisor con máquinas virtuales (VM) y contenedores LXC que ejecutan servicios automatizados mediante Ansible, Cloud‑Init o scripts personalizados. La gestión de claves SSH se vuelve un punto crítico: por un lado, la automatización requiere claves sin frase de paso para que los playbooks no se interrumpan; por otro, la seguridad exige que cada entidad tenga el menor privilegio posible. La llegada de agentes de IA (Hermes, Pi.dev, etc.) que necesitan acceso SSH a los nodos complica aún más la ecuación, pues pueden ejecutar comandos con privilegios elevados y, si no se controla, podrían causar daños irreparables. El reto es definir una arquitectura de claves que permita automatizar sin fricción y, al mismo tiempo, limitar el alcance de cada credencial.
Causa
- Uso indiscriminado de claves sin frase de paso – Muchos administradores copian la misma clave privada a todas las VM/LXC para simplificar Ansible. Eso elimina la fricción, pero cualquier compromiso de una máquina compromete todo el clúster.
- Falta de separación entre hipervisor y workloads – Cuando la misma pareja de claves sirve tanto al nodo Proxmox como a los sistemas invitados, se pierde la capacidad de revocar acceso de forma granular.
- Agentes de IA con privilegios de sudo sin control – Al crear un usuario “ai‑bot” y otorgarle
NOPASSWDen/etc/sudoers, se abre la puerta a comandos peligrosos sin revisión humana. - Ausencia de bastión o proxy de autorización – Sin un punto de control central, cada nodo acepta conexiones directas, lo que dificulta auditorías y la aplicación de políticas de “least privilege”.
- Configuraciones de SSH dispersas – Archivos
authorized_keysen cada VM se gestionan manualmente, lo que lleva a claves huérfanas o a permisos excesivos que pasan desapercibidos.
Solución
1. Arquitectura de claves en tres capas
| Capa | Propósito | Tipo de clave | Frase de paso |
|---|---|---|---|
| Hipervisor (Proxmox) | Acceso administrativo al host y a la API de Proxmox | RSA 4096 o Ed25519 | Sí (cifrada con ssh-agent) |
| Workloads (VM/LXC) | Operaciones de configuración y despliegue (Ansible, Cloud‑Init) | Ed25519 (sin frase) | No |
| AI agents | Ejecutar tareas limitadas bajo supervisión | Ed25519 (sin frase) + restricciones en authorized_keys |
No |
-
Generar claves separadas
# Hipervisor (con frase) ssh-keygen -t ed25519 -f ~/.ssh/proxmox_id_ed25519 -C "proxmox-admin" -N "miFraseSegura" # Workloads (sin frase) ssh-keygen -t ed25519 -f ~/.ssh/workload_id_ed25519 -C "ansible-automation" -N "" # AI agents (sin frase, con restricciones) ssh-keygen -t ed25519 -f ~/.ssh/ai_bot_id_ed25519 -C "ai-bot" -N "" -
Almacenar la clave del hipervisor en
ssh-agenty usarla solo cuando sea necesario ejecutar comandos administrativos (pveam,qm,pct). -
Distribuir la clave de workloads a los nodos mediante Cloud‑Init o Ansible, pero no copiarla al hipervisor. Cada VM/LXC tiene su propio
authorized_keysque contiene únicamente la clave de workloads. -
Restringir la clave del AI bot mediante opciones de
authorized_keys:command="/usr/local/bin/ai-wrapper.sh",no-agent-forwarding,no-port-forwarding,no-pty,no-user-rc,no-X11-forwarding,permitopen="localhost:22" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBot... ai-botcommandapunta a un script que valida la petición (por ejemplo, verifica que el comando seajournalctlosystemctl status).permitopenlimita las conexiones a puertos internos específicos.
-
Crear un bastión SSH (puede ser un contenedor LXC dedicado) que actúe como único punto de entrada. Todos los usuarios, incluidos los bots, deben conectar primero al bastión y luego usar
ProxyJumppara llegar a los destinos.Host bastion HostName bastion.lan User admin IdentityFile ~/.ssh/proxmox_id_ed25519 Host *.lab ProxyJump bastion IdentityFile ~/.ssh/workload_id_ed25519
2. Políticas de sudoers por host
En cada VM/LXC define un archivo bajo /etc/sudoers.d/ que conceda solo los comandos necesarios al usuario ansible y al usuario ai-bot.
# /etc/sudoers.d/ansible
ansible ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart nginx, /usr/bin/apt-get update
# /etc/sudoers.d/ai-bot
ai-bot ALL=(ALL) NOPASSWD: /usr/bin/journalctl -u nginx, /usr/bin/systemctl status nginx
Evita ALL=(ALL) NOPASSWD: ALL.
3. Rotación y auditoría automática
Implementa un playbook que:
- Revoca claves caducadas (ej. > 90 días).
- Registra cada acceso en
auth.logy los envía a un índice de Elasticsearch o a un archivo centralizado. - Genera un informe semanal con
ssh-auditpara detectar claves débiles.
Cuándo aplicar esta solución
- Entornos mixtos donde coexisten hipervisor Proxmox, VMs/LXC y procesos automatizados (Ansible, Cloud‑Init).
- Uso de agentes de IA que requieren acceso a nodos pero deben estar limitados a tareas concretas.
- Política de least privilege obligatoria por cumplimiento interno o regulatorio.
- Escenarios donde la rotación de claves es factible mediante Ansible o scripts de gestión.
No es necesario aplicar este esquema si:
- Solo tienes un único nodo sin automatización.
- No utilizas agentes externos que ejecuten comandos en los nodos.
- La infraestructura está aislada y no hay riesgo de acceso externo.
Código
# Generar claves separadas (hipervisor, workloads, AI bot)
ssh-keygen -t ed25519 -f ~/.ssh/proxmox_id_ed25519 -C "proxmox-admin" -N "miFraseSegura"
ssh-keygen -t ed25519 -f ~/.ssh/workload_id_ed25519 -C "ansible-automation" -N ""
ssh-keygen -t ed25519 -f ~/.ssh/ai_bot_id_ed25519 -C "ai-bot" -N ""
# Añadir la clave del AI bot con restricciones
cat <<'EOF' >> ~/.ssh/authorized_keys
command="/usr/local/bin/ai-wrapper.sh",no-agent-forwarding,no-port-forwarding,no-pty,no-user-rc,no-X11-forwarding,permitopen="localhost:22" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBot... ai-bot
EOF
# Configuración de ProxyJump en ~/.ssh/config
cat <<'EOF' >> ~/.ssh/config
Host bastion
HostName bastion.lan
User admin
IdentityFile ~/.ssh/proxmox_id_ed25519
Host *.lab
ProxyJump bastion
IdentityFile ~/.ssh/workload_id_ed25519
EOF
Verificación
-
Conexión al bastión
ssh bastionDebe solicitar la frase de paso del hipervisor.
-
Acceso a una VM
ssh vm01.labNo debe pedir frase y debe usar la clave de workloads.
-
Prueba del AI bot
ssh -i ~/.ssh/ai_bot_id_ed25519 [email protected] "journalctl -u nginx -n 5"El comando solo debe ejecutarse si está permitido en
authorized_keysy ensudoers. -
Auditoría
Revisa/var/log/auth.logen el bastión y en la VM para confirmar que el usuario y la clave aparecen registrados.
Notas adicionales
- Backup de claves: guarda la clave del hipervisor en un gestor de secretos (Vault, Bitwarden) y nunca la almacenes sin cifrar en repositorios.
- Rotación automática: combina
ansible.builtin.openssh_keypairconcronpara regenerar la clave de workloads cada 60 días y actualizarauthorized_keysen todos los nodos. - Limitaciones de AI bots: si el agente necesita ejecutar más comandos, añádelos explícitamente al archivo
sudoers.d/ai-boty actualiza la lista depermitopenenauthorized_keys. - Escalado: en clústers con varios nodos Proxmox, usa la misma clave del hipervisor en todos los hosts y gestiona el acceso mediante
pveum(Proxmox User Management) en lugar de SSH directo cuando sea posible. - Monitorización: un simple
fail2banconfigurado para filtrar intentos de login fallidos desde la IP del AI bot ayuda a contener ataques de fuerza bruta.