
Enrutador SSH consciente de la red - enruta conexiones a diferentes IPs/puertos/claves/hosts de salto según la VPN o red activa
Enrutador SSH consciente de la red. Detecta tu red activa o VPN y selecciona automáticamente el host, puerto, archivo de identidad y host de salto correctos para cada conexión SSH, sin tocar ~/.ssh/config.
Define cada host lógico una vez con un perfil default anidado y opcionales anulaciones por red. En cada conexión, sshroute detecta en qué red estás (VPN, LAN de oficina, par WireGuard, etc.) y resuelve los parámetros SSH correctos antes de pasar el control al /usr/bin/ssh real.
ssh myserver
→ sshroute detecta: corp-vpn está activa
→ resuelve: 10.100.0.50:2222 vía bastion.corp.internal
→ exec /usr/bin/ssh -p 2222 -i ~/.ssh/corp_key -J bastion.corp.internal 10.100.0.50
Tu laboratorio probablemente tiene al menos dos realidades: o estás en casa en la LAN, o estás fuera y te conectas a través de WireGuard u otra VPN. El problema es que ~/.ssh/config no sabe en cuál estás, así que terminas con alias separados (server-lan, server-vpn), o un host de salto que solo funciona la mitad del tiempo, o simplemente memorizas direcciones IP.
sshroute soluciona esto detectando tu red actual antes de cada conexión. Cuando la interfaz WireGuard está activa y la ruta del par existe, se conecta directamente a la IP del túnel. Cuando estás en la LAN, usa la dirección local. Cuando ninguna es alcanzable, recurre al nombre de host público. Un alias, tres realidades, cero cambios manuales.
También intercepta SSH de manera transparente: git push, rsync, scp — todo pasa a través de él automáticamente una vez que configuras el modo sombra. Sin envoltorios, sin funciones de shell, sin pensar.
Las redes empresariales son peores. Tienes la internet pública, tal vez una VPN sitio a sitio, tal vez una VPN personal con split-tunnel, y dentro de eso diferentes hosts de salto según el entorno al que apuntes — desarrollo, staging, producción, cada uno con su bastión y clave. Mantener esto en orden en ~/.ssh/config significa o un archivo enorme que se rompe cada vez que cambia la infraestructura, o escribir un script que todos en el equipo mantienen de manera diferente.
sshroute te permite definir la lógica de enrutamiento de forma declarativa, mantenerla en un archivo YAML versionado y compartirla con el equipo. La misma configuración funciona para todos: la red correcta se detecta automáticamente según qué interfaces o rutas están activas en cada máquina. Las claves, puertos, usuarios y hosts de salto se resuelven sin que el usuario tenga que pensar en ello.
Teleport y Boundary son una categoría diferente: añaden control de acceso, registros de auditoría y autenticación basada en certificados sobre el enrutamiento. Si eso es lo que necesitas, úsalos. sshroute es para cuando quieres la inteligencia de enrutamiento sin la sobrecarga operativa de ejecutar un servidor de autenticación central.
Descarga la última versión desde GitHub Releases. Hay binarios disponibles para Linux, macOS y Android en AMD64 y ARM64.
go install github.com/thereisnotime/sshroute@latest
Descarga el tarball android_arm64 desde GitHub Releases, extráelo y coloca el binario en ~/.local/bin:
mkdir -p ~/.local/bin
curl -Lo "$TMPDIR/sshroute.tar.gz" \
https://github.com/thereisnotime/sshroute/releases/latest/download/sshroute_android_arm64.tar.gz
tar -xzf "$TMPDIR/sshroute.tar.gz" -C ~/.local/bin sshroute
chmod +x ~/.local/bin/sshroute
Añade ~/.local/bin a tu PATH en ~/.bashrc o ~/.profile si aún no está:
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
Alternativamente, compila desde el código fuente con Go de Termux. Debido a que el toolchain oficial de Go no publica binarios android/arm64, configura GOTOOLCHAIN=local para usar el que proporciona Termux:
GOTOOLCHAIN=local go install github.com/thereisnotime/sshroute@latest
Después de instalar, establece la ruta del binario SSH, ya que Termux no tiene /usr/bin/ssh:
# ~/.config/sshroute/config.yaml
ssh_binary: /data/data/com.termux/files/usr/bin/ssh
O mediante una variable de entorno: export SSHROUTE_SSH=$(which ssh)
docker run --rm -v ~/.config/sshroute:/root/.config/sshroute \
ghcr.io/thereisnotime/sshroute network
podman run --rm -v ~/.config/sshroute:/root/.config/sshroute \
ghcr.io/thereisnotime/sshroute network
En sistemas con SELinux habilitado (Fedora, RHEL, etc.) añade :Z al flag de volumen:
podman run --rm -v ~/.config/sshroute:/root/.config/sshroute:Z \
ghcr.io/thereisnotime/sshroute network
Instala sshroute como ssh antes en tu $PATH. Todas las llamadas SSH — desde tu terminal, git, rsync, scp — son interceptadas automáticamente. Los hosts que no estén en tu configuración pasan a /usr/bin/ssh sin cambios.
mkdir -p ~/.local/bin
ln -s $(which sshroute) ~/.local/bin/ssh
# Añade a ~/.bashrc o ~/.zshrc si aún no está:
export PATH="$HOME/.local/bin:$PATH"
# Añade un host con un perfil por defecto
sshroute add myserver --host myserver.example.com --user alice --key ~/.ssh/id_ed25519
# Añade una anulación específica para VPN
sshroute add myserver --network vpn --host 10.8.0.50 --port 2222 --jump bastion.vpn
# Conéctate — la red se detecta automáticamente
sshroute connect myserver
# Previsualiza el comando resuelto sin ejecutarlo
sshroute connect myserver --dry-run
# Ve qué red está activa actualmente
sshroute network
Estos flags aplican a todos los comandos:
initCrea un archivo de configuración inicial con ejemplos comentados. Falla si el archivo ya existe.
| Flag | Valor por defecto | Descripción |
|---|---|---|
--force | false | Sobrescribe un archivo de configuración existente |
connect <alias>Detecta la red activa, resuelve los parámetros SSH para alias y ejecuta el binario SSH real. Cualquier argumento extra después del alias se pasa a SSH sin cambios.
Con --reconnect, sshroute mantiene ssh activo a través de caídas de conexión (suspensión del portátil, traspaso WiFi, itinerancia entre redes). Como redetecta la red en cada reconexión, te sigue a una ruta diferente: por ejemplo, dormir en la LAN y despertar en un punto de acceso público se reconecta por la ruta pública en lugar de reintentar la dirección LAN ahora inalcanzable. Un cierre de sesión limpio (código de salida 0) o un fallo de autenticación/comando remoto detienen el bucle; solo las caídas genuinas de conexión provocan reconexión. La reconexión ejecuta ssh como un subproceso (como --fallback), por lo que sshroute permanece residente durante la sesión; SIGINT/SIGTERM lo derriba. El estado de la sesión a través del parpadeo es trabajo de tu multiplexor (tmux/zellij); combina --reconnect con -- tmux attach o -- zellij attach -c <nombre> para volver directamente a tu sesión:
sshroute connect myserver --reconnect --fallback -- zellij attach -c work
listLista todos los hosts configurados y los parámetros SSH que se usarían en la red actual. Soporta -o table|json|yaml.
add <alias>Añade un host o actualiza uno existente. Los flags omitidos mantienen su valor actual. Ejecuta varias veces con diferentes valores de --network para construir anulaciones por red.
remove <alias>Elimina todos los perfiles de alias de la configuración.
networkImprime el nombre de la red detectada actualmente (o default si ninguna coincide).
network listLista todas las redes configuradas con su prioridad, reglas de comprobación y estado activo actual. Soporta -o table|json|yaml.
network test <name>Ejecuta cada comprobación de la red name e imprime aprobado/fallido por regla. Útil para depurar la lógica de detección.
configImprime la ruta resuelta al archivo de configuración.
config editAbre el archivo de configuración en $EDITOR (si no está definido, usa nano). Crea el archivo y su directorio padre si no existen.
resolve <alias>Imprime los parámetros SSH que se usarían para alias en la red actual. Útil para depuración y scripting. Usa --network <name> para anular la red detectada. Soporta -o table|json|yaml.
| Flag | Valor por defecto | Descripción |
|---|---|---|
--network | detección automática | Perfil de red contra el que resolver |
copy <alias> <src> <dst>Copia archivos hacia o desde un host configurado usando scp con los mismos parámetros resueltos (clave, puerto, salto) que connect. Usa la sintaxis <alias>:<ruta> para rutas remotas:
sshroute copy myserver ./local.txt myserver:/remote/path/
sshroute copy myserver myserver:/remote/file.txt ./local/
La variable de entorno SSHROUTE_SCP anula el binario scp utilizado.
versionImprime la versión, el commit de Git, la fecha de compilación y la información del runtime Go.
updateActualiza sshroute in situ a la última versión de GitHub. Descarga el archivo para tu plataforma, verifica su sha256 contra checksums.txt y — si cosign está instalado — verifica la firma cosign del lanzamiento, antes de reemplazar atómicamente el binario en ejecución.
sshroute update # descarga, verifica e instala la última versión
sshroute update --check # solo informa si hay una versión más reciente disponible
sshroute update --force # reinstala la última versión incluso si ya está actualizado
Si la verificación sha256 (o cosign, cuando está presente) falla, la actualización se cancela y el binario no se modifica. Esto está pensado para instalaciones del binario de lanzamiento; si instalaste mediante go install o un gestor de paquetes, actualiza con ese método.
Ubicación por defecto: ~/.config/sshroute/config.yaml
networks:
corp-vpn:
priority: 10 # lower = checked first
checks:
- type: interface
match: wg0
- type: route
match: 10.100.0.0
office:
priority: 20
checks:
- type: ping
host: 192.168.1.1
timeout: 500ms
hosts:
myserver:
default: # required — used when no network matches
host: myserver.example.com
port: 22
user: alice
key: ~/.ssh/id_ed25519
options: # optional — passed as SSH -o Key=Value flags
ConnectTimeout: "10"
ServerAliveInterval: "30"
corp-vpn:
host: 10.100.0.50
port: 2222
key: ~/.ssh/corp_key
jump: bastion.corp.internal
options:
ConnectTimeout: "5" # overrides default for this network only
office:
host: 192.168.1.50
Todo host debe tener un perfil default. Los perfiles de red solo necesitan especificar los campos que difieren del valor por defecto: los campos no establecidos heredan de default.
Las claves de options se fusionan desde default hacia los perfiles de red: los valores de red anulan las claves coincidentes, las claves no superpuestas se heredan.
Las redes se evalúan en orden de priority (el valor más bajo primero). El orden alfabético desempata. Se usa la primera red cuyas comprobaciones pasen todas; si ninguna coincide, se aplica default.
Múltiples comprobaciones dentro de una misma definición de red usan lógica AND — todas deben pasar.
Archivos de configuración listos para usar en examples/:
Guías detalladas en docs/:
Todos los comandos de lista soportan múltiples formatos de salida:
sshroute list # table (por defecto)
sshroute list -o json # JSON — para scripting
sshroute list -o yaml # YAML
sshroute network list -o json
Obtén el software — descarga un binario precompilado desde Releases, instálalo con go install github.com/thereisnotime/sshroute@latest, o compila desde el código fuente.
Comentarios e informes de errores — abre un issue en GitHub Issues. Usa la plantilla de informe de errores para comportamientos inesperados y la plantilla de solicitud de características para ideas.
Contribuciones — consulta CONTRIBUTING.md para saber cómo configurar el proyecto, ejecutar pruebas y abrir un pull request. Las vulnerabilidades de seguridad deben notificarse de forma privada a través de GitHub Security Advisories.
git clone [email protected]:thereisnotime/sshroute.git
cd sshroute
just build # outputs bin/sshroute
just build-all # cross-compile linux/darwin × amd64/arm64
just test # run tests with race detector
just install # go install with version ldflags injected
|
|
| Característica | ~/.ssh/config | Solo WireGuard | Teleport / Boundary | sshroute |
|---|
| Detecta tu red actual | ❌ | ❌ | ❌ | ✅ |
| Elige la mejor ruta automáticamente | ❌ | ❌ | ❌ | ✅ |
| Reintenta en caso de fallo de conexión | ❌ | ❌ | ✅ | ✅ |
| Reconexión automática + reenrutamiento al caer | ❌ | ⚠️ el túnel vaga | ⚠️ a través de proxy fijo | ✅ |
| Un comando por host, desde cualquier ubicación | ❌ | ⚠️ VPN debe estar activa | ✅ | ✅ |
| Tamaño de configuración para 10 hosts × 4 rutas | 📄 ~600 líneas | 📄 ~600 líneas + config VPN | 📄 configuración del lado servidor | 📄 ~60 líneas |
| Dispositivos móviles en itinerancia | ⚠️ alias manuales | ⚠️ VPN requerida | ✅ | ✅ |
| Encadenamiento automático de host de salto | ⚠️ manual -J | ➖ n/a | ✅ | ✅ |
| Funciona con scp / rsync / git / Ansible | ✅ | ✅ | ⚠️ parcial | ✅ |
| Sin instalación en el servidor en los destinos | ✅ | ❌ | ❌ | ✅ |
| Sin servidor de autenticación ni demonio | ✅ | ❌ | ❌ | ✅ |
| Sin agente cliente | ✅ | ❌ | ❌ | ✅ |
| Código abierto, completamente autogestionado | ✅ | ✅ | ⚠️ open-core | ✅ |
| Flag | Variable de entorno | Valor por defecto | Descripción |
|---|
--config | SSHROUTE_CONFIG | ~/.config/sshroute/config.yaml | Ruta del archivo de configuración |
-o, --output | table | Formato de salida: table, json, yaml | |
-v, --verbose | SSHROUTE_VERBOSE=1 | false | Registro de depuración en stderr |
--dry-run | false | Imprime el comando SSH resuelto sin ejecutarlo |
| Flag | Valor por defecto | Descripción |
|---|
--fallback | false | Intenta cada perfil en orden de prioridad, reintentando el siguiente solo si falla la conexión (código de salida 255) |
--reconnect | false | Supervisa la conexión y se reconecta automáticamente cuando se cae, redetectando la red activa y recalculando la ruta cada vez |
--reconnect-delay | 2s | Espera entre intentos de reconexión cuando se usa --reconnect |
| Flag | Valor por defecto | Descripción |
|---|
--host | Nombre de host o dirección IP | |
--port | 22 | Puerto SSH |
--user | Usuario SSH | |
--key | Ruta al archivo de identidad (soporta ~) | |
--jump | Host de salto — se pasa como -J a SSH | |
--network | default | Perfil de red en el que escribir los parámetros |
| Campo | Tipo | Descripción |
|---|
host | string | Nombre de host o dirección IP |
port | int | Puerto SSH (por defecto: 22) |
user | string | Usuario SSH |
key | string | Ruta al archivo de identidad (se expande ~) |
jump | string | Alias de host de salto o user@host |
options | map | Flags arbitrarios SSH -o Key=Value (ej. ConnectTimeout, StrictHostKeyChecking) |
comment | string | Descripción mostrada en sshroute list |
tags | list | Etiquetas para filtrar con sshroute list --tag |
| Tipo de comprobación | Pasa cuando | Campos requeridos |
|---|
route | La subred/IP aparece en la tabla de enrutamiento del kernel | match |
interface | La interfaz nombrada existe y está operativamente activa | match |
ping | El host responde al eco ICMP dentro del tiempo de espera | host, timeout (opcional, por defecto 2s) |
exec | El comando de shell termina con código 0 | command |
| Archivo | Caso de uso |
|---|
basic.yaml | Host único, respaldo VPN vs público |
multi-network.yaml | LAN de oficina, VPN corporativa, VPN remota, público |
wireguard-backconnect.yaml | Par WireGuard que se reconecta contigo |
jump-hosts.yaml | Diferentes bastiones por red |
multi-zone-roaming.yaml | Homelab multi-zona con gateway WireGuard y dispositivos móviles en itinerancia |
| Guía | Descripción |
|---|
| Configuración de homelab | Homelab multi-zona con WireGuard, hosts de salto, NAS, nodos k3s |
| Itinerancia multi-zona | Múltiples LANs, gateway WireGuard, dispositivos móviles que se mueven entre redes |
| Entorno corporativo / multi-entorno | Desarrollo/staging/producción con bastiones por entorno y detección de VPN |
| Modo sombra | Reemplazo transparente de SSH — git, rsync, scp, Ansible |
| Finalización de shell | Finalización dinámica de alias para bash, zsh, fish |
| Scripting y automatización | Uso de resolve y copy en scripts y pipelines CI |