SSH: archivo config y port forwarding

El cliente SSH acepta decenas de opciones en la línea de comandos, pero escribir ssh -i ~/.ssh/clave_proyecto -p 2222 -l deploy 203.0.113.45 cada vez que necesitas conectarte a un servidor es una receta para errores y frustración. El archivo de configuración del cliente ~/.ssh/config resuelve eso: asocia un alias corto a toda la configuración necesaria para un host concreto, de modo que ssh proyecto hace exactamente lo mismo que ese comando largo.

El mecanismo es sencillo. Cuando ejecutas ssh <destino>, el cliente lee ~/.ssh/config de arriba abajo y aplica el primer bloque Host cuyo patrón coincida con lo que escribiste. Después sigue leyendo y aplica también cualquier otro bloque que coincida —incluyendo Host *—, pero la primera definición de cada opción gana. Por eso el orden importa: los bloques específicos van antes, y Host * al final como fallback global.

El momento de configurar este archivo es desde el principio, no cuando ya tienes diez servidores sin documentar. Si un proyecto tiene un servidor de base de datos al que solo accedes a través de un jump host, o si mantienes varias claves para diferentes clientes, ~/.ssh/config es donde esa complejidad desaparece de tu flujo de trabajo diario.

Si lo haces mal, las consecuencias van desde inocuas (alias que no funcionan porque el nombre de opción tiene un typo) hasta problemáticas: un IdentityFile incorrecto hace que el agente pruebe todas las claves disponibles antes de fallar, lo que en servidores con MaxAuthTries bajo puede bloquearte. Un StrictHostKeyChecking no puesto por comodidad en Host * deja abierta la puerta a ataques de hombre en el medio.


El port forwarding es otra función del cliente SSH que merece atención propia. Cuando estableces un túnel, SSH negocia con el servidor remoto que el tráfico que llega a un puerto determinado se reenvíe a otro destino, todo cifrado dentro de la misma conexión SSH. El caso más frecuente es el túnel local (-L): abres un puerto en tu máquina local que el cliente SSH conecta al puerto de un servicio en la red del servidor remoto. Desde tu perspectiva, ese servicio parece estar corriendo en localhost. El túnel inverso (-R) hace lo contrario: expone un puerto de tu máquina local en el servidor remoto. Y el proxy SOCKS (-D) convierte la conexión SSH en un proxy dinámico que enruta tráfico arbitrario.

# ── ~/.ssh/config ────────────────────────────────────────────────
# Permisos correctos; sin esto, el cliente SSH ignora el archivo:
#   chmod 600 ~/.ssh/config

# Bloque para el servidor de producción principal
Host prod
    HostName       203.0.113.45
    User           deploy
    Port           2222
    IdentityFile   ~/.ssh/id_ed25519_prod

# Base de datos de staging, accesible solo desde dentro de la VPN/red interna.
# Nos conectamos a través de prod como jump host.
Host db-staging
    HostName       10.10.5.20
    User           postgres
    Port           22
    IdentityFile   ~/.ssh/id_ed25519_staging
    ProxyJump      prod

# Configuración por defecto para cualquier host no definido arriba.
# ServerAliveInterval envía un paquete nulo cada 60 s para evitar
# que firewalls o NAT cierren la conexión por inactividad.
Host *
    ServerAliveInterval    60
    ServerAliveCountMax    3
    # ControlMaster auto + ControlPath: multiplexado de conexiones.
    # La primera conexión a un host crea el socket de control;
    # las siguientes la reutilizan sin nueva negociación TLS/auth.
    ControlMaster          auto
    ControlPath            ~/.ssh/cm_%r@%h:%p
    ControlPersist         10m
    AddKeysToAgent         yes

# ── Conectarse con los alias definidos ───────────────────────────
# En lugar de: ssh -i ~/.ssh/id_ed25519_prod -p 2222 deploy@203.0.113.45
#   ssh prod

# En lugar de saltar manualmente por prod:
#   ssh db-staging

