Skip to main content

SSH tunnel server + agent with reverse port forwarding

Project description

ssh-tunnel-gateway

Single Python package that ships both the server and agent CLIs.

Usage (Start Here)

Server foreground:

API_KEY="change-me" ssh-tunnel-server

Server systemd mode:

API_KEY="change-me" ssh-tunnel-server -d

Agent foreground:

ssh-tunnel-agent --api-key change-me --endpoint http://server:12000

Agent systemd mode:

ssh-tunnel-agent -d --api-key change-me --endpoint http://server:12000

Notes:

  • If --agent-id is not provided, agent generates a UUID once and caches it locally for future restarts.
  • Agent writes current session info (including port_b) to ${STATE_DIR}/session.json by default.
  • Server reuses the same port_b for the same agent_id when possible.
  • If the previous port is busy, server tries to reclaim (kill listener on that port) and reuse it.
  • If reclaim fails, server rotates to a new free port.
  • Default reverse bind host is 0.0.0.0 (public bind on gateway).
  • Use --reverse-bind-host 127.0.0.1 if you want loopback-only bind for strict bastion usage.
  • If not set, agent follows the server-provided reverse bind host.
  • In -d mode, register/startup failures exit immediately so systemd can restart the unit.

Install

On the gateway server host:

pip install ssh-tunnel-gateway

On each agent host:

pip install ssh-tunnel-gateway

Quick Start (ProxyJump)

  1. On the gateway server host, start server:
API_KEY="change-me" ssh-tunnel-server

With default public reverse bind (AGENT_REVERSE_BIND_HOST=0.0.0.0), gateway sshd_config must include:

  • AllowTcpForwarding remote
  • GatewayPorts clientspecified
  1. On the agent host, start agent:
ssh-tunnel-agent --api-key change-me --endpoint http://<gateway_host>:12000
  1. Get port_b from the agent side:
  • Foreground mode prints a log line with port_b.
  • Foreground logs also print:
    • ssh_user: tunnel login user returned by server.
    • jump_user: ProxyJump user returned by server.
    • agent_user_hint: defaults to local $USER on the agent host.
  • Session file is always written to ${STATE_DIR}/session.json (default ./data/session.json):
cat ./data/session.json

Get only port_b:

python3 -c 'import json; print(json.load(open("./data/session.json"))["port_b"])'
  1. Add user SSH config (~/.ssh/config):
Host GWServer
    HostName <gateway_public_ip_or_dns>
    User <gateway_ssh_user>
    Port 22

Host AgentServer
    HostName localhost
    User <agent_ssh_user>
    Port <port_b_from_agent_session_json>
    ProxyJump GWServer
  1. Connect:
ssh AgentServer

Systemd Usage

Server unit (/etc/systemd/system/ssh-tunnel-server.service):

[Unit]
Description=ssh-tunnel-gateway server
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=gw-tunnel
WorkingDirectory=/opt/ssh-tunnel
EnvironmentFile=/etc/ssh-tunnel/server.env
ExecStart=/usr/local/bin/ssh-tunnel-server -d
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Example server env (/etc/ssh-tunnel/server.env):

API_KEY=change-me
SERVER_PORT=12000
SSH_USER=gw-tunnel
SSH_JUMP_USER=li
SSH_PUBLIC_HOST=gateway.example.com
AUTHORIZED_KEYS_PATH=/home/gw-tunnel/.ssh/authorized_keys
AGENT_REVERSE_BIND_HOST=0.0.0.0

Agent unit (/etc/systemd/system/ssh-tunnel-agent.service):

[Unit]
Description=ssh-tunnel-gateway agent
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=root
WorkingDirectory=/opt/ssh-tunnel
EnvironmentFile=/etc/ssh-tunnel/agent.env
ExecStart=/usr/local/bin/ssh-tunnel-agent -d
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Example agent env (/etc/ssh-tunnel/agent.env):

API_KEY=change-me
API_URL=http://<gateway_host>:12000
SSH_HOST=<gateway_host>
SSH_PORT=22
LOCAL_TARGET_HOST=127.0.0.1
LOCAL_TARGET_PORT=22

Enable and start:

sudo systemctl daemon-reload
sudo systemctl enable --now ssh-tunnel-server
sudo systemctl enable --now ssh-tunnel-agent

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ssh_tunnel_gateway-0.1.8.tar.gz (16.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ssh_tunnel_gateway-0.1.8-py3-none-any.whl (19.1 kB view details)

Uploaded Python 3

File details

Details for the file ssh_tunnel_gateway-0.1.8.tar.gz.

File metadata

  • Download URL: ssh_tunnel_gateway-0.1.8.tar.gz
  • Upload date:
  • Size: 16.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.14

File hashes

Hashes for ssh_tunnel_gateway-0.1.8.tar.gz
Algorithm Hash digest
SHA256 ab8798df3fc9373cec3b04e567aeb5914a2b180ab7596e25a5ed103d8748c28c
MD5 0f0cd70d080e6029ef0b6abdd9b187cf
BLAKE2b-256 e83433a16edc97c25fff67038ab2eccfc8e66aa879b97a7b6f43ff0230a3329d

See more details on using hashes here.

File details

Details for the file ssh_tunnel_gateway-0.1.8-py3-none-any.whl.

File metadata

File hashes

Hashes for ssh_tunnel_gateway-0.1.8-py3-none-any.whl
Algorithm Hash digest
SHA256 50fc43993e0179a8d1fdb492abf414c98f48d9232685397dff3d83c518884fcf
MD5 1a0ae853531284eb050d241c390243d2
BLAKE2b-256 00ff64eb203d692848d60cbc5bd414370e07857a2278b91d91b997aade93b7c7

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page