Sincronización de Claves SSH ============================= NoxPanel sincroniza automáticamente las claves SSH del usuario hacia los shell users de hosting, permitiendo acceso SSH sin contraseña tanto a VMs como a hosting compartido desde una única sección del panel. Visión General -------------- Cuando un usuario añade o elimina una clave SSH en la sección **Claves SSH** del panel, NoxPanel despliega automáticamente esa clave en todos los shell users de hosting asociados al usuario, escribiendo el fichero ``authorized_keys`` en el contenedor ``hosting-core`` via SSH. .. note:: Las claves SSH sirven tanto para VPS (Proxmox) como para hosting compartido. El panel muestra un banner informativo indicando esta doble funcionalidad. Arquitectura ------------ Módulo Principal ~~~~~~~~~~~~~~~~ **Ubicación**: ``hosting/ssh_key_sync.py`` Funciones principales: ``build_authorized_keys_content(user) → str`` Recopila todas las claves SSH activas del usuario (modelo ``SSHKey`` en ``accounts/models.py``) y genera el contenido del fichero ``authorized_keys``. ``get_shell_users_for_user(user) → QuerySet`` Encuentra todos los shell users activos del usuario siguiendo la cadena: ``User → TenantUser → Tenant → HostingShellUser``. ``sync_ssh_keys_for_shell_user(shell_user, content) → bool`` Escribe el fichero ``authorized_keys`` en hosting-core via ``_ssh_exec()``: 1. Obtiene UID/GID del usuario Linux con ``id -u`` / ``id -g`` 2. Crea el directorio ``.ssh`` con ``mkdir -p`` 3. Escribe el contenido con ``printf`` 4. Aplica permisos: ``chmod 700 .ssh``, ``chmod 600 authorized_keys`` 5. Aplica ownership: ``chown uid:gid`` ``sync_ssh_keys_for_user(user) → dict`` Función principal: sincroniza todas las claves de un usuario a todos sus shell users. Devuelve ``{'synced': N, 'errors': [...]}``. Triggers de Sincronización ~~~~~~~~~~~~~~~~~~~~~~~~~~ La sincronización se dispara automáticamente en estos puntos: .. list-table:: :header-rows: 1 :widths: 30 30 40 * - Evento - Fichero - Detalle * - Añadir clave SSH (web) - ``accounts/views.py`` - Después de ``SSHKey.objects.create()`` * - Eliminar clave SSH (web) - ``accounts/views.py`` - Después de ``SSHKey.objects.filter().delete()`` * - Añadir clave SSH (API) - ``accounts/api.py`` - ``SSHKeyViewSet.perform_create()`` * - Eliminar clave SSH (API) - ``accounts/api.py`` - ``SSHKeyViewSet.perform_destroy()`` * - Crear shell user (API) - ``hosting/api.py`` - ``HostingShellUserViewSet.perform_create()`` Path del authorized_keys ~~~~~~~~~~~~~~~~~~~~~~~~~ .. important:: El path correcto es el del **chroot**, no el de ISPConfig: ``/var/www/chroot/{username}/home/{username}/.ssh/authorized_keys`` SSH lee ``authorized_keys`` **antes** del chroot, usando el home del usuario en ``/etc/passwd`` (que apunta a ``/home/{username}`` — un directorio que no existe). Por eso se requiere ``AuthorizedKeysFile`` explícito en ``sshd_config``. Configuración SSH en hosting-core ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ El ``Match Group shellusers`` en ``/etc/ssh/sshd_config`` debe incluir: .. code-block:: text Match Group shellusers AuthorizedKeysFile /var/www/chroot/%u/home/%u/.ssh/authorized_keys ChrootDirectory /var/www/chroot/%u AllowTcpForwarding no X11Forwarding no PermitTunnel no AllowAgentForwarding no Esta configuración se genera automáticamente en ``hosting-core/entrypoint.sh`` durante el arranque del contenedor (función ``configure_shell_sandbox``). Conexión SSH ~~~~~~~~~~~~~ Los usuarios se conectan via: .. code-block:: bash ssh {username}@{dominio} -p 2222 El puerto 2222 del host se mapea al puerto 22 del contenedor ``hosting-core``. Diagrama de Flujo ~~~~~~~~~~~~~~~~~~ .. code-block:: text ┌──────────────────┐ │ Usuario añade │ │ clave SSH │ │ (panel/API) │ └────────┬─────────┘ │ ▼ ┌──────────────────┐ │ SSHKey.create() │ │ (accounts) │ └────────┬─────────┘ │ ▼ ┌──────────────────┐ │ sync_ssh_keys_ │ │ for_user() │ └────────┬─────────┘ │ ▼ ┌──────────────────┐ SSH (paramiko) ┌──────────────────┐ │ build_authorized │ ──────────────────────> │ hosting-core │ │ _keys_content() │ │ authorized_keys │ └──────────────────┘ └──────────────────┘ Troubleshooting ---------------- **El SSH sigue pidiendo contraseña:** 1. Verificar que la clave está en el path correcto: .. code-block:: bash docker exec noxpanel-hosting-core-1 cat /var/www/chroot/{user}/home/{user}/.ssh/authorized_keys 2. Verificar permisos (deben ser 700 el dir, 600 el archivo): .. code-block:: bash docker exec noxpanel-hosting-core-1 ls -la /var/www/chroot/{user}/home/{user}/.ssh/ 3. Verificar ``AuthorizedKeysFile`` en sshd_config: .. code-block:: bash docker exec noxpanel-hosting-core-1 grep AuthorizedKeysFile /etc/ssh/sshd_config 4. Verificar que el usuario pertenece al grupo ``shellusers``: .. code-block:: bash docker exec noxpanel-hosting-core-1 groups {username} **La sincronización falla silenciosamente:** La sincronización usa ``try/except`` con ``pass`` para no bloquear la operación principal. Revisar logs de Django: .. code-block:: bash docker logs noxpanel-web-1 2>&1 | grep ssh_key_sync