
Roteador SSH consciente da rede - roteia conexões para diferentes IPs/portas/chaves/hosts de salto com base na VPN ou rede ativa
# sshroute
<table>
<tr>
<th>CI</th>
<th>Código</th>
<th>OpenSpec</th>
<th>Segurança</th>
</tr>
<tr>
<td>
<a href="https://github.com/thereisnotime/sshroute/actions/workflows/ci.yaml"><img src="https://github.com/thereisnotime/sshroute/actions/workflows/ci.yaml/badge.svg" alt="CI"></a><br>
<a href="https://github.com/thereisnotime/sshroute/actions/workflows/release.yaml"><img src="https://github.com/thereisnotime/sshroute/actions/workflows/release.yaml/badge.svg" alt="Release"></a><br>
<a href="https://github.com/thereisnotime/sshroute/actions/workflows/openspec-badge.yaml"><img src="https://github.com/thereisnotime/sshroute/actions/workflows/openspec-badge.yaml/badge.svg" alt="OpenSpec Badge"></a><br>
<a href="https://github.com/thereisnotime/sshroute/actions/workflows/scorecard.yaml"><img src="https://github.com/thereisnotime/sshroute/actions/workflows/scorecard.yaml/badge.svg" alt="Scorecard"></a>
</td>
<td>
<a href="https://github.com/thereisnotime/sshroute/releases/latest"><img src="https://img.shields.io/github/v/release/thereisnotime/sshroute" alt="Latest Release"></a><br>
<a href="https://codecov.io/gh/thereisnotime/sshroute"><img src="https://codecov.io/gh/thereisnotime/sshroute/branch/main/graph/badge.svg" alt="codecov"></a><br>
<a href="https://goreportcard.com/report/github.com/thereisnotime/sshroute"><img src="https://goreportcard.com/badge/github.com/thereisnotime/sshroute" alt="Go Report Card"></a><br>
<a href="https://pkg.go.dev/github.com/thereisnotime/sshroute"><img src="https://pkg.go.dev/badge/github.com/thereisnotime/sshroute.svg" alt="Go Reference"></a>
</td>
<td>
<a href="https://github.com/thereisnotime/sshroute/blob/main/openspec/specs"><img src="https://raw.githubusercontent.com/thereisnotime/sshroute/gh-pages/badges/number_of_specs.svg" alt="Specs"></a><br>
<a href="https://github.com/thereisnotime/sshroute/blob/main/openspec/specs"><img src="https://raw.githubusercontent.com/thereisnotime/sshroute/gh-pages/badges/number_of_requirements.svg" alt="Requirements"></a><br>
<a href="https://github.com/thereisnotime/sshroute/blob/main/openspec/changes"><img src="https://raw.githubusercontent.com/thereisnotime/sshroute/gh-pages/badges/tasks_status.svg" alt="Tasks"></a><br>
<a href="https://github.com/thereisnotime/sshroute/blob/main/openspec/changes"><img src="https://raw.githubusercontent.com/thereisnotime/sshroute/gh-pages/badges/open_changes.svg" alt="Open Changes"></a>
</td>
<td>
<a href="https://scorecard.dev/viewer/?uri=github.com/thereisnotime/sshroute"><img src="https://api.scorecard.dev/projects/github.com/thereisnotime/sshroute/badge" alt="OpenSSF Scorecard"></a><br>
<a href="https://www.bestpractices.dev/projects/12389"><img src="https://www.bestpractices.dev/projects/12389/badge" alt="CII Best Practices"></a><br>
<a href="https://github.com/thereisnotime/sshroute/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-Apache%202.0-blue.svg" alt="License: Apache 2.0"></a>
</td>
</tr>
</table>
Roteador SSH ciente de rede. Detecta sua rede ativa ou VPN e seleciona automaticamente o host, porta, arquivo de identidade e host de salto corretos para cada conexão SSH — sem tocar no `~/.ssh/config`.
## Como funciona
Defina cada host lógico uma vez com um perfil `default` e substituições opcionais por rede. Em cada conexão, o sshroute detecta em qual rede você está (VPN, LAN do escritório, peer WireGuard, etc.) e resolve os parâmetros SSH corretos antes de passar para o `/usr/bin/ssh` real.
```
ssh myserver
→ sshroute detecta: corp-vpn está ativo
→ resolve: 10.100.0.50:2222 via bastion.corp.internal
→ exec /usr/bin/ssh -p 2222 -i ~/.ssh/corp_key -J bastion.corp.internal 10.100.0.50
```
## Por que sshroute?
### Para donos de homelab
Seu laboratório provavelmente tem pelo menos duas realidades: você está em casa na LAN, ou está fora e acessa via WireGuard ou outra VPN. O problema é que o `~/.ssh/config` não sabe em qual você está — então você acaba com apelidos separados (`server-lan`, `server-vpn`), ou um host de salto que só funciona metade do tempo, ou você simplesmente memoriza IPs.
O sshroute resolve isso detectando sua rede atual antes de cada conexão. Quando a interface WireGuard está ativa e a rota do peer existe, ele conecta diretamente ao IP do túnel. Quando você está na LAN, ele usa o endereço local. Quando nenhum é alcançável, ele recai para o nome de host público. Um apelido, três realidades, zero troca manual.
Ele também intercepta SSH de forma transparente — `git push`, `rsync`, `scp` passam automaticamente por ele assim que você configura o modo sombra. Sem wrappers, sem funções de shell, sem pensar.
### Para ambientes corporativos
Redes empresariais são piores. Você tem a internet pública, talvez uma VPN site-to-site, talvez uma VPN pessoal com split-tunnel, e dentro disso você tem diferentes hosts de salto dependendo de qual ambiente está mirando — dev, staging, produção, cada um com seu bastião e chave. Manter isso correto no `~/.ssh/config` significa ou um arquivo enorme que quebra quando a infra muda, ou você escreve um script que cada pessoa da equipe mantém de forma diferente.
O sshroute permite que você defina a lógica de roteamento de forma declarativa, mantenha-a em um arquivo YAML versionado e compartilhe com a equipe. A mesma configuração funciona para todos — a rede correta é detectada automaticamente com base em quais interfaces ou rotas estão ativas em cada máquina. Chaves, portas, usuários e hosts de salto são resolvidos sem que o usuário precise pensar nisso.
## Como se compara
| Funcionalidade | `~/.ssh/config` | Somente WireGuard | Teleport / Boundary | sshroute |
|---|---|---|---|---|
| Detecta sua rede atual | ❌ | ❌ | ❌ | ✅ |
| Escolhe o melhor caminho automaticamente | ❌ | ❌ | ❌ | ✅ |
| Recai em caso de falha de conexão | ❌ | ❌ | ✅ | ✅ |
| Reconexão automática + re-roteamento ao cair | ❌ | ⚠️ túnel faz roaming | ⚠️ via proxy fixo | ✅ |
| Um comando por host, qualquer local | ❌ | ⚠️ VPN precisa estar ativa | ✅ | ✅ |
| Tamanho da config para 10 hosts × 4 caminhos | 📄 ~600 linhas | 📄 ~600 linhas + config VPN | 📄 config no servidor | 📄 ~60 linhas |
| Dispositivos móveis em roaming | ⚠️ apelidos manuais | ⚠️ VPN necessária | ✅ | ✅ |
| Encadeamento automático de host de salto | ⚠️ `-J` manual | ➖ n/a | ✅ | ✅ |
| Funciona com scp / rsync / git / Ansible | ✅ | ✅ | ⚠️ parcial | ✅ |
| Sem instalação no servidor nos alvos | ✅ | ❌ | ❌ | ✅ |
| Sem servidor de autenticação ou daemon para executar | ✅ | ❌ | ❌ | ✅ |
| Sem agente cliente | ✅ | ❌ | ❌ | ✅ |
| Código aberto, totalmente auto-hospedado | ✅ | ✅ | ⚠️ open-core | ✅ |
Teleport e Boundary são uma categoria diferente — eles adicionam controle de acesso, logs de auditoria e autenticação baseada em certificado sobre o roteamento. Se é isso que você precisa, use-os. O sshroute é para quando você quer a inteligência de roteamento sem a sobrecarga operacional de executar um servidor de autenticação central.
## Instalação
### Download do binário
Baixe a versão mais recente em [GitHub Releases](https://github.com/thereisnotime/sshroute/releases). Binários estão disponíveis para Linux, macOS e Android em AMD64 e ARM64.
### Instalação via Go
```sh
go install github.com/thereisnotime/sshroute@latest
```
### Android (Termux)
Baixe o pacote `android_arm64` do [GitHub Releases](https://github.com/thereisnotime/sshroute/releases), extraia e coloque o binário em `~/.local/bin`:
```sh
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
```
Adicione `~/.local/bin` ao seu `PATH` no `~/.bashrc` ou `~/.profile` se ainda não estiver:
```sh
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
```
Alternativamente, compile a partir do código-fonte com o Go do Termux. Como a toolchain oficial do Go não publica binários android/arm64, defina `GOTOOLCHAIN=local` para usar o que o Termux fornece:
```sh
GOTOOLCHAIN=local go install github.com/thereisnotime/sshroute@latest
```
Após instalar, defina o caminho do binário SSH, já que o Termux não tem `/usr/bin/ssh`:
```yaml
# ~/.config/sshroute/config.yaml
ssh_binary: /data/data/com.termux/files/usr/bin/ssh
```
Ou via variável de ambiente: `export SSHROUTE_SSH=$(which ssh)`
### Docker
```sh
docker run --rm -v ~/.config/sshroute:/root/.config/sshroute \
ghcr.io/thereisnotime/sshroute network
```
### Podman
```sh
podman run --rm -v ~/.config/sshroute:/root/.config/sshroute \
ghcr.io/thereisnotime/sshroute network
```
Em sistemas com SELinux ativado (Fedora, RHEL, etc.) adicione `:Z` ao flag de volume:
```sh
podman run --rm -v ~/.config/sshroute:/root/.config/sshroute:Z \
ghcr.io/thereisnotime/sshroute network
```
### Modo sombra (substituto transparente do SSH)
Instale o sshroute como `ssh` antes no seu `$PATH`. Todas as chamadas SSH — do seu terminal, `git`, `rsync`, `scp` — são interceptadas automaticamente. Hosts que não estão na sua configuração passam para o `/usr/bin/ssh` sem alterações.
```sh
mkdir -p ~/.local/bin
ln -s $(which sshroute) ~/.local/bin/ssh
# Adicione ao ~/.bashrc ou ~/.zshrc se ainda não estiver:
export PATH="$HOME/.local/bin:$PATH"
```
## Início rápido
```sh
# Adicione um host com um perfil padrão
sshroute add myserver --host myserver.example.com --user alice --key ~/.ssh/id_ed25519
# Adicione uma substituição específica para VPN
sshroute add myserver --network vpn --host 10.8.0.50 --port 2222 --jump bastion.vpn
# Conecte — a rede é detectada automaticamente
sshroute connect myserver
# Visualize o comando resolvido sem executá-lo
sshroute connect myserver --dry-run
# Veja qual rede está ativa no momento
sshroute network
```
## Comandos
### Flags globais
Estas flags se aplicam a todos os comandos:
| Flag | Variável de ambiente | Padrão | Descrição |
|---|---|---|---|
| `--config` | `SSHROUTE_CONFIG` | `~/.config/sshroute/config.yaml` | Caminho do arquivo de configuração |
| `-o, --output` | | `table` | Formato de saída: `table`, `json`, `yaml` |
| `-v, --verbose` | `SSHROUTE_VERBOSE=1` | `false` | Log de depuração no stderr |
| `--dry-run` | | `false` | Imprime o comando SSH resolvido sem executar |
### `init`
Cria um arquivo de configuração inicial com exemplos comentados. Falha se o arquivo já existir.
| Flag | Padrão | Descrição |
|---|---|---|
| `--force` | `false` | Sobrescreve um arquivo de configuração existente |
### `connect <alias>`
Detecta a rede ativa, resolve os parâmetros SSH para `alias` e executa o binário SSH real. Quaisquer argumentos extras após o alias são passados diretamente para o SSH sem alteração.
| Flag | Padrão | Descrição |
|---|---|---|
| `--fallback` | `false` | Tenta cada perfil em ordem de prioridade, repetindo o próximo apenas em caso de falha de conexão (código de saída 255) |
| `--reconnect` | `false` | Supervisiona a conexão e reconecta automaticamente quando ela cai, redetectando a rede ativa e re-resolvendo a rota a cada vez |
| `--reconnect-delay` | `2s` | Tempo de espera entre tentativas de reconexão quando `--reconnect` está definido |
Com `--reconnect`, o sshroute mantém o ssh ativo durante quedas de conexão (suspensão do laptop, handoff de WiFi, roaming entre redes). Como ele redetecta a rede a cada reconexão, ele te segue para uma rota diferente: por exemplo, dormir na LAN e acordar em um hotspot reconecta pela rota pública em vez de tentar novamente o endereço LAN agora inalcançável. Um logout limpo (código 0) ou uma falha de autenticação/comando remoto interrompe o loop; apenas quedas genuínas de conexão reconectam. O reconnect executa o ssh como subprocesso (como `--fallback`), então o sshroute permanece residente durante a sessão; `SIGINT`/`SIGTERM` o derruba. O estado da sessão durante o lapso é responsabilidade do seu multiplexador (tmux/zellij); combine `--reconnect` com `-- tmux attach` ou `-- zellij attach -c <name>` para cair direto na sua sessão:
```sh
sshroute connect myserver --reconnect --fallback -- zellij attach -c work
```
### `list`
Lista todos os hosts configurados e os parâmetros SSH que seriam usados na rede atual. Suporta `-o table|json|yaml`.
### `add <alias>`
Adiciona um host ou atualiza um existente. Flags omitidas mantêm seu valor atual. Execute várias vezes com diferentes valores de `--network` para construir substituições por rede.
| Flag | Padrão | Descrição |
|---|---|---|
| `--host` | | Nome do host ou endereço IP |
| `--port` | `22` | Porta SSH |
| `--user` | | Usuário SSH |
| `--key` | | Caminho para o arquivo de identidade (suporta `~`) |
| `--jump` | | Host de salto — passado como `-J` para o SSH |
| `--network` | `default` | Perfil de rede no qual escrever os parâmetros |
### `remove <alias>`
Remove todos os perfis de `alias` da configuração.
### `network`
Imprime o nome da rede atualmente detectada (ou `default` se nenhuma corresponder).
### `network list`
Lista todas as redes configuradas com sua prioridade, regras de verificação e estado ativo atual. Suporta `-o table|json|yaml`.
### `network test <name>`
Executa cada verificação para a rede `name` e imprime passou/falhou por regra. Útil para depurar a lógica de detecção.
### `config`
Imprime o caminho resolvido para o arquivo de configuração.
### `config edit`
Abre o arquivo de configuração no `$EDITOR` (recai para `nano`). Cria o arquivo e seu diretório pai se não existirem.
### `resolve <alias>`
Imprime os parâmetros SSH que seriam usados para `alias` na rede atual. Útil para depuração e scripts. Use `--network <name>` para sobrescrever a rede detectada. Suporta `-o table|json|yaml`.
| Flag | Padrão | Descrição |
|---|---|---|
| `--network` | detecção automática | Perfil de rede contra o qual resolver |
### `copy <alias> <src> <dst>`
Copia arquivos de ou para um host configurado usando `scp` com os mesmos parâmetros resolvidos (chave, porta, salto) que `connect`. Use a sintaxe `<alias>:<path>` para caminhos remotos:
```sh
sshroute copy myserver ./local.txt myserver:/remote/path/
sshroute copy myserver myserver:/remote/file.txt ./local/
```
A variável de ambiente `SSHROUTE_SCP` substitui o binário `scp` usado.
### `version`
Imprime a versão, commit git, data de compilação e informações do runtime Go.
### `update`
Atualiza o sshroute no local para o último lançamento do GitHub. Ele baixa o arquivo para sua plataforma, verifica seu sha256 em relação ao `checksums.txt` e — se o [`cosign`](https://github.com/sigstore/cosign) estiver instalado — verifica a assinatura cosign do lançamento, antes de substituir atomicamente o binário em execução.
```sh
sshroute update # baixa, verifica e instala o último lançamento
sshroute update --check # apenas informa se uma versão mais nova está disponível
sshroute update --force # reinstala o último mesmo se já estiver atualizado
```
Se a verificação sha256 (ou cosign, quando presente) falhar, a atualização é abortada e o binário permanece intacto. Isso tem como alvo instalações do binário de lançamento; se você instalou via `go install` ou um gerenciador de pacotes, atualize por esses meios.
## Arquivo de configuração
Localização padrão: `~/.config/sshroute/config.yaml`
```yaml
networks:
corp-vpn:
priority: 10 # menor = verificado primeiro
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: # obrigatório — usado quando nenhuma rede corresponde
host: myserver.example.com
port: 22
user: alice
key: ~/.ssh/id_ed25519
options: # opcional — passado como flags SSH -o Key=Value
ConnectTimeout: "10"
ServerAliveInterval: "30"
corp-vpn:
host: 10.100.0.50
port: 2222
key: ~/.ssh/corp_key
jump: bastion.corp.internal
options:
ConnectTimeout: "5" # substitui o padrão apenas para esta rede
office:
host: 192.168.1.50
```
Todo host deve ter um perfil `default`. Perfis de rede só precisam especificar campos que diferem do padrão — campos não definidos herdam do `default`.
### Campos do perfil do host
| Campo | Tipo | Descrição |
|---|---|---|
| `host` | string | Nome do host ou endereço IP |
| `port` | int | Porta SSH (padrão: 22) |
| `user` | string | Usuário SSH |
| `key` | string | Caminho para o arquivo de identidade (`~` é expandido) |
| `jump` | string | Apelido do host de salto ou `user@host` |
| `options` | mapa | Flags SSH `-o Key=Value` arbitrários (ex.: `ConnectTimeout`, `StrictHostKeyChecking`) |
| `comment` | string | Descrição mostrada em `sshroute list` |
| `tags` | lista | Tags para filtragem com `sshroute list --tag` |
As chaves de `options` são mescladas de `default` para perfis de rede — valores de rede substituem chaves correspondentes, chaves não sobrepostas são herdadas.
## Detecção de rede
As redes são avaliadas em ordem de `priority` (menor valor primeiro). Ordem alfabética desempata. A primeira rede cujas verificações passam é usada; se nenhuma corresponder, `default` se aplica.
| Tipo de verificação | Passa quando | Campos obrigatórios |
|---|---|---|
| `route` | Sub-rede/IP aparece na tabela de roteamento do kernel | `match` |
| `interface` | Interface nomeada existe e está operacionalmente ativa | `match` |
| `ping` | O host responde ao eco ICMP dentro do tempo limite | `host`, `timeout` (opcional, padrão 2s) |
| `exec` | O comando shell sai com código 0 | `command` |
Múltiplas verificações dentro de uma definição de rede usam lógica **E** — todas devem passar.
## Exemplos
Arquivos de configuração prontos para uso estão em [`examples/`](https://github.com/thereisnotime/sshroute/blob/main/examples):
| Arquivo | Caso de uso |
|---|---|
| [`basic.yaml`](https://github.com/thereisnotime/sshroute/blob/main/examples/basic.yaml) | Host único, recuo entre VPN e público |
| [`multi-network.yaml`](https://github.com/thereisnotime/sshroute/blob/main/examples/multi-network.yaml) | LAN do escritório, VPN corporativa, VPN remota, público |
| [`wireguard-backconnect.yaml`](https://github.com/thereisnotime/sshroute/blob/main/examples/wireguard-backconnect.yaml) | Peer WireGuard que se reconecta a você |
| [`jump-hosts.yaml`](https://github.com/thereisnotime/sshroute/blob/main/examples/jump-hosts.yaml) | Bastiões diferentes por rede |
| [`multi-zone-roaming.yaml`](https://github.com/thereisnotime/sshroute/blob/main/examples/multi-zone-roaming.yaml) | Homelab multi-zona com gateway WireGuard e dispositivos móveis em roaming |
## Documentação
Guias aprofundados estão em [`docs/`](https://github.com/thereisnotime/sshroute/blob/main/docs):
| Guia | Descrição |
|---|---|
| [Configuração de homelab](https://github.com/thereisnotime/sshroute/blob/main/docs/homelab.md) | Homelab multi-zona com WireGuard, hosts de salto, NAS, nós k3s |
| [Roaming multi-zona](https://github.com/thereisnotime/sshroute/blob/main/docs/multi-zone-roaming.md) | Várias LANs, gateway WireGuard, dispositivos móveis que roam entre redes |
| [Ambiente corporativo / multi-ambiente](https://github.com/thereisnotime/sshroute/blob/main/docs/corporate.md) | Dev/staging/prod com bastiões por ambiente e detecção de VPN |
| [Modo sombra](https://github.com/thereisnotime/sshroute/blob/main/docs/shadow-mode.md) | Substituto transparente do SSH — git, rsync, scp, Ansible |
| [Complemento de shell](https://github.com/thereisnotime/sshroute/blob/main/docs/shell-completion.md) | Complemento dinâmico de apelidos para bash, zsh, fish |
| [Scripts e automação](https://github.com/thereisnotime/sshroute/blob/main/docs/scripting.md) | Usando `resolve` e `copy` em scripts e pipelines CI |
## Formatos de saída
Todos os comandos de listagem suportam múltiplos formatos de saída:
```sh
sshroute list # table (padrão)
sshroute list -o json # JSON — para scripts
sshroute list -o yaml # YAML
sshroute network list -o json
```
## Comunidade
**Obtenha o software** — baixe um binário pré-compilado de [Releases](https://github.com/thereisnotime/sshroute/releases), instale com `go install github.com/thereisnotime/sshroute@latest`, ou [compile a partir do código-fonte](#compilando-a-partir-do-código-fonte).
**Feedback e relatórios de bug** — abra uma issue em [GitHub Issues](https://github.com/thereisnotime/sshroute/issues). Use o modelo de relatório de bug para comportamento inesperado e o modelo de solicitação de funcionalidade para ideias.
**Contribuições** — veja [CONTRIBUTING.md](https://github.com/thereisnotime/sshroute/blob/main/CONTRIBUTING.md) para saber como configurar o projeto, executar testes e abrir um pull request. Vulnerabilidades de segurança devem ser relatadas em particular via [GitHub Security Advisories](https://github.com/thereisnotime/sshroute/security/advisories/new).
## Compilando a partir do código-fonte
Requer [Go 1.22+](https://go.dev/dl/) e [just](https://just.systems/).
```sh
git clone [email protected]:thereisnotime/sshroute.git
cd sshroute
just build # gera bin/sshroute
just build-all # compilação cruzada linux/darwin × amd64/arm64
just test # executa testes com detector de raça
just install # go install com ldflags de versão injetadas
```