# ── Túnel local: PostgreSQL remoto como si fuera local ───────────
# Mapea localhost:5432 → 10.10.5.20:5432 a través del túnel SSH.
# -N: no ejecutes ningún comando remoto, solo mantén el túnel abierto.
# -f: envía el proceso al fondo (background).
ssh -N -f -L 5432:10.10.5.20:5432 prod

# Ahora puedes conectarte desde tu máquina como si la BD fuera local:
#   psql -h localhost -U postgres -d miapp

# ── Túnel inverso: expón un servicio local en el servidor remoto ──
# Cualquiera en prod que conecte a localhost:8080 llega a
# tu máquina local en el puerto 3000 (útil para demos o webhooks).
# GatewayPorts no está activo por defecto en sshd, así que el puerto
# en el servidor solo es accesible desde localhost del propio servidor.
ssh -N -f -R 8080:localhost:3000 prod

# ── Proxy SOCKS dinámico ─────────────────────────────────────────
# -D 1080 crea un proxy SOCKS5 en tu localhost:1080.
# Configura tu navegador o aplicación para usarlo y todo el tráfico
# saldrá cifrado a través de prod.
ssh -N -f -D 1080 prod

Lo que está pasando en cada decisión

El bloque Host prod le dice al cliente que cuando escribas ssh prod, use 203.0.113.45 como destino real, se autentique como deploy, conecte al puerto 2222 y use solo esa clave específica. Sin el IdentityFile explícito, el agente SSH intentaría todas las claves que tenga cargadas, lo cual además de lento puede causar bloqueos en servidores estrictos.

El bloque Host db-staging usa ProxyJump prod, que es la forma moderna de lo que antes se hacía con ProxyCommand y ssh -W. Lo que ocurre internamente es que el cliente establece primero la conexión SSH a prod y luego, dentro de esa sesión, abre un canal directo hacia 10.10.5.20:22. El resultado es que tu máquina nunca tiene conexión TCP directa con la base de datos; toda la ruta está cifrada y mediada por el jump host.

En Host *, ServerAliveInterval 60 y ServerAliveCountMax 3 trabajan juntos: el cliente envía un paquete SSH_MSG_GLOBAL_REQUEST vacío cada 60 segundos; si el servidor no responde después de tres intentos (180 s), la conexión se cierra limpiamente. Esto evita que termines con sesiones zombie que el OS considera abiertas pero que en realidad están muertas porque un firewall eliminó el estado NAT hace diez minutos.

El trío ControlMaster auto, ControlPath y ControlPersist implementa multiplexado de conexiones. El socket que se crea en ~/.ssh/cm_deploy@203.0.113.45:2222 es literalmente un socket Unix que el primer cliente crea y los siguientes clientes reutilizan. Cuando ejecutas scp o rsync hacia prod mientras ya tienes una sesión abierta, no hay nueva negociación de clave ni autenticación: el canal está listo en milisegundos. ControlPersist 10m mantiene ese socket vivo diez minutos después de que cierres la última sesión visible, así las conexiones seguidas siguen siendo baratas.

En el túnel local ssh -N -f -L 5432:10.10.5.20:5432 prod, el formato -L local_port:destino_host:destino_port define tres cosas: el puerto que el cliente SSH abre en tu máquina (5432), el host al que el servidor SSH remoto conectará (10.10.5.20, que puede ser diferente del propio servidor SSH), y el puerto de destino. El servidor SSH actúa de proxy TCP en ese segundo salto: él puede alcanzar 10.10.5.20 aunque tú no puedas. La opción -f manda el proceso al fondo después de autenticarse, y -N evita abrir una shell remota. Sin -N, el proceso también abriría una sesión interactiva que no necesitas y consumiría recursos.

El túnel inverso ssh -R 8080:localhost:3000 prod funciona en dirección contraria: el demonio sshd en prod abre un socket en su propio localhost:8080 y reenvía las conexiones que lleguen ahí a localhost:3000 de tu máquina local. La restricción importante: por defecto sshd solo vincula ese puerto en 127.0.0.1 del servidor, no en su interfaz pública. Si necesitas que sea accesible desde fuera del servidor, el administrador debe poner GatewayPorts yes en /etc/ssh/sshd_config.

83

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Scroll al inicio