SSH-Agent: ¿Qué es y cómo funciona?
Hoy aprenderemos a usar ssh-agent y ssh-add para gestionar un anillo de claves privadas disponibles para conexiones SSH.
Introducción
Hace algunos días un alumno de mi curso online de SSH me consultó qué era SSH-Agent, y para qué servía, ya que debía agregar una clave privada para utilizar la autenticación asimétrica contra el servidor. Aquí veremos para qué sirven ssh-agent
y ssh-add
.
En varias ocasiones hacemos uso de la autenticación asimétrica para evitar colocar una contraseña en el login contra algunos servicios, tales como servicios git
, o algún servidor SSH remoto.
Anteriormente hablamos sobre cómo conectarnos sin utilizar contraseña en SSH. En ese artículo vimos cómo generar un par de claves para autenticarnos sin necesidad de utilizar contraseña. Es requisito saber cómo gestionar esas claves para poder entender lo que sigue, así que los invito a pasar por esa entrada si no la leyeron!
Muchas veces el sistemas nos seguirá pidiendo nuestra contraseña para poder utilizar la clave privada durante el establecimiento de la conexión segura. Esto se da, por ejemplo, si dicha clave a su vez está protegida por una contraseña o passphrase. Esto nos hace pensar dos cosas:
- Seguridad: utilizar una passphrase para la clave es más seguro que utilizar solamente una password, más si alguien se hace con nuestra computadora. De hecho sería conveniente establecer una passphrase a la hora de generar la clave privada.
- Comodidad: a nivel práctico, utilizar clave privada con passphrase sería similar a conectarse sin usar clave privada: el sistema igual nos pide una contraseña/passphrase.
¿Cómo podemos maximizar la seguridad sin complicarnos la vida? Acá es donde interviene ssh-agent
🙂
ssh-agent y ssh-add
ssh-agent
es un manejador de claves para SSH, es decir, mantiene las claves privadas en memoria, descifradas y listas para usarse. Esto nos facilita el hecho de utilizar dichas claves sin necesidad de cargarlas y descifrarlas (en el caso de que hayamos seteado una passphrase) cada vez que vayamos a usarlas.
ssh-agent
corre como un servicio en segundo plano de manera independiente al servicio principal de SSH. Además, mantiene las claves seguras evitando escribir información privada en el disco, y prohibiendo que cualquier aplicación del sistema pueda exportar las claves privadas sin el consentimiento del usuario.
¿Cómo funciona ssh-agent?
Primero que nada hay que aclarar un punto importante: las claves públicas y privadas de SSH están destinadas a autenticar al usuario, NO a cifrar el contenido del tráfico.
Seguramente en breve escriba algún post sobre mecanismos criptográficos, pero por el momento basta saber que el cifrado asimétrico (claves públicas y privadas) consumen muchos más recursos de cómputo que el simétrico (una clave secreta). Es por esto que el tráfico se cifra usando cifrado simétrico.
Desde el punto de vista del servidor SSH, el protocolo funciona de la siguiente manera (muy breve y resumido, da para un artículo entero):
- El cliente le da su clave pública.
- El servidor genera un mensaje denominado Key Challenge, y lo envía al cliente para realizar la verificación de identidad.
- El cliente usa la clave privada para realizar la verificación, y responde al servidor.
- El servidor constata la veracidad del Key Challenge del cliente.
- El servidor ahora sabe que el cliente es quien dice ser y establece el túnel.
Luego de esto se generan claves simétricas efímeras para el intercambio de tráfico cifrado.
Ahora bien, cuando decimos que el cliente usa su clave privada para generar la firma, en realidad «pide» al ssh-agent
la firma del mensaje, y el ssh-agent
, que tiene acceso a la clave privada, lo firma y la devuelve.
La siguiente imagen muestra, con poco nivel de detalle, los pasos principales para establecer el túnel utilizando ssh-agent.
Aquí es donde radica la fortaleza del protocolo cuando se hace uso de ssh-agent
: el servicio SSH deja de estar en contacto directo con las claves privadas, que pasan a estar gestionadas por un servicio independiente.
En un futuro artículo explicaré paso por paso el intercambio de mensajes de SSH.
Ejecutando ssh-agent
Para que nuestras claves sean accedidas por ssh-agent
primero debemos cargarlas utilizando la utilidad ssh-add
.
Para ello, primero verifiquemos que ssh-add
puede conectar con el daemon de ssh-agent
:
ssh-add -diego@cryptos ~ $ ssh-add -l
Could not open a connection to your authentication agent.
El modificador -l
de ssh-add
permite listar las claves añadidas a ssh-agent
. En este caso particular me dice que no puede conectar al daemon de ssh-agent
.
Dato que será de utilidad más adelante: el retorno de ssh-add
aquí es 2.
La conexión se lleva a cabo por sockets del dominio Unix (un archivo especial en el disco), cuya ruta está almacenada en la variable de entorno SSH_AUTH_SOCK
, que en mi caso todavía no tiene valor. Esto significa que en mi sesión de terminal no tengo en ejecución el daemon de ssh-agent
.
Podemos lanzarlo de esta forma:
diego@cryptos ~ $ eval $(ssh-agent)
Agent pid 157587
Esto debe haber cargado dicha variable:
diego@cryptos ~ $ echo $SSH_AUTH_SOCK
/tmp/ssh-XXXXXXV2gGiC/agent.157586
En general es un archivo temporal de socket Unix que incluye el PID del daemon de ssh-agent
.
Veamos ahora si el ssh-add
puede listar las claves añadidas:
diego@cryptos ~ $ ssh-add -l
The agent has no identities.
Ahora sí pudo establecer la conexión, aunque no tengo claves añadidas en el anillo de claves de ssh-agent
.
Dato importante, aquí ssh-add
retornó 1.
Añadiendo claves privadas
Se pueden añadir claves por nombre, o simplemente ejecutar ssh-add
sin argumentos para cargar las claves privadas RSA y DSA almacenadas en nuestro directorio ~/.ssh/
siempre y cuando tengan los nombres predeterminados:
~/.ssh/id_rsa
~/.ssh/id_ed25519
~/.ssh/id_dsa
~/.ssh/id_ecdsa
Veamos mi caso particular:
diego@cryptos ~ $ ssh-add
Identity added: /home/diego/.ssh/id_rsa (/home/diego/.ssh/id_rsa)
Aquí ssh-add
buscó en mi directorio .ssh/
las claves privadas con nombres predefinidos y las agregó. En el caso de que la clave hubiera tenido passphrase la hubiera pedido en este punto.
Si la clave no tiene el nombre predeterminado deberemos añadirla manualmente.
Un ejemplo completo: clave con contraseña
Para que veamos el proceso completo, he creado una clave con un nombre específico, y además le puse passphrase, de esta forma:
diego@cryptos ~ $ ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/home/diego/.ssh/id_rsa): /home/diego/.ssh/key_with_pass
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/diego/.ssh/key_with_pass
Your public key has been saved in /home/diego/.ssh/key_with_pass.pub
The key fingerprint is:
SHA256:rYFGrBg2fWYb2LuiTWx1Vrxf+fTEFTkdXYQNifvJpDc diego@cryptos
The key's randomart image is:
+---[RSA 3072]----+
| ..OO|
| . + . . oo=|
| + o O o . o|
| . + * = o .. .o.|
| . . * S o =o.+|
| . o + o ...E+.|
| = . . .. .o|
| = . |
| . . |
+----[SHA256]-----+
La clave se llama ~/.ssh/key_with_pass
, y tiene passphrase. Veamos qué ocurre si la añadimos al ssh-agent
:
diego@cryptos ~ $ ssh-add ~/.ssh/key_with_pass
Enter passphrase for /home/diego/.ssh/key_with_pass: <strong>XXXXXX</strong>
Identity added: /home/diego/.ssh/key_with_pass (diego@cryptos)
Como se ve, la pasé por argumento al comando ssh-add
, y luego éste me pidió la passphrase (lo que marqué con XXXXXX
en la salida por pantalla).
Ahora podemos listar las identidades almacenadas en e ssh-agent
:
diego@cryptos ~ $ ssh-add -l
2048 SHA256:K1qQ4JjkG17W6lfo8zbFAGbcf9HE/cNIaJ2O+uWi4wE /home/diego/.ssh/id_rsa (RSA)
3072 SHA256:rYFGrBg2fWYb2LuiTWx1Vrxf+fTEFTkdXYQNifvJpDc diego@cryptos (RSA)
Ahora tenemos las identidades (claves privadas) cargadas en nuestra sesión de terminal, dentro de ssh-agent
. Esto significa que hemos autorizado a ssh-agent
a utilizar estas claves por nosotros. Ahora, si vamos a conectarnos a un servidor ssh remoto usando alguna de estas claves, incluso la que tiene contraseña, el cliente SSH no nos pedirá dicha contraseña.
El cliente SSH le pedirá a ssh-agent
la firma de los mensajes, y como ya hemos autorizado a ssh-agent
a utilizar las claves privadas, directamente las usará.
Ejemplo: conectando a un servidor
Nada mejor que un ejemplo para verlo funcionando. En mi caso ya he copiado la clave pública ~/.ssh/key_with_pass.pub
dentro del authorized_keys
del usuario user
en el equipo remoto 192.168.0.150
utilizando la utilidad ssh-copy-id
tal y como aprendimos antes.
Si intento conectar directamente utilizando dicha clave me encuentro con esto:
diego@cryptos ~ $ ssh -i ~/.ssh/key_with_pass user@192.168.0.150
Enter passphrase for key '/home/diego/.ssh/key_with_pass':
Esta passphrase me la va a pedir cada vez que intente conectar, así que vamos por el camino de ssh-agent
como acabamos de ver:
diego@cryptos ~ $ eval $(ssh-agent)
Agent pid 158156
diego@cryptos ~ $ ssh-add ~/.ssh/key_with_pass
Enter passphrase for /home/diego/.ssh/key_with_pass:
Identity added: /home/diego/.ssh/key_with_pass (diego@cryptos)
Ahora si volvemos a intentar conectar, conecta directamente 🙂
diego@cryptos ~ $ ssh -i ~/.ssh/key_with_pass user@192.168.0.150
Linux enerious 5.18.0-12.2-liquorix-amd64 #1 ZEN SMP PREEMPT_DYNAMIC liquorix 5.18-13.1~bookworm (2022-07- x86_64
[...]
user@enerious:~$
Esto es válido únicamente para esta sesión de terminal.
Usando ssh-agent en todas las terminales
ssh-agent
se carga en la sesión de terminal actual, por lo que si abrimos una nueva terminal y necesitamos conectarnos, deberemos hacer todo el proceso nuevamente: ejecutar ssh-agent
para que se cargue la variable de entorno SSH_AUTH_SOCK
con el path del socket, y cargar las identidades con el ssh-add
.
Sí, es muy engorroso!
Como se vio, ssh-agent
genera un socket para su uso por ssh-add
, pero cada vez que ejecutamos ssh-agent
se genera un socket nuevo, por lo que, si lo ejecutamos en la sesión de terminal (.bashrc
, por ejemplo) tendremos un daemon ssh-agent
por sesión de terminal con un socket único.
Una interesante forma es configurar a ssh-agent para que utilice un socket Unix predefinido, y almacenar este valor en la variable de entorno SSH_AUTH_SOCK
. Así podemos correr ssh-agent
con la opción -a
para indicarle que utilice dicho socket. Esto último podemos hacerlo, además, sólo en el caso de que no haya otro ssh-agent
corriendo, para evitar que se genere más de un proceso autenticador.
Por ejemplo, podríamos agregar las siguientes líneas en nuestro ~/.bashrc
. Esta configuración es personal, así que, si alguien lo resolvió de otra forma, puede comentarme su alternativa.
# creamos la variable de entorno con el nombre del socket Unix
# (en este caso, dentro de ~/.ssh/
export SSH_AUTH_SOCK=~/.ssh/ssh-agent.$HOSTNAME.sock
# Verificamos si está corriendo el daemon ssh-agent
ssh-add -l 2>/dev/null >/dev/null
# si estaba corriendo, ssh-add lo usará y retornará 1 (no hay claves)
# si no estaba corriendo, su retorno será 2, por lo que
# procedemos a ejecutar el ssh-agent,
# y le decimos dónde crear el socket Unix (SSH_AUTH_SOCK):
if [ $? -ge 2 ]; then
ssh-agent -a "$SSH_AUTH_SOCK" >/dev/null
fi
De esta forma, cuando abramos cualquier terminal ya tendremos el ssh-agent
corriendo, asociado a un socket, y con la variable de entorno creada.
Ahora, cuando necesitemos conectarnos a un servicio SSH, solamente deberemos ejecutar ssh-add
y cargar las claves que necesitemos. Esta clave se cargará al daemon ssh-agent
, y podrá ser utilizada en dicha sesión de terminal, o en cualquier otra terminal que abramos.
Otra forma alternativa, si es que no quieren ensuciarse las manos en la terminal, es usar front-ends gráficos como Keychain 😛
Conclusiones
Y hemos llegado al final!
Ya sabíamos cómo generar y utilizar claves asimétricas para nuestras conexiones SSH, ahora a eso le sumamos cómo usar ssh-agent para mejorar la seguridad, además de facilitar el acceso a dichas claves.
Quedan varios temas en el tintero para futuros artículos. Entre ellos, el reenvío del servicio ssh-agent
a través de una conexión SSH… pero eso es otra historia, y lo trataré en otro artículo.
¡Espero que les haya gustado y les sirva el contenido!
Como siempre, cualquier comentario o duda pueden hacerla mediante nuestros medios de contacto.
¡Hasta la próxima!