¡Cuidado!, "ssh -A" puede ser muy peligroso

Veo muchos administradores de sistemas recomendando el uso de ssh -A con alegría, sin explicar los riesgos de seguridad asociados.

Todos sabemos que ssh permite entrar en servidores remotos de forma segura. Suponiendo que ese servidor sea una frontera y queramos saltar de allí a otros servidores de la red interna, tenemos diferentes posibilidades:

  • Tener una clave ssh en ese servidor frontera, lanzar un ssh-agent en él y cargar la clave. El problema es que tenemos una clave ahí y, por tanto, necesitamos poder confiar en ese servidor.
  • Utilizar ssh para establecer un túnel con el servidor frontera y emplear dicho túnel para enviar conexiones ssh nuevas a los servidores internos. Esta configuración es segura, pero complicada y poco transparente.
  • Utilizar ssh -A para que al establecer sesiones ssh a otros servidores (una vez que estamos conectados al servidor frontera), la solicitud de autenticación se canalice al ssh-agent de nuestro propia máquina personal. La clave ssh nunca sale de nuestra máquina.

ssh -A parece perfecto y seguro, salvo por un pequeño detalle: nuestro ssh-agent local completará cualquier petición de autenticación que le llegue. Si estamos conectados a un servidor malicioso, los malos podrían solicitar a nuestro ssh-agent autenticaciones para servidores diferentes, conectarse a ellos con nuestra identidad y hacer allí lo que les de la gana, incluyendo instalar sus propias claves públicas ssh para poder entrar con posterioridad sin depender de nosotros. Todo ello en milisegundos y sin que nos enteremos.

Veamos el manual del comando ssh:

-A

Enables forwarding of the authentication agent connection. This can also be specified on a per-host basis in a configuration file.

Agent forwarding should be enabled with caution. Users with the ability to bypass file permissions on the remote host (for the agent's UNIX-domain socket) can access the local agent through the forwarded connection. An attacker cannot obtain key material from the agent, however they can perform operations on the keys that enable them to authenticate using the identities loaded into the agent.

Comprobamos que el problema está perfectamente documentado, pero aún así sigo viendo recomendar el uso de ssh -A por todas partes, sin explicar los riesgos de seguridad asociados.

Lo cierto es que ssh -A es muy útil y conveniente. ¿Qué podemos hacer?.

En la bibliografía se proponen diversas alternativas seguras. Os recomiendo que le echeis un vistazo. En mi caso uso dos alternativas sencillas:

  • Al añadir una clave ssh al ssh-agent, le indico que pida confirmación cada vez que se solicite una autenticación. Eso se hace con ssh-add -c. El manual dice lo siguiente:

    -c

    Indicates that added identities should be subject to confirmation before being used for authentication. Confirmation is performed by ssh-askpass(1). Successful confirmation is signaled by a zero exit status from ssh-askpass(1), rather than text entered into the requester.

    El problema de esta opción es que es un coñazo meter la clave cada vez que hay una autenticación y que un actor malicioso puede esperar a que exista una petición legítima de autenticación para secuestrarla y colarnos el acceso a la máquina que no es.

    Por tanto, no recomiendo esta opción.

  • Mi opción favorita es utilizar una clave separada para los servicios que requieren el uso de ssh -A, de forma que su compromiso solo afecte a una red concreta, no a toda la infraestructura a la que tengo acceso. Esto es muy importante en mi caso, que puedo necesitar usar ssh -A en redes en las que no puedo confiar o activamente hostiles.

    Los pasos serían los siguientes:

    • Creo claves ssh separadas para entornos separados. El compromiso de una clave no afecta al resto. Es conveniente poner nombres claros a las claves para evitar confusiones.

    • A la hora entrar en una red determinada:

      1. Lanzamos un nuevo ssh-agent:

        $ ssh-agent bash
        

        Este comando lanza un proceso bash nuevo con un ssh-agent propio. Este ssh-agent estará vacío.

        Cuando el proceso bash termina, el ssh-agent muere también.

      2. Añadimos la clave de ese servicio mediante ssh-add.

        $ ssh-add ~/.ssh/example.org
        Enter passphrase for /home/jcea/.ssh/example.org:
        Identity added: /home/jcea/.ssh/example.org (/home/jcea/.ssh/example.org)
        
      3. Confirmamos que el ssh-agent tiene la clave ssh correcta, y solo esa clave:

        $ ssh-add -L
        ssh-rsa AAA...5cj /home/jcea/.ssh/example.org
        

      En este momento al usar ssh -A estaremos exponiendo exclusivamente la clave de example.org. Es un riesgo de seguridad, es cierto, pero si esa red está comprometida, al menos no pongo en peligro el resto de la infraestructura a la que tengo acceso.

Para evitar errores, tengo scripts como el siguiente:

#!/bin/sh

# Me aseguro de estar usando la clave correcta
ssh-add -L | grep example.org >/dev/null || { echo No estas usando la clave SSH correcta; exit 1; }

ansible-playbook -i inventory/lizardfs --ssh-common-args='-o ProxyCommand="ssh -W %h:%p example.org"' --extra-vars="ansible_ssh_user=jcea" lizardfs.yml

Este script lanza un despliegue LizardFS a través de Ansible y un túnel ssh, asegurándose de que estás usando la clave ssh correcta. Todo automático para evitar errores.