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

  1. 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.
  2. 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.
  3. Agentes de IA con privilegios de sudo sin control – Al crear un usuario “ai‑bot” y otorgarle NOPASSWD en /etc/sudoers, se abre la puerta a comandos peligrosos sin revisión humana.
  4. 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”.
  5. Configuraciones de SSH dispersas – Archivos authorized_keys en 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 (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
  1. 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 ""
    
  2. Almacenar la clave del hipervisor en ssh-agent y usarla solo cuando sea necesario ejecutar comandos administrativos (pveam, qm, pct).

  3. 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_keys que contiene únicamente la clave de workloads.

  4. 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-bot
    
    • command apunta a un script que valida la petición (por ejemplo, verifica que el comando sea journalctl o systemctl status).
    • permitopen limita las conexiones a puertos internos específicos.
  5. 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 ProxyJump para 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:

  1. Revoca claves caducadas (ej. > 90 días).
  2. Registra cada acceso en auth.log y los envía a un índice de Elasticsearch o a un archivo centralizado.
  3. Genera un informe semanal con ssh-audit para 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

  1. Conexión al bastión

    ssh bastion
    

    Debe solicitar la frase de paso del hipervisor.

  2. Acceso a una VM

    ssh vm01.lab
    

    No debe pedir frase y debe usar la clave de workloads.

  3. 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_keys y en sudoers.

  4. Auditoría
    Revisa /var/log/auth.log en 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_keypair con cron para regenerar la clave de workloads cada 60 días y actualizar authorized_keys en todos los nodos.
  • Limitaciones de AI bots: si el agente necesita ejecutar más comandos, añádelos explícitamente al archivo sudoers.d/ai-bot y actualiza la lista de permitopen en authorized_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 fail2ban configurado para filtrar intentos de login fallidos desde la IP del AI bot ayuda a contener ataques de fuerza bruta.