
네트워크 인식 SSH 라우터 - 활성 VPN 또는 네트워크에 따라 다른 IP/포트/키/점프 호스트로 연결을 라우팅합니다.
네트워크 인식 SSH 라우터. 현재 활성 네트워크나 VPN을 감지하여 각 SSH 연결에 대해 적절한 호스트, 포트, 아이덴티티 파일, 점프 호스트를 ~/.ssh/config를 건드리지 않고 자동으로 선택합니다.
각 논리적 호스트를 default 프로필과 네트워크별 선택적 오버라이드로 한 번 정의합니다. 각 연결 시 sshroute는 현재 어떤 네트워크( VPN, 사무실 LAN, WireGuard 피어 등)에 있는지 감지하고, 올바른 SSH 매개변수를 확인한 후 실제 /usr/bin/ssh로 넘깁니다.
ssh myserver
→ sshroute 감지: corp-vpn 활성
→ 확인: 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
랩에는 적어도 두 가지 현실이 있을 겁니다: 집에서 LAN에 있거나, 외출 중에 WireGuard나 다른 VPN을 통해 접속하는 경우입니다. 문제는 ~/.ssh/config가 현재 어떤 상태인지 모른다는 점입니다. 그래서 별칭을 따로 만들거나(server-lan, server-vpn), 절반만 작동하는 점프 호스트를 사용하거나, IP를 외우게 됩니다.
sshroute는 각 연결 전에 현재 네트워크를 감지하여 이 문제를 해결합니다. WireGuard 인터페이스가 작동 중이고 피어 라우트가 존재하면 터널 IP로 직접 연결합니다. LAN에 있을 때는 로컬 주소를 사용합니다. 둘 다 도달할 수 없으면 공용 호스트명으로 대체됩니다. 하나의 별칭, 세 가지 현실, 수동 전환 불필요.
또한 SSH를 투명하게 가로챕니다. git push, rsync, scp 모두 섀도우 모드 설정 후 자동으로 이를 통해 작동합니다. 래퍼나 셸 함수, 고민이 필요 없습니다.
기업 네트워크는 더 복잡합니다. 공용 인터넷, 사이트 간 VPN, 개인 VPN 분할 터널(분할 터널링) 등이 있을 수 있으며, 그 안에서 타겟 환경(dev, staging, prod)마다 다른 점프 호스트와 키를 사용합니다. 이것을 ~/.ssh/config에 정리하려면 거대한 설정 파일 하나를 유지하거나, 팀원마다 다르게 관리하는 스크립트를 작성해야 합니다.
sshroute를 사용하면 라우팅 로직을 선언적으로 정의하고, 버전 관리되는 YAML 파일에 저장하며, 팀 전체에 공유할 수 있습니다. 동일한 설정이 모든 사람에게 적용됩니다. 각 머신에서 활성화된 인터페이스나 라우트를 기반으로 올바른 네트워크가 자동으로 감지됩니다. 키, 포트, 사용자, 점프 호스트가 사용자가 신경 쓸 필요 없이 확인됩니다.
Teleport와 Boundary는 라우팅 위에 액세스 제어, 감사 로그, 인증서 기반 인증을 추가하는 별도의 범주입니다. 그런 기능이 필요하면 사용하세요. sshroute는 중앙 인증 서버 운영의 부담 없이 라우팅 지능만 필요한 경우를 위한 것입니다.
GitHub Releases에서 최신 릴리스를 다운로드합니다. Linux, macOS, Android용 AMD64 및 ARM64 바이너리가 제공됩니다.
go install github.com/thereisnotime/sshroute@latest
GitHub Releases에서 android_arm64 tarball을 다운로드하고, 압축을 풀어 ~/.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
~/.bashrc나 ~/.profile에 ~/.local/bin을 PATH에 추가합니다(아직 없다면):
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
또는 Termux의 Go를 사용하여 소스에서 컴파일합니다. 공식 Go 툴체인이 android/arm64 바이너리를 제공하지 않으므로, GOTOOLCHAIN=local로 설정하여 Termux가 제공하는 Go를 사용합니다:
GOTOOLCHAIN=local go install github.com/thereisnotime/sshroute@latest
설치 후, Termux에는 /usr/bin/ssh가 없으므로 SSH 바이너리 경로를 설정합니다:
# ~/.config/sshroute/config.yaml
ssh_binary: /data/data/com.termux/files/usr/bin/ssh
또는 환경 변수로 설정: 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
SELinux 활성 시스템(Fedora, RHEL 등)에서는 볼륨 플래그에 :Z를 추가합니다:
podman run --rm -v ~/.config/sshroute:/root/.config/sshroute:Z \
ghcr.io/thereisnotime/sshroute network
$PATH에서 더 앞쪽에 ssh로 sshroute를 설치합니다. 터미널, git, rsync, scp의 모든 SSH 호출이 자동으로 가로채집니다. 설정에 없는 호스트는 변경 없이 /usr/bin/ssh로 전달됩니다.
mkdir -p ~/.local/bin
ln -s $(which sshroute) ~/.local/bin/ssh
# ~/.bashrc 또는 ~/.zshrc에 추가 (아직 없다면):
export PATH="$HOME/.local/bin:$PATH"
# 기본 프로필로 호스트 추가
sshroute add myserver --host myserver.example.com --user alice --key ~/.ssh/id_ed25519
# VPN 전용 오버라이드 추가
sshroute add myserver --network vpn --host 10.8.0.50 --port 2222 --jump bastion.vpn
# 연결 — 네트워크가 자동으로 감지됨
sshroute connect myserver
# 실행하지 않고 확인된 명령 미리 보기
sshroute connect myserver --dry-run
# 현재 활성 네트워크 보기
sshroute network
이 플래그들은 모든 명령에 적용됩니다:
init주석이 포함된 예제로 시작 설정 파일을 만듭니다. 파일이 이미 있으면 실패합니다.
| 플래그 | 기본값 | 설명 |
|---|---|---|
--force | false | 기존 설정 파일 덮어쓰기 |
connect <alias>활성 네트워크를 감지하고, alias에 대한 SSH 매개변수를 확인한 후 실제 SSH 바이너리를 실행합니다. alias 뒤의 추가 인수는 변경 없이 SSH로 전달됩니다.
--reconnect와 함께 사용하면 sshroute는 연결이 끊어져도(노트북 절전, WiFi 핸드오프, 네트워크 간 로밍) ssh를 유지합니다. 재연결할 때마다 네트워크를 다시 감지하므로 다른 경로를 따라 이동합니다. 예를 들어 LAN에서 절전 후 핫스팟에서 깨어나면 공용 경로를 통해 재연결하고, 더 이상 도달할 수 없는 LAN 주소를 다시 시도하지 않습니다. 정상 로그아웃(exit 0) 또는 인증/원격 명령 실패는 루프를 중단합니다. 실제 연결 끊김만 재연결합니다. 재연결은 ssh를 하위 프로세스로 실행하므로(--fallback처럼) sshroute는 세션 동안 상주합니다. SIGINT/SIGTERM은 중단합니다. 끊김 시 세션 상태는 멀티플렉서(tmux/zellij)의 역할입니다. --reconnect와 -- tmux attach 또는 -- zellij attach -c <name>을 결합하여 세션으로 바로 돌아갑니다:
sshroute connect myserver --reconnect --fallback -- zellij attach -c work
list설정된 모든 호스트와 현재 네트워크에서 사용될 SSH 매개변수를 나열합니다. -o table|json|yaml을 지원합니다.
add <alias>호스트를 추가하거나 기존 호스트를 업데이트합니다. 생략된 플래그는 현재 값을 유지합니다. 다른 --network 값으로 여러 번 실행하여 네트워크별 오버라이드를 구축합니다.
remove <alias>설정에서 alias의 모든 프로필을 제거합니다.
network현재 감지된 네트워크의 이름을 출력합니다(일치하는 것이 없으면 default).
network list설정된 모든 네트워크를 우선순위, 검사 규칙, 현재 활성 상태와 함께 나열합니다. -o table|json|yaml을 지원합니다.
network test <name>네트워크 name에 대한 모든 검사를 실행하고 규칙별로 통과/실패를 출력합니다. 감지 로직 디버깅에 유용합니다.
config설정 파일의 확인된 경로를 출력합니다.
config edit$EDITOR에서 설정 파일을 엽니다(nano로 대체). 파일과 상위 디렉터리가 없으면 생성합니다.
resolve <alias>현재 네트워크에서 alias에 사용될 SSH 매개변수를 출력합니다. 디버깅 및 스크립팅에 유용합니다. --network <name>을 사용하여 감지된 네트워크를 재정의합니다. -o table|json|yaml을 지원합니다.
| 플래그 | 기본값 | 설명 |
|---|---|---|
--network | 자동 감지 | 확인할 네트워크 프로필 |
copy <alias> <src> <dst>connect와 동일한 확인된 매개변수(키, 포트, 점프)를 사용하여 scp로 설정된 호스트에 파일을 복사하거나 호스트에서 가져옵니다. 원격 경로에는 <alias>:<path> 구문을 사용합니다:
sshroute copy myserver ./local.txt myserver:/remote/path/
sshroute copy myserver myserver:/remote/file.txt ./local/
SSHROUTE_SCP 환경 변수는 사용되는 scp 바이너리를 재정의합니다.
version버전, git 커밋, 빌드 날짜, Go 런타임 정보를 출력합니다.
updatesshroute를 최신 GitHub 릴리스로 제자리에서 업데이트합니다. 플랫폼에 맞는 아카이브를 다운로드하고, checksums.txt에 대해 sha256을 확인하며, cosign이 설치되어 있으면 릴리스의 cosign 서명을 확인한 후 실행 중인 바이너리를 원자적으로 교체합니다.
sshroute update # 최신 릴리스를 다운로드, 확인, 설치
sshroute update --check # 최신 버전이 있는지만 보고
sshroute update --force # 이미 최신이어도 최신 버전 재설치
sha256(또는 cosign, 설치된 경우) 확인이 실패하면 업데이트가 중단되고 바이너리는 그대로 유지됩니다. 이 명령은 릴리스 바이너리 설치를 대상으로 합니다. go install이나 패키지 관리자로 설치한 경우 대신 해당 방법으로 업데이트하세요.
기본 위치: ~/.config/sshroute/config.yaml
networks:
corp-vpn:
priority: 10 # 값이 낮을수록 먼저 확인
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: # 필수 — 일치하는 네트워크가 없을 때 사용
host: myserver.example.com
port: 22
user: alice
key: ~/.ssh/id_ed25519
options: # 선택 사항 — 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" # 이 네트워크에 대해서만 기본값 재정의
office:
host: 192.168.1.50
모든 호스트에는 default 프로필이 있어야 합니다. 네트워크 프로필은 기본값과 다른 필드만 지정하면 됩니다. 설정되지 않은 필드는 default에서 상속됩니다.
options 키는 default에서 네트워크 프로필로 병합됩니다. 네트워크 값이 일치하는 키를 재정의하고, 겹치지 않는 키는 상속됩니다.
네트워크는 priority 순서로 평가됩니다(값이 가장 낮은 것부터). 동일한 우선순위는 알파벳 순서로 결정됩니다. 모든 검사를 통과하는 첫 번째 네트워크가 사용됩니다. 일치하는 것이 없으면 default가 적용됩니다.
하나의 네트워크 정의 내 여러 검사는 AND 논리를 사용합니다. 모두 통과해야 합니다.
사용 준비가 된 설정 파일은 examples/에 있습니다:
심층 가이드는 docs/에 있습니다:
모든 목록 명령은 여러 출력 형식을 지원합니다:
sshroute list # table (기본값)
sshroute list -o json # JSON — 스크립팅용
sshroute list -o yaml # YAML
sshroute network list -o json
소프트웨어 받기 — Releases에서 미리 빌드된 바이너리를 다운로드하거나, go install github.com/thereisnotime/sshroute@latest로 설치하거나, 소스에서 빌드하세요.
피드백 및 버그 신고 — GitHub Issues에서 이슈를 열어주세요. 예상치 못한 동작에는 버그 리포트 템플릿을, 아이디어에는 기능 요청 템플릿을 사용하세요.
기여하기 — 프로젝트 설정 방법, 테스트 실행 방법, 풀 리퀘스트 여는 방법은 CONTRIBUTING.md를 참조하세요. 보안 취약점은 GitHub Security Advisories를 통해 개인적으로 보고해 주세요.
git clone [email protected]:thereisnotime/sshroute.git
cd sshroute
just build # bin/sshroute 출력
just build-all # linux/darwin × amd64/arm64 크로스 컴파일
just test # 경쟁 탐지기로 테스트 실행
just install # 버전 ldflags가 삽입된 go install
|
|
| 기능 | ~/.ssh/config | WireGuard 전용 | Teleport / Boundary | sshroute |
|---|
| 현재 네트워크 감지 | ❌ | ❌ | ❌ | ✅ |
| 최적 경로 자동 선택 | ❌ | ❌ | ❌ | ✅ |
| 연결 실패 시 대체(fallback) | ❌ | ❌ | ✅ | ✅ |
| 연결 끊김 시 자동 재연결 + 경로 변경 | ❌ | ⚠️ 터널 로밍 | ⚠️ 고정 프록시 경유 | ✅ |
| 어느 위치에서든 호스트당 하나의 명령 | ❌ | ⚠️ VPN 켜야 함 | ✅ | ✅ |
| 10개 호스트 × 4개 경로의 설정 크기 | 📄 ~600줄 | 📄 ~600줄 + VPN 설정 | 📄 서버 측 설정 | 📄 ~60줄 |
| 모바일 기기 로밍 | ⚠️ 수동 별칭 | ⚠️ VPN 필요 | ✅ | ✅ |
| 점프 호스트 자동 체이닝 | ⚠️ 수동 -J | ➖ 해당 없음 | ✅ | ✅ |
| scp / rsync / git / Ansible 호환 | ✅ | ✅ | ⚠️ 일부 | ✅ |
| 타겟에 서버 측 설치 불필요 | ✅ | ❌ | ❌ | ✅ |
| 인증 서버나 데몬 실행 불필요 | ✅ | ❌ | ❌ | ✅ |
| 클라이언트 에이전트 불필요 | ✅ | ❌ | ❌ | ✅ |
| 오픈 소스, 완전 자체 호스팅 | ✅ | ✅ | ⚠️ 오픈코어 | ✅ |
| 플래그 | 환경 변수 | 기본값 | 설명 |
|---|
--config | SSHROUTE_CONFIG | ~/.config/sshroute/config.yaml | 설정 파일 경로 |
-o, --output | table | 출력 형식: table, json, yaml | |
-v, --verbose | SSHROUTE_VERBOSE=1 | false | stderr로 디버그 로깅 |
--dry-run | false | 확인된 SSH 명령을 실행하지 않고 출력 |
| 플래그 | 기본값 | 설명 |
|---|
--fallback | false | 우선순위 순서로 모든 프로필을 시도하고, 연결 실패(exit 255)가 발생할 때만 다음 프로필을 재시도 |
--reconnect | false | 연결을 감독하고 끊어지면 자동으로 재연결하며, 매번 활성 네트워크를 다시 감지하고 경로를 다시 확인 |
--reconnect-delay | 2s | --reconnect 설정 시 재연결 시도 간 대기 시간 |
| 플래그 | 기본값 | 설명 |
|---|
--host | 호스트명 또는 IP 주소 | |
--port | 22 | SSH 포트 |
--user | SSH 사용자 이름 | |
--key | 아이덴티티 파일 경로 (~ 지원) | |
--jump | 점프 호스트 — SSH에 -J로 전달 | |
--network | default | 매개변수를 기록할 네트워크 프로필 |
| 필드 | 유형 | 설명 |
|---|
host | string | 호스트명 또는 IP 주소 |
port | int | SSH 포트 (기본값: 22) |
user | string | SSH 사용자 |
key | string | 아이덴티티 파일 경로 (~ 확장됨) |
jump | string | 점프 호스트 별칭 또는 user@host |
options | map | 임의의 SSH -o Key=Value 플래그 (예: ConnectTimeout, StrictHostKeyChecking) |
comment | string | sshroute list에 표시되는 설명 |
tags | list | sshroute list --tag로 필터링하기 위한 태그 |
| 검사 유형 | 통과 조건 | 필수 필드 |
|---|
route | 서브넷/IP가 커널 라우팅 테이블에 나타남 | match |
interface | 명명된 인터페이스가 존재하고 작동 상태(up)임 | match |
ping | 호스트가 시간 초과 내에 ICMP 에코에 응답 | host, timeout (선택, 기본값 2s) |
exec | 셸 명령이 종료 코드 0으로 종료 | command |
| 파일 | 사용 사례 |
|---|
basic.yaml | 단일 호스트, VPN vs 공용 대체 |
multi-network.yaml | 사무실 LAN, 회사 VPN, 원격 VPN, 공용 |
wireguard-backconnect.yaml | 사용자에게 역연결하는 WireGuard 피어 |
jump-hosts.yaml | 네트워크별 다른 배스천 |
multi-zone-roaming.yaml | WireGuard 게이트웨이와 로밍 모바일 장치를 갖춘 다중 영역 홈랩 |
| 가이드 | 설명 |
|---|
| 홈랩 설정 | WireGuard, 점프 호스트, NAS, k3s 노드를 사용한 다중 영역 홈랩 |
| 다중 영역 로밍 | 여러 LAN, WireGuard 게이트웨이, 네트워크 간 로밍하는 모바일 장치 |
| 기업 / 다중 환경 | 환경별 배스천 및 VPN 감지를 사용한 Dev/Staging/Prod |
| 섀도우 모드 | 투명한 SSH 대체 — git, rsync, scp, Ansible |
| 셸 완성 | bash, zsh, fish용 동적 별칭 완성 |
| 스크립팅 및 자동화 | 스크립트 및 CI 파이프라인에서 resolve 및 copy 사용 |