
Router SSH consapevole della rete - instrada le connessioni verso diversi IP/porte/chiavi/host di salto in base alla VPN attiva o alla rete
# sshroute
<table>
<tr>
<th>CI</th>
<th>Codice</th>
<th>OpenSpec</th>
<th>Sicurezza</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="Ultima 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="Specifiche"></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="Requisiti"></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="Attività"></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="Modifiche Aperte"></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="Licenza: Apache 2.0"></a>
</td>
</tr>
</table>
Router SSH consapevole della rete. Rileva la rete attiva o la VPN e seleziona automaticamente l'host, la porta, il file di identità e il jump host corretti per ogni connessione SSH, senza toccare `~/.ssh/config`.
## Come funziona
Definisci ogni host logico una volta con un profilo `default` e override opzionali per rete. Ad ogni connessione, sshroute rileva su quale rete ti trovi (VPN, LAN ufficio, peer WireGuard, ecc.) e risolve i parametri SSH corretti prima di passare il controllo al vero `/usr/bin/ssh`.
```
ssh myserver
→ sshroute rileva: corp-vpn è attiva
→ risolve: 10.100.0.50:2222 tramite bastion.corp.internal
→ exec /usr/bin/ssh -p 2222 -i ~/.ssh/corp_key -J bastion.corp.internal 10.100.0.50
```
## Perché sshroute?
### Per gli appassionati di homelab
Il tuo laboratorio ha probabilmente almeno due realtà: sei a casa sulla LAN, oppure sei fuori e ti connetti tramite WireGuard o un'altra VPN. Il problema è che `~/.ssh/config` non sa in quale ti trovi — così ti ritrovi con alias separati (`server-lan`, `server-vpn`), o un jump host che funziona solo a metà, o semplicemente memorizzi gli IP.
sshroute risolve questo problema rilevando la rete corrente prima di ogni connessione. Quando l'interfaccia WireGuard è attiva e la route del peer esiste, si connette direttamente all'IP del tunnel. Quando sei sulla LAN, usa l'indirizzo locale. Quando nessuno dei due è raggiungibile, ricade sul nome host pubblico. Un alias, tre realtà, nessuna commutazione manuale.
Intercetta anche SSH in modo trasparente — `git push`, `rsync`, `scp` passano tutti attraverso automaticamente una volta impostata la modalità shadow. Nessun wrapper, nessuna funzione di shell, nessun pensiero.
### Per ambienti aziendali
Le reti aziendali sono peggio. Hai Internet pubblico, forse una VPN site-to-site, forse una VPN personale split-tunnel, e all'interno di questa diversi jump host a seconda dell'ambiente che stai targetizzando — dev, staging, prod, ciascuno con il proprio bastion e la propria chiave. Mantenere tutto in ordine in `~/.ssh/config` significa o un file enorme che si rompe ogni volta che l'infrastruttura cambia, o scrivere uno script che ogni membro del team mantiene in modo diverso.
sshroute ti permette di definire la logica di routing in modo dichiarativo, mantenerla in un file YAML con versionamento e condividerla con il team. La stessa configurazione funziona per tutti — la rete giusta viene rilevata automaticamente in base alle interfacce o route attive su ciascuna macchina. Chiavi, porte, utenti e jump host vengono risolti senza che l'utente debba pensarci.
## Confronto
| Caratteristica | `~/.ssh/config` | Solo WireGuard | Teleport / Boundary | sshroute |
|---|---|---|---|---|
| Rileva la rete corrente | ❌ | ❌ | ❌ | ✅ |
| Sceglie automaticamente il percorso migliore | ❌ | ❌ | ❌ | ✅ |
| Ricade su connessione fallita | ❌ | ❌ | ✅ | ✅ |
| Riconnessione automatica + re-routing su caduta | ❌ | ⚠️ il tunnel vaga | ⚠️ tramite proxy fisso | ✅ |
| Un comando per host, ovunque | ❌ | ⚠️ VPN deve essere attiva | ✅ | ✅ |
| Dimensione configurazione per 10 host × 4 percorsi | 📄 ~600 righe | 📄 ~600 righe + config VPN | 📄 configurazione lato server | 📄 ~60 righe |
| Dispositivi mobili in roaming | ⚠️ alias manuali | ⚠️ VPN necessaria | ✅ | ✅ |
| Concatenamento automatico jump host | ⚠️ `-J` manuale | ➖ n/a | ✅ | ✅ |
| Funziona con scp / rsync / git / Ansible | ✅ | ✅ | ⚠️ parziale | ✅ |
| Nessuna installazione lato server sui target | ✅ | ❌ | ❌ | ✅ |
| Nessun server di autenticazione o demone da eseguire | ✅ | ❌ | ❌ | ✅ |
| Nessun agente client | ✅ | ❌ | ❌ | ✅ |
| Open source, completamente self-hosted | ✅ | ✅ | ⚠️ open-core | ✅ |
Teleport e Boundary sono una categoria diversa — aggiungono controllo degli accessi, log di audit e autenticazione basata su certificati oltre al routing. Se è ciò di cui hai bisogno, usali. sshroute è per quando vuoi l'intelligenza di routing senza il sovraccarico operativo di gestire un server di autenticazione centrale.
## Installazione
### Download binario
Scarica l'ultima release da [GitHub Releases](https://github.com/thereisnotime/sshroute/releases). I binari sono disponibili per Linux, macOS e Android su AMD64 e ARM64.
### Installazione con Go
```sh
go install github.com/thereisnotime/sshroute@latest
```
### Android (Termux)
Scarica il tarball `android_arm64` da [GitHub Releases](https://github.com/thereisnotime/sshroute/releases), estrailo e posiziona il binario in `~/.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
```
Aggiungi `~/.local/bin` al tuo `PATH` in `~/.bashrc` o `~/.profile` se non lo è già:
```sh
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
```
In alternativa, compila dal sorgente con Go di Termux. Poiché la toolchain ufficiale di Go non pubblica binari android/arm64, imposta `GOTOOLCHAIN=local` per usare ciò che Termux fornisce:
```sh
GOTOOLCHAIN=local go install github.com/thereisnotime/sshroute@latest
```
Dopo l'installazione, imposta il percorso del binario SSH poiché Termux non ha `/usr/bin/ssh`:
```yaml
# ~/.config/sshroute/config.yaml
ssh_binary: /data/data/com.termux/files/usr/bin/ssh
```
Oppure tramite variabile d'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
```
Su sistemi con SELinux abilitato (Fedora, RHEL, ecc.) aggiungi `:Z` al flag del volume:
```sh
podman run --rm -v ~/.config/sshroute:/root/.config/sshroute:Z \
ghcr.io/thereisnotime/sshroute network
```
### Modalità shadow (sostituzione SSH trasparente)
Installa sshroute come `ssh` prima nel tuo `$PATH`. Tutte le chiamate SSH — dal terminale, da `git`, `rsync`, `scp` — vengono intercettate automaticamente. Gli host non presenti nella tua configurazione passano a `/usr/bin/ssh` invariati.
```sh
mkdir -p ~/.local/bin
ln -s $(which sshroute) ~/.local/bin/ssh
# Aggiungi a ~/.bashrc o ~/.zshrc se non già presente:
export PATH="$HOME/.local/bin:$PATH"
```
## Avvio rapido
```sh
# Aggiungi un host con un profilo predefinito
sshroute add myserver --host myserver.example.com --user alice --key ~/.ssh/id_ed25519
# Aggiungi un override specifico per VPN
sshroute add myserver --network vpn --host 10.8.0.50 --port 2222 --jump bastion.vpn
# Connettiti — la rete viene rilevata automaticamente
sshroute connect myserver
# Anteprima del comando risolto senza eseguirlo
sshroute connect myserver --dry-run
# Vedi quale rete è attualmente attiva
sshroute network
```
## Comandi
### Flag globali
Questi flag si applicano a ogni comando:
| Flag | Variabile d'ambiente | Default | Descrizione |
|---|---|---|---|
| `--config` | `SSHROUTE_CONFIG` | `~/.config/sshroute/config.yaml` | Percorso del file di configurazione |
| `-o, --output` | | `table` | Formato di output: `table`, `json`, `yaml` |
| `-v, --verbose` | `SSHROUTE_VERBOSE=1` | `false` | Log di debug su stderr |
| `--dry-run` | | `false` | Stampa il comando SSH risolto senza eseguire |
### `init`
Crea un file di configurazione iniziale con esempi commentati. Fallisce se il file esiste già.
| Flag | Default | Descrizione |
|---|---|---|
| `--force` | `false` | Sovrascrive un file di configurazione esistente |
### `connect <alias>`
Rileva la rete attiva, risolve i parametri SSH per `alias` ed esegue il vero binario SSH. Eventuali argomenti extra dopo l'alias vengono passati a SSH invariati.
| Flag | Default | Descrizione |
|---|---|---|
| `--fallback` | `false` | Prova ogni profilo in ordine di priorità, ritentando il successivo solo in caso di fallimento della connessione (exit 255) |
| `--reconnect` | `false` | Supervisiona la connessione e si riconnette automaticamente quando cade, rilevando nuovamente la rete attiva e risolvendo la route ogni volta |
| `--reconnect-delay` | `2s` | Attesa tra tentativi di riconnessione quando è impostato `--reconnect` |
Con `--reconnect`, sshroute mantiene ssh vivo attraverso cadute di connessione (sospensione del laptop, handoff WiFi, roaming tra reti). Poiché rileva la rete a ogni riconnessione, ti segue su una route diversa: ad esempio, dormire sulla LAN e svegliarsi su un hotspot si riconnette tramite la route pubblica invece di ritentare l'indirizzo LAN ora irraggiungibile. Un logout pulito (exit 0) o un fallimento di autenticazione/comando remoto ferma il ciclo; solo le cadute genuine di connessione provocano la riconnessione. Riconnetti esegue ssh come sottoprocesso (come `--fallback`), quindi sshroute rimane residente per la sessione; `SIGINT`/`SIGTERM` lo abbatte. Lo stato della sessione attraverso il breve intervallo è compito del tuo multiplexer (tmux/zellij); combina `--reconnect` con `-- tmux attach` o `-- zellij attach -c <nome>` per tornare direttamente nella tua sessione:
```sh
sshroute connect myserver --reconnect --fallback -- zellij attach -c lavoro
```
### `list`
Elenca tutti gli host configurati e i parametri SSH che verrebbero utilizzati sulla rete corrente. Supporta `-o table|json|yaml`.
### `add <alias>`
Aggiunge un host o ne aggiorna uno esistente. I flag omessi mantengono il loro valore corrente. Esegui più volte con diversi valori `--network` per creare override per rete.
| Flag | Default | Descrizione |
|---|---|---|
| `--host` | | Nome host o indirizzo IP |
| `--port` | `22` | Porta SSH |
| `--user` | | Nome utente SSH |
| `--key` | | Percorso del file di identità (supporta `~`) |
| `--jump` | | Jump host — passato come `-J` a SSH |
| `--network` | `default` | Profilo di rete in cui scrivere i parametri |
### `remove <alias>`
Rimuove tutti i profili per `alias` dalla configurazione.
### `network`
Stampa il nome della rete attualmente rilevata (o `default` se nessuna corrisponde).
### `network list`
Elenca tutte le reti configurate con la loro priorità, regole di controllo e stato attuale. Supporta `-o table|json|yaml`.
### `network test <nome>`
Esegue ogni controllo per la rete `nome` e stampa superato/fallito per ogni regola. Utile per il debug della logica di rilevamento.
### `config`
Stampa il percorso risolto del file di configurazione.
### `config edit`
Apre il file di configurazione in `$EDITOR` (ricade su `nano`). Crea il file e la directory padre se non esistono.
### `resolve <alias>`
Stampa i parametri SSH che verrebbero utilizzati per `alias` sulla rete corrente. Utile per debug e scripting. Usa `--network <nome>` per sovrascrivere la rete rilevata. Supporta `-o table|json|yaml`.
| Flag | Default | Descrizione |
|---|---|---|
| `--network` | rilevamento automatico | Profilo di rete su cui risolvere |
### `copy <alias> <src> <dst>`
Copia file da o verso un host configurato usando `scp` con gli stessi parametri risolti (chiave, porta, jump) di `connect`. Usa la sintassi `<alias>:<percorso>` per i percorsi remoti:
```sh
sshroute copy myserver ./local.txt myserver:/percorso/remoto/
sshroute copy myserver myserver:/file_remoto.txt ./locale/
```
La variabile d'ambiente `SSHROUTE_SCP` sovrascrive il binario `scp` utilizzato.
### `version`
Stampa la versione, il commit git, la data di build e le informazioni sul runtime Go.
### `update`
Aggiorna sshroute sul posto all'ultima release di GitHub. Scarica l'archivio per la tua piattaforma, verifica il suo sha256 rispetto a `checksums.txt` e, se [`cosign`](https://github.com/sigstore/cosign) è installato, verifica la firma cosign della release, prima di sostituire atomicamente il binario in esecuzione.
```sh
sshroute update # scarica, verifica e installa l'ultima release
sshroute update --check # segnala solo se è disponibile una versione più recente
sshroute update --force # reinstalla l'ultima anche se già attuale
```
Se la verifica sha256 (o cosign, quando presente) fallisce, l'aggiornamento viene annullato e il binario rimane intatto. Questo è destinato alle installazioni del binario di release; se hai installato tramite `go install` o un gestore di pacchetti, aggiorna con quello.
## File di configurazione
Posizione predefinita: `~/.config/sshroute/config.yaml`
```yaml
networks:
corp-vpn:
priority: 10 # inferiore = controllato per primo
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: # richiesto — usato quando nessuna rete corrisponde
host: myserver.example.com
port: 22
user: alice
key: ~/.ssh/id_ed25519
options: # opzionale — passato come flag 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" # sovrascrive il default solo per questa rete
office:
host: 192.168.1.50
```
Ogni host deve avere un profilo `default`. I profili di rete devono solo specificare i campi che differiscono dal default — i campi non impostati ereditano da `default`.
### Campi del profilo host
| Campo | Tipo | Descrizione |
|---|---|---|
| `host` | stringa | Nome host o indirizzo IP |
| `port` | int | Porta SSH (default: 22) |
| `user` | stringa | Utente SSH |
| `key` | stringa | Percorso del file di identità (`~` viene espanso) |
| `jump` | stringa | Alias del jump host o `user@host` |
| `options` | mappa | Flag arbitrari SSH `-o Key=Value` (es. `ConnectTimeout`, `StrictHostKeyChecking`) |
| `comment` | stringa | Descrizione mostrata in `sshroute list` |
| `tags` | lista | Tag per filtrare con `sshroute list --tag` |
Le chiavi di `options` vengono unite dal `default` nei profili di rete — i valori di rete sovrascrivono le chiavi corrispondenti, le chiavi non sovrapposte vengono ereditate.
## Rilevamento rete
Le reti vengono valutate in ordine di `priority` (valore più basso per primo). L'ordine alfabetico rompe i pareggi. Viene utilizzata la prima rete i cui controlli passano tutti; se nessuna corrisponde, viene applicato `default`.
| Tipo di controllo | Passa quando | Campi richiesti |
|---|---|---|
| `route` | La sottorete/IP appare nella tabella di routing del kernel | `match` |
| `interface` | L'interfaccia nominata esiste ed è operativamente attiva | `match` |
| `ping` | L'host risponde all'echo ICMP entro il timeout | `host`, `timeout` (opzionale, default 2s) |
| `exec` | Il comando shell esce con codice 0 | `command` |
Più controlli all'interno di una definizione di rete utilizzano logica **AND** — tutti devono passare.
## Esempi
I file di configurazione pronti all'uso sono in [`examples/`](https://github.com/thereisnotime/sshroute/blob/main/examples):
| File | Caso d'uso |
|---|---|
| [`basic.yaml`](https://github.com/thereisnotime/sshroute/blob/main/examples/basic.yaml) | Singolo host, VPN vs fallback pubblico |
| [`multi-network.yaml`](https://github.com/thereisnotime/sshroute/blob/main/examples/multi-network.yaml) | LAN ufficio, VPN aziendale, VPN remota, pubblico |
| [`wireguard-backconnect.yaml`](https://github.com/thereisnotime/sshroute/blob/main/examples/wireguard-backconnect.yaml) | Peer WireGuard che si riconnette a te |
| [`jump-hosts.yaml`](https://github.com/thereisnotime/sshroute/blob/main/examples/jump-hosts.yaml) | Bastion diversi per rete |
| [`multi-zone-roaming.yaml`](https://github.com/thereisnotime/sshroute/blob/main/examples/multi-zone-roaming.yaml) | Homelab multi-zona con gateway WireGuard e dispositivi mobili in roaming |
## Documentazione
Guide approfondite sono in [`docs/`](https://github.com/thereisnotime/sshroute/blob/main/docs):
| Guida | Descrizione |
|---|---|
| [Configurazione homelab](https://github.com/thereisnotime/sshroute/blob/main/docs/homelab.md) | Homelab multi-zona con WireGuard, jump host, NAS, nodi k3s |
| [Roaming multi-zona](https://github.com/thereisnotime/sshroute/blob/main/docs/multi-zone-roaming.md) | Più LAN, gateway WireGuard, dispositivi mobili che vagano tra reti |
| [Aziendale / multi-ambiente](https://github.com/thereisnotime/sshroute/blob/main/docs/corporate.md) | Dev/staging/prod con bastion per ambiente e rilevamento VPN |
| [Modalità shadow](https://github.com/thereisnotime/sshroute/blob/main/docs/shadow-mode.md) | Sostituzione SSH trasparente — git, rsync, scp, Ansible |
| [Completamento shell](https://github.com/thereisnotime/sshroute/blob/main/docs/shell-completion.md) | Completamento dinamico alias per bash, zsh, fish |
| [Scripting e automazione](https://github.com/thereisnotime/sshroute/blob/main/docs/scripting.md) | Uso di `resolve` e `copy` in script e pipeline CI |
## Formati di output
Tutti i comandi di elenco supportano più formati di output:
```sh
sshroute list # table (default)
sshroute list -o json # JSON — per scripting
sshroute list -o yaml # YAML
sshroute network list -o json
```
## Community
**Ottieni il software** — scarica un binario precompilato da [Releases](https://github.com/thereisnotime/sshroute/releases), installa con `go install github.com/thereisnotime/sshroute@latest`, o [compila dal sorgente](#compilazione-dal-sorgente).
**Feedback e segnalazioni di bug** — apri un issue su [GitHub Issues](https://github.com/thereisnotime/sshroute/issues). Usa il template per segnalazione bug per comportamenti imprevisti e il template per richiesta funzionalità per idee.
**Contribuire** — vedi [CONTRIBUTING.md](https://github.com/thereisnotime/sshroute/blob/main/CONTRIBUTING.md) per come impostare il progetto, eseguire i test e aprire una pull request. Le vulnerabilità di sicurezza devono essere segnalate privatamente tramite [GitHub Security Advisories](https://github.com/thereisnotime/sshroute/security/advisories/new).
## Compilazione dal sorgente
Richiede [Go 1.22+](https://go.dev/dl/) e [just](https://just.systems/).
```sh
git clone [email protected]:thereisnotime/sshroute.git
cd sshroute
just build # output in bin/sshroute
just build-all # cross-compila linux/darwin × amd64/arm64
just test # esegue i test con rilevatore di race condition
just install # go install con ldflags di versione iniettati
```