8 min de lectura
Así gestiono mis conexiones SSH con ~/.ssh/config

Hasta hace poco mi forma de gestionar conexiones SSH era bastante rudimentaria.

Para empezar, tenía la mala práctica de utilizar la misma llave SSH para prácticamente todos mis servidores.

Algo como:

ssh -i ~/.ssh/id_rsa ubuntu@192.168.1.100

Como tampoco quería memorizar las IPs, usuarios y comandos completos, mi solución fue crear aliases directamente en mi .bashrc:

alias production="ssh -i ~/.ssh/id_rsa ubuntu@192.168.1.100"
alias staging="ssh -i ~/.ssh/id_rsa ubuntu@192.168.1.200"
alias personal-server="ssh -i ~/.ssh/id_rsa root@203.0.113.10"

Así podía simplemente ejecutar:

production

Y técnicamente funcionaba.

Pero con el tiempo empecé a encontrar varios problemas con esta solución.

Mis dotfiles

Actualmente tengo mis dotfiles versionados con Git para poder mantener y reutilizar mi configuración entre diferentes máquinas.

Eso incluye mi .bashrc.

Y ahí apareció el problema. Al tener la definición de mis conexiones SSH dentro de los aliases, estas también pasarían a formar parte de mi repositorio de Git.

No quería publicar innecesariamente las direcciones IP de mis servidores. No porque fueran un secreto o porque ocultarlas fuera una medida de seguridad, sino simplemente para evitar tener información donde no debería estar.

Mi primera idea fue buscar una forma de separar esos aliases del .bashrc.

Básicamente quería tener un archivo local con mis conexiones SSH que pudiera mantener fuera de mi repositorio. Buscando cómo hacerlo terminé descubriendo que estaba intentando solucionar un problema que SSH ya tenía resuelto.

No necesitaba tener otro archivo de aliases en mi máquina, sino utilizar el archivo ~/.ssh/config que SSH ya tiene para este propósito.

Descubriendo ~/.ssh/config

SSH permite definir configuraciones específicas para nuestras conexiones directamente en el archivo:

~/.ssh/config

Por ejemplo, uno de mis aliases:

alias production="ssh -i ~/.ssh/id_rsa ubuntu@192.168.1.100"

podía convertirse en:

Host production
    HostName 192.168.1.100
    User ubuntu
    IdentityFile ~/.ssh/production

Ahora puedo conectarme simplemente ejecutando:

ssh production

production funciona como un alias para esa conexión, pero a diferencia de mi alias de Bash, ahora es SSH quien conoce la configuración.

Cada propiedad tiene una función:

  • Host es el nombre que voy a utilizar para identificar la conexión.
  • HostName es la dirección real del servidor, ya sea una IP o un dominio.
  • User es el usuario con el que quiero conectarme.
  • IdentityFile indica qué llave privada quiero utilizar.

También podemos configurar otras opciones.

Por ejemplo, si nuestro servidor utiliza un puerto diferente al puerto 22:

Host production
    HostName 192.168.1.100
    User ubuntu
    Port 2222
    IdentityFile ~/.ssh/production

Pero seguimos conectándonos exactamente de la misma manera:

ssh production

Una llave diferente para cada servidor

Descubrir ~/.ssh/config también me hizo cambiar otra mala práctica que tenía: utilizar la misma llave SSH en diferentes servidores.

Antes podía tener algo así:

alias production="ssh -i ~/.ssh/id_rsa ubuntu@192.168.1.100"
alias staging="ssh -i ~/.ssh/id_rsa ubuntu@192.168.1.200"
alias personal-server="ssh -i ~/.ssh/id_rsa root@203.0.113.10"

Todos utilizaban id_rsa.

Si esa llave llegara a comprometerse, tendría que reemplazarla en todos los lugares donde la estaba utilizando.

En cambio, podemos generar una llave diferente para cada servidor.

Generando una llave con Ed25519

Para llaves nuevas, una opción moderna es Ed25519. Podemos generar una llave para nuestro servidor de producción utilizando:

ssh-keygen -t ed25519 -f ~/.ssh/production

Con -t ed25519 indicamos el tipo de llave que queremos generar y con -f ~/.ssh/production definimos dónde queremos guardarla.

Después de ejecutar el comando tendremos dos archivos:

~/.ssh/production
~/.ssh/production.pub

production contiene nuestra llave privada. Esta es la que posteriormente utilizaremos en IdentityFile y debemos mantener únicamente en nuestra máquina.

production.pub contiene nuestra llave pública, que es la que podemos agregar al servidor al que queremos conectarnos.

Generando una llave con RSA

También podemos utilizar RSA. Por ejemplo, podemos generar una llave RSA de 4096 bits con:

ssh-keygen -t rsa -b 4096 -f ~/.ssh/production

En este caso:

  • -t rsa indica que queremos generar una llave RSA.
  • -b 4096 define el tamaño de la llave en 4096 bits.
  • -f ~/.ssh/production indica dónde queremos guardarla.

Al igual que con Ed25519, obtendremos nuestra llave privada y pública:

~/.ssh/production
~/.ssh/production.pub

Para llaves nuevas, Ed25519 suele ser una buena opción por su tamaño reducido y buen nivel de seguridad. RSA sigue siendo útil cuando necesitamos compatibilidad con sistemas que no soportan Ed25519.

Independientemente del tipo de llave que utilicemos, podemos repetir el proceso para cada uno de nuestros servidores:

ssh-keygen -t ed25519 -f ~/.ssh/production
ssh-keygen -t ed25519 -f ~/.ssh/staging
ssh-keygen -t ed25519 -f ~/.ssh/personal-server

Y finalmente configurar cada una dentro de ~/.ssh/config:

Host production
    HostName 192.168.1.100
    User ubuntu
    IdentityFile ~/.ssh/production

Host staging
    HostName 192.168.1.200
    User ubuntu
    IdentityFile ~/.ssh/staging

Host personal-server
    HostName 203.0.113.10
    User root
    IdentityFile ~/.ssh/personal-server

Esto limita el alcance si alguna de las llaves llega a comprometerse y también hace mucho más sencillo revocar o reemplazar el acceso a un servidor específico.

Y desde mi perspectiva, conectarme sigue siendo igual de sencillo:

ssh production
ssh staging
ssh personal-server

También podemos utilizar archivos .pem

IdentityFile no está limitado a las llaves que normalmente encontramos dentro de ~/.ssh, como id_rsa o id_ed25519. También podemos utilizar archivos .pem.

Un ejemplo bastante común es AWS EC2.

Cuando creamos un key pair para conectarnos a una instancia EC2, AWS nos permite descargar la llave privada como un archivo .pem.

Normalmente podríamos conectarnos a una instancia con un comando parecido a este:

ssh -i ~/.ssh/my-ec2-server.pem ubuntu@203.0.113.10

El usuario dependerá de la imagen que estemos utilizando. En este ejemplo estamos asumiendo una instancia basada en Ubuntu.

Con ~/.ssh/config podemos mover toda esa información a nuestra configuración:

Host my-ec2-server
    HostName 203.0.113.10
    User ubuntu
    IdentityFile ~/.ssh/my-ec2-server.pem

Y ahora simplemente podemos ejecutar:

ssh my-ec2-server

Para SSH, la extensión .pem no es lo realmente importante. En este caso, el archivo sigue conteniendo nuestra llave privada y IdentityFile simplemente indica dónde encontrarla.

Podemos incluso mezclar diferentes tipos de archivos en nuestra configuración:

Host personal-server
    HostName 192.168.1.100
    User deploy
    IdentityFile ~/.ssh/personal-server

Host aws-production
    HostName 203.0.113.10
    User ubuntu
    IdentityFile ~/.ssh/aws-production.pem

Y conectarnos de la misma forma:

ssh personal-server
ssh aws-production

Un detalle importante con los archivos .pem que descargamos de AWS es asegurarnos de que tengan permisos suficientemente restrictivos:

chmod 400 ~/.ssh/aws-production.pem

De lo contrario, SSH puede rechazar nuestra llave privada por tener permisos demasiado abiertos.

No solamente funciona con ssh

Otra ventaja que no tenía con mis aliases de .bashrc es que esta configuración no solamente sirve cuando ejecuto directamente el comando ssh.

Mis aliases solamente existían dentro de mi shell. Pero si quería copiar un archivo utilizando scp, nuevamente tenía que escribir toda la conexión:

scp -i ~/.ssh/id_rsa ./backup.sql ubuntu@192.168.1.100:/tmp/backup.sql

Con ~/.ssh/config, scp puede utilizar el mismo host:

scp ./backup.sql production:/tmp/backup.sql

Lo mismo ocurre con rsync:

rsync -av ./dist/ production:/var/www/app/

Y con otras herramientas que utilizan SSH por debajo.

La configuración deja de ser simplemente un atajo para escribir menos en la terminal y pasa a convertirse en un lugar centralizado donde definir cómo conectarme a cada servidor.

Configuración compartida entre varios servidores

SSH también permite evitar repetir configuraciones cuando varios servidores comparten ciertas propiedades.

Por ejemplo, si production y staging utilizan el mismo usuario:

Host production staging
    User ubuntu

Host production
    HostName 192.168.1.100
    IdentityFile ~/.ssh/production

Host staging
    HostName 192.168.1.200
    IdentityFile ~/.ssh/staging

También podemos utilizar wildcards:

Host *.company
    User ubuntu

Host api.company
    HostName 192.168.1.100
    IdentityFile ~/.ssh/api

Host worker.company
    HostName 192.168.1.200
    IdentityFile ~/.ssh/worker

Y conectarnos normalmente:

ssh api.company

Esto puede ser especialmente útil cuando empezamos a manejar una mayor cantidad de servidores.

Estaba solucionando el problema en la capa equivocada

Lo curioso es que inicialmente no estaba buscando una mejor forma de utilizar SSH. Solamente quería sacar las IPs de mis servidores de mi .bashrc para evitar que terminaran publicadas junto con mis dotfiles.

Pero buscando una solución terminé descubriendo ~/.ssh/config. Y eso terminó solucionando varios problemas al mismo tiempo.

Pasé de tener:

alias production="ssh -i ~/.ssh/id_rsa ubuntu@192.168.1.100"

a simplemente:

ssh production

Mis conexiones dejaron de estar mezcladas con la configuración de mi shell, pude empezar a utilizar llaves diferentes para cada servidor y herramientas como scp o rsync pudieron reutilizar exactamente la misma configuración.

Al final, mis aliases de .bashrc eran básicamente una versión casera y mucho más limitada de algo que SSH ya tenía resuelto.

Y todo empezó porque no quería subir unas IPs a mi repositorio de dotfiles